#javascript #node.js
#javascript #node.js
Вопрос:
Я пытаюсь создать функцию для разбора аргументов командной строки из текстового файла. Это означает, что каждый флаг и значение должны быть возвращены как отдельные элементы в одном массиве. Строки следует игнорировать, если они пустые или начинаются с #
, ;
или ]
.
У меня возникли многочисленные проблемы с моей текущей функцией. Во-первых, разделение массивов внутри функции reduce не добавляет массивы в накопитель, как при использовании push, но добавляет новый массив в накопитель. Во-вторых, строки в кавычках могут быть разделены на массивы, даже если они должны рассматриваться как отдельные аргументы.
const argsFile = `
# Command line arguments
--download example.com
--pass
--no-fail
--output "C:\Users\User\Desktop\New Folder"
--binary-location 'C:\Users\Desktop\New Folder\executable program.exe'
`;
let parsedArguments = argsFile.split(/r?n/)
.filter(argument => (!argument.startsWith('#') amp;amp; !argument.startsWith(';') amp;amp; !argument.startsWith(']')))
.reduce((a, c) => [...a, c.split(' ')])
.filter(argument => argument !== '');
console.dir(parsedArguments)
Это желаемый результат для моей функции:
[
"--download",
"example.com",
"--pass",
"--no-fail",
"--output",
"C:\Users\User\Desktop\New Folder",
"--binary-location",
"C:\Users\Desktop\New Folder\executable program.exe"
]
Как я могу изменить свою функцию для достижения желаемого результата? Если есть библиотека, которая справилась бы с этой ситуацией, я не смог ее найти.
Ответ №1:
Yargs, похоже, довольно надежно анализирует аргументы для строки, и это довольно настраиваемо.
Я придумал следующее, которое, похоже, дает желаемый результат. Однако я не тестировал его ни с какими другими строками:
const parse = require("yargs-parser");
const argsFile = `
# Command line arguments
--download example.com
--pass
--no-fail
--output "C:\Users\User\Desktop\New Folder"
--binary-location 'C:\Users\Desktop\New Folder\executable program.exe'
`;
let parsedArguments = argsFile
.split(/r?n/)
.filter(
(argument) =>
!argument.startsWith("#") amp;amp;
!argument.startsWith(";") amp;amp;
!argument.startsWith("]")
)
.map((line) => {
return parse(line, {
string: true,
configuration: {
"boolean-negation": false,
"camel-case-expansion": false,
},
});
})
.map((ar) => {
delete ar._;
let properties = Object.keys(ar);
if (properties.length == 0) return [];
return [
"--" properties[0],
typeof ar[properties[0]] == "boolean" ? "" : ar[properties[0]],
];
})
.filter((argument) => argument.length != 0);
let flatArgs = [].concat.apply([], parsedArguments).filter((i) => i != "");
console.dir(flatArgs);
Выдает следующее:
[ '--download',
'example.com',
'--pass',
'--no-fail',
'--output',
'C:\Users\User\Desktop\New Folder',
'--binary-location',
'C:\Users\Desktop\New Folder\executable program.exe' ]
Анализатор Yargs анализирует строку «слишком агрессивно» для ваших конкретных требований, поэтому мы должны выполнить сопоставление с некоторым изменением того, что сделал анализатор (добавив ‘—‘, игнорируя логические значения и т.д.). Затем, в конце, мы должны «сгладить» массив, поскольку каждая строка анализируется в свой собственный массив.
Редактировать: итак, если мы также должны позаботиться о коротких аргументах, yargs на самом деле не подойдет, поскольку у нас нет доступа к необработанной строке после синтаксического анализа. Однако мы могли бы использовать внутреннюю функцию yarg, которая маркирует строку (мне нужно было только перенести ее в js):
function tokenizeArgString(argString) {
if (Array.isArray(argString)) {
return argString.map(e => typeof e !== 'string' ? e '' : e);
}
argString = argString.trim();
let i = 0;
let prevC = null;
let c = null;
let opening = null;
const args = [];
for (let ii = 0; ii < argString.length; ii ) {
prevC = c;
c = argString.charAt(ii);
// split on spaces unless we're in quotes.
if (c === ' ' amp;amp; !opening) {
if (!(prevC === ' ')) {
i ;
}
continue;
}
// don't split the string if we're in matching
// opening or closing single and double quotes.
if (c === opening) {
opening = null;
}
else if ((c === "'" || c === '"') amp;amp; !opening) {
opening = c;
}
if (!args[i])
args[i] = '';
args[i] = c;
}
return args;
}
const argsFile = `
# Command line arguments
--download example.com
--pass
--no-fail
--output "C:\Users\User\Desktop\New Folder"
-a test
--binary-location 'C:\Users\Desktop\New Folder\executable program.exe'
`;
let parsedArguments = argsFile.split(/r?n/)
.filter(argument => (!argument.startsWith('#') amp;amp; !argument.startsWith(';') amp;amp; !argument.startsWith(']')))
.map(line => tokenizeArgString(line))
.filter(argument => argument.length != 0);
let flatArgsNoQuotes = [].concat.apply([], parsedArguments).map(args => args.replace(/['"] /g, '')).filter(i => i != "");
console.dir(flatArgsNoQuotes)
Комментарии:
1. Каким образом yargs слишком агрессивно анализирует строку? Что касается добавления с
--
, это моя ошибка, но также могут быть аргументы, которые начинаются с одного-
like-a file.txt
.2. Выполняет слишком агрессивный синтаксический анализ для ваших требований , так как любой стандартный анализатор избавится от дефисов, но они вам нужны. Хорошо, мы должны позаботиться об аргументах с одним дефисом, что-нибудь еще? В вашем исходном примере такого аргумента нет, вот почему я спрашиваю.
3. @Nostupidquestions немного отредактировал ответ, чтобы также учитывать короткие аргументы.
4. Я считаю, что одиночные аргументы — это единственная другая вещь. Я пытаюсь воссоздать функциональность анализатора из параметра youtube-dl —config-location. github.com/ytdl-org/youtube-dl /…