Разбор аргументов командной строки из конфигурационного текстового файла

#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 /…