以下是一个可选的解决方案,try 使用循环而不是正则表达式:
我们的 idea 是找到包含pip install
个文本的行,因此它们是我们感兴趣的行.然后,将命令拆分成单词,并循环使用它们,直到我们到达命令的包部分.
首先,我们将定义包的正则表达式.请记住,一个包裹可以是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|$)/;
注意named groups,package_part
用来标识"Package with Version"字符串,而package_name
用来提取包名.
About the arguments
我们有两种类型的CLI参数:options和flags.
options的问题是,我们需要理解,下一个单词不是包名,而是option值.
因此,首先我列出了pip install
中的所有options命令:
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 handleArgument = (argument, restCommandWords) => {
let index = 0;
index += argument.length + 1; // +1 for the space removed by 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;
};
该函数接收所标识的参数和命令的其余部分,并将其拆分成单词.
(在这里,您将开始看到"索引计数器".因为我们还需要找到每个发现的位置,所以我们需要跟踪原始文本中的当前位置).
在函数的最后几行中,您可以看到我同时处理--option=something
和--option something
.
The Parser
现在,主解析器正在将原始文本拆分成行,然后再拆分成单词.
每个操作都必须更新global index以跟踪我们在文本中的位置,而且,这个索引帮助我们在文本中进行搜索和查找,而不会落入错误的子串,购买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 for the newline
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;
};