Analyser la commande 'pip install' pour obtenir les plages dans le texte du package installé
P粉431220279
P粉431220279 2023-09-07 18:54:45
0
2
595

Je travaille sur un projet qui me demande d'extraire le nom et l'emplacement d'un package Python installé à l'aide de la commande pip install.

Une page Web contient un élément code qui contient plusieurs lignes de texte et des commandes bash. Je souhaite écrire un code JS capable d'analyser ce texte et de trouver les packages et leur emplacement dans le texte.

Par exemple, si le texte est :

$ pip install numpy
pip install --global-option build_ext -t ../ pandas>=1.0.0,<2
sudo apt update
pip uninstall numpy
pip install "requests==12.2.2"

Je veux obtenir des résultats comme celui-ci :

[
    {
        "name": "numpy",
        "position": 14
    },
    {
        "name": "pandas",
        "position": 65
    },
    {
        "name": "requests",
        "position": 131
    }
]

Comment puis-je implémenter cette fonctionnalité en JavaScript ?

P粉431220279
P粉431220279

répondre à tous(2)
P粉773659687

Vous pouvez voir le code que j'ai expliqué dans cette réponse.

Voici une autre solution similaire, davantage basée sur les expressions régulières :

const pipOptionsWithArg = [
  '-c',
  '--constraint',
  '-e',
  '--editable',
  '-t',
  '--target',
  '--platform',
  '--python-version',
  '--implementation',
  '--abi',
  '--root',
  '--prefix',
  '-b',
  '--build',
  '--src',
  '--upgrade-strategy',
  '--install-option',
  '--global-option',
  '--no-binary',
  '--only-binary',
  '--progress-bar',
  '-i',
  '--index-url',
  '--extra-index-url',
  '-f',
  '--find-links',
  '--log',
  '--proxy',
  '--retires',
  '--timeout',
  '--exists-action',
  '--trusted-host',
  '--cert',
  '--client-cert',
  '--cache-dir',
];
const optionWithArgRegex = `( (${pipOptionsWithArg.join('|')})(=| )\S+)*`;
const options = /( -[-\w=]+)*/;
const packageArea = /["']?(?<package_part>(?<package_name>\w[\w.-]*)([=<>~!]=?[\w.,<>]+)?)["']?(?=\s|$)/g;
const repeatedPackages = `(?<packages>( ${packageArea.source})+)`;
const whiteSpace = / +/;
const PIP_COMMAND_REGEX = new RegExp(
  `(?<command>pip install${optionWithArgRegex}${options.source})${repeatedPackages}`.replaceAll(' ', whiteSpace.source),
  'g'
);
export const parseCommand = (command) => {
  const matches = Array.from(command.matchAll(PIP_COMMAND_REGEX));

  const results = matches.flatMap((match) => {
    const packagesStr = match?.groups.packages;
    if (!packagesStr) return [];

    const packagesIndex = command.indexOf(packagesStr, match.index + match.groups.command.length);

    return Array.from(packagesStr.matchAll(packageArea))
      .map((packageMatch) => {
        const packagePart = packageMatch.groups.package_part;
        const name = packageMatch.groups.package_name;

        const startIndex = packagesIndex + packagesStr.indexOf(packagePart, packageMatch.index);
        const endIndex = startIndex + packagePart.length;

        return {
          type: 'pypi',
          name,
          version: undefined,
          startIndex,
          endIndex,
        };
      })
      .filter((result) => result.name !== 'requirements.txt');
  });

  return results;
};
P粉194541072

Voici une solution alternative, essayez d'utiliser une boucle au lieu d'une expression régulière :

L'idée est de retrouver des lignes contenant pip install du texte, ce sont ces lignes qui nous intéressent. Ensuite, divisez la commande en mots et parcourez-les jusqu'à ce que vous atteigniez la partie package de la commande.

Tout d’abord, nous allons définir une expression régulière pour le package. N'oubliez pas qu'un colis peut ressembler à quelque chose comme pip install 'stevedore>=1.3.0,<1.4.0' "MySQL_python==1.2.2" : 

const packageArea = /(?<=\s|^)["']?(?<package_part>(?<package_name>\w[\w.-]*)([=<>~!]=?[\w.,<>]+)?)["']?(?=\s|$)/;

Remarquez le groupement nommé , package_part 用于识别“带版本的包”字符串,而 package_name utilisé pour extraire le nom du package.


À propos des paramètres

Nous avons deux types d'arguments de ligne de commande : options et flags.

Le problème avec

options est que nous devons comprendre que le mot suivant n'est pas le nom du package, mais la valeur options.

Donc, j'ai d'abord listé toutes les options dans la commande pip install :

const pipOptionsWithArg = [
  '-c',
  '--constraint',
  '-e',
  '--editable',
  '-t',
  '--target',
  '--platform',
  '--python-version',
  '--implementation',
  '--abi',
  '--root',
  '--prefix',
  '-b',
  '--build',
  '--src',
  '--upgrade-strategy',
  '--install-option',
  '--global-option',
  '--no-binary',
  '--only-binary',
  '--progress-bar',
  '-i',
  '--index-url',
  '--extra-index-url',
  '-f',
  '--find-links',
  '--log',
  '--proxy',
  '--retires',
  '--timeout',
  '--exists-action',
  '--trusted-host',
  '--cert',
  '--client-cert',
  '--cache-dir',
];

J'ai ensuite écrit une fonction que j'utiliserai plus tard pour décider quoi faire lorsqu'elle voit un argument :

const handleArgument = (argument, restCommandWords) => {
  let index = 0;
  index += argument.length + 1; // +1 是为了去掉 split 时的空格

  if (argument === '-r' || argument === '--requirement') {
    while (restCommandWords.length > 0) {
      index += restCommandWords.shift().length + 1;
    }
    return index;
  }

  if (!pipOptionsWithArg.includes(argument)) {
    return index;
  }

  if (argument.includes('=')) return index;

  index += restCommandWords.shift().length + 1;
  return index;
};

Cette fonction reçoit les paramètres reconnus et le reste de la commande, découpés en mots.

(Ici, vous commencez à voir le « compteur d'index ». Puisque nous devons également trouver la position de chaque découverte, nous devons garder une trace de la position actuelle dans le texte original).

Dans les dernières lignes de la fonction, vous pouvez voir que je gère --option=something--option something les deux cas.


Parseur

L'analyseur principal divise désormais le texte brut en lignes puis en mots.

Chaque opération doit mettre à jour l'index global pour savoir où nous en sommes dans le texte, et cet index nous aide à rechercher et trouver dans le texte sans rester coincé dans la mauvaise sous-chaîne, en utilisant indexOf(str, counterIndex) :

export const parseCommand = (multilineCommand) => {
  const packages = [];
  let counterIndex = 0;

  const lines = multilineCommand.split('\n');
  while (lines.length > 0) {
    const line = lines.shift();

    const pipInstallMatch = line.match(/pip +install/);
    if (!pipInstallMatch) {
      counterIndex += line.length + 1; // +1 是为了换行符
      continue;
    }

    const pipInstallLength = pipInstallMatch.index + pipInstallMatch[0].length;
    const argsAndPackagesWords = line.slice(pipInstallLength).split(' ');
    counterIndex += pipInstallLength;

    while (argsAndPackagesWords.length > 0) {
      const word = argsAndPackagesWords.shift();

      if (!word) {
        counterIndex++;
        continue;
      }

      if (word.startsWith('-')) {
        counterIndex += handleArgument(word, argsAndPackagesWords);
        continue;
      }

      const packageMatch = word.match(packageArea);
      if (!packageMatch) {
        counterIndex += word.length + 1;
        continue;
      }

      const startIndex = multilineCommand.indexOf(packageMatch.groups.package_part, counterIndex);
      packages.push({
        type: 'pypi',
        name: packageMatch.groups.package_name,
        version: undefined,
        startIndex,
        endIndex: startIndex + packageMatch.groups.package_part.length,
      });

      counterIndex += word.length + 1;
    }
  }

  return packages;
};
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal