easimonenko Evgeny Simonenko

Обзор пакетов Node.js для разбора опций командной строки

30 Dec 2016 |  Tutorial  |  Command Line Interface   Node.js   JavaScript  

Node.js, как и другие среды разработки, предоставляет базовые средства работы с опциями командной строки. В нашем случае это массив process.argv. Но обычно, кроме простейших случаев типа A + B, обрабатывать опции командной строки вручную очень неудобно. Для этого есть несколько популярных пакетов. Я написал небольшую программу, которая построила сводную таблицу по этим пакетам, выбрал из них три самых популярных и рассмотрел их поближе. (Материал статьи на 7 января 2020 года по прежнему актуален, сводная таблица обновлена и дополнена.)

Данные из NPM:

# NPM Package NPM Stars Deprecated Last Version Last Update Created Dependencies
1 commander 1011   4.1.0 2020-01-06 2011-08-15 0
2 yargs 512   15.1.0 2020-01-02 2013-11-23 11
3 minimist 432   1.2.0 2019-08-11 2013-06-25 0
4 optimist 143 V 0.6.1 2018-03-21 2010-12-21 2
5 meow 79   6.0.0 2019-12-07 2013-01-24 11
6 cli 69   1.0.1 2018-03-15 2011-01-01 2
7 command-line-args 49   5.1.1 2019-03-31 2014-05-27 4
8 nopt 47   4.0.1 2020-01-02 2011-03-30 2
9 nomnom 32 V 1.8.1 2018-03-17 2011-04-08 2
10 argparse 21   1.0.10 2018-02-27 2012-05-17 1
11 stdio 9   2.0.1 2019-12-19 2013-03-16 0
12 dashdash 9   1.14.1 2017-12-28 2013-02-28 1
13 has-flag 5   4.0.0 2019-04-06 2015-07-08 0
14 clp 2   4.0.11 2019-01-03 2015-04-17 3
15 clap 1   2.0.1 2019-12-17 2014-02-10 1
16 argentum 0   0.6.0 2016-07-29 2015-11-26 0
17 getoptie 0   1.0.2 2015-03-09 2015-03-09 0

Данные из GitHub:

# NPM Package GitHub Repository GitHub Stars Last Commit
1 commander tj/commander.js 16885 2020-01-06
2 yargs yargs/yargs 7154 2020-01-02
3 minimist substack/minimist 3950 2015-08-29
4 optimist substack/node-optimist 2589 2014-02-05
5 meow sindresorhus/meow 2032 2019-12-07
6 cli node-js-libs/cli 772 2016-10-23
7 command-line-args 75lb/command-line-args 451 2019-09-22
8 nopt npm/nopt 478 2019-01-26
9 nomnom harthur/nomnom 471 2015-09-09
10 argparse nodeca/argparse 359 2019-11-05
11 stdio sgmonda/stdio 143 2019-12-19
12 dashdash trentm/node-dashdash 124 2019-08-28
13 has-flag sindresorhus/has-flag 53 2019-05-31
14 clp IonicaBizau/clp 12 2019-01-03
15 clap lahmatiy/clap 16 2020-01-06
16 argentum rumkin/argentum 1 2016-07-29
17 getoptie avz/node-getoptie 0 2015-03-09

Сводная таблица

Эта таблица была сгенерирована небольшой программой на JavaScript. Исходные тексты этого обзора, включая и эту программу, расположены в репозитории на GitHub. Так как через некоторое время эти данные скорее всего устареют, вы можете, загрузив себе эти исходники, перегенерировать эту таблицу, а также пополнить её новыми данными просто добавив соответствующие строки в файл со списком пакетов.

Пакеты в таблице упорядочены по рейтингу, который считается на основе количества звёзд на NPM и GitHub по формуле:

npmStars * k + githubStars

Коэффициент k понадобился, так как звёзды на NPM выглядят “весомее” звёзд на GitHub. Сам коэффициент считается очень просто: суммируем количество звёзд на NPM и на GitHub, затем делим число звёзд на GitHub на число звёзд на NPM, округляем получившееся число, это и есть наш коэффициент k:

k = floor( Sgithub / Snpm)

Из получившейся таблицы хорошо видно, что главный фаворит, это пакет commander. Далее идут с близким рейтингом пакеты minimist и yargs. Хороший рейтинг имеет также пакет optimist, но автором он объявлен устаревшим, а на его место он рекомендует им же написанный пакет minimist, а также советует посмотреть yargs и nomnom. В качестве преемника optimist также позиционируется пакет yargs. Авторы объявленного устаревшим nomnom рекомендуют commander.

Таким образом в первую очередь нужно рассмотреть пакеты commander, minimist и yargs. Вероятно есть смысл также обратить внимание на пакеты meow и nopt, но не в этот раз. Уже после публикации статьи, благодаря сервису npms.io удалось обнаружить ещё один популярный пакет cli.

commander

Научиться использовать пакет commander несложно. Автор предоставил, хоть и не всегда ясную, но всё же неплохую документацию. Чтобы разобраться, как использовать этот пакет, нужно было как следует поэкспериментировать. Ниже я опишу основные моменты этого пакета.

Итак, после того как мы загрузили пакет:

const commander = require('commander')

Мы можем, вызывая последовательно или раздельно его функции, настроить его на обработку опций командной строки. При этом пакет обеспечивает:

Короткие опции объявляются так:

commander
  .option('-a', 'option a')

Первый аргумент функции option задаёт формат опции, а второй даёт ей словесное описание. Доступ к опции -a в коде программы осуществляется через соответствующее свойство commander:

if (commander.a) {
  console.log(commander.a)
}

Пример для длинной опции:

commander
  .option('--camel-case-option', 'camel case option')

При этом в коде доступ к опции будет происходить по имени camelCaseOption.

Возможно задание для опций параметров как обязательных, так необязательных:

commander
  .option('-s, --source <path>', 'source file')
  .option('-l, --list [items]', 'value list', toArray, [])

Во втором случае, параметр у опции list необязателен, для него назначены функция-обработчик и значение по-умолчанию.

Параметры опций могут обрабатываться также с помощью регулярных выражений, например:

commander
  .option('--size [size]', 'size', /^(large|medium|small)$/i)

Субкоманда подразумевает, что для неё пишется отдельный модуль. При этом, если основная программа называется program, а субкоманда command, то модуль субкоманды должен называться program-command. Опции, переданные после субкоманды передаются модулю команды.

commander
  .command('search <first> [other...]', 'search with query')
  .alias('s')

Для автоматической подсказки можно указать версию программы:

commander.version('0.2.0')

Подсказка может быть сопровождена дополнительными действия, например, дополнена нестандартными текстом. Для этого нужно обрабатывать событие --help.

commander.on('--help', () => {
  console.log('  Examples:')
  console.log('')
  console.log('    node commander.js')
  console.log('    node commander.js --help')
  console.log('    node commander.js -h')
  ...
  console.log('    node commander.js --size large')
  console.log('    node commander.js search a b c')
  console.log('    node commander.js -abc')
})

Завершается настройка вызовом функции parse с параметром process.argv:

commander.parse(process.argv)

minimist

Автор пакета minimist предоставил весьма минималистичную документацию. Но всё равно попробуем разобраться.

После того как мы загрузили пакет, подключим и воспользуемся им:

const minimist = require('minimist')

const args = minimist(process.argv.slice(2))

console.dir(args)

Этот незамысловатый код позволит нам начать работать с этим пакетом. Поэкспериментируем:

node minimist.js
{ _: [] }

Что мы здесь видим? Набор разобранных опций организуется в объект. Свойство с именем _ содержит список параметров, не связанных с опциями. Например:

node minimist.js a b c
{ _: [ 'a', 'b', 'c' ] }

Продолжим эксперимент:

node minimist.js --help
{ _: [], help: true }

Как видим, minimist не предоставляет автоматического отображения подсказки, а просто определяет наличие данной опции.

Поэкспериментируем ещё:

node minimist.js -abc
{ _: [], a: true, b: true, c: true }

Всё верно. Посмотрим ещё:

node minimist.js --camel-case-option
{ _: [], 'camel-case-option': true }

В отличие от minimist никаких преобразований.

Опция с параметром:

node minimist.js --source path
{ _: [], source: 'path' }

Со знаком равно тоже работает:

node minimist.js --source=path
{ _: [], source: 'path' }

Поддерживается специальный режим передачи опций с использванием --:

node minimist.js -h -- --size=large
{ _: [ '--size=large' ], h: true }

Аргументы, следующие за -- не обрабатываются и просто помещаются в свойство _.

Вот в общем-то и всё, что есть в базе. Посмотрим, какие возможности настройки обработки опций предлагает нам minimist.

Для настройки обработки аргументов командной строки мы должны передать парсеру второй параметр с нашими настройками. Рассмотрим на примерах:

const minimist = require('minimist')

const args = minimist(process.argv.slice(2), {
  string: ['size'],
  boolean: true,
  alias: {'help': 'h'},
  default: {'help': true},
  unknown: (arg) => {
    console.error('Unknown option: ', arg)
    return false
  }
})

console.dir(args)
node minimist-with-settings.js --help
{ _: [], help: true, h: true }
node minimist-with-settings.js -h
{ _: [], h: true, help: true }

Мы задали для опции --help синоним -h. Результат, как видим, идентичен.

Опция boolean, установленная в true, говорит о том, что все опции без параметров после знака равно будут иметь булево значение. Например:

node minimist-with-settings.js --no-help
{ _: [], help: false, h: false }

Здесь мы увидели, как обрабатываются булевы опции: префикс no устанавливает значение опции равным false.

Но такой пример при этом больше не работает, нужен знак равно:

node minimist-with-settings.js --size large
Unknown option:  large
{ _: [], size: '', help: true, h: true }

Здесь же мы увидели обработку неизвестной опции и опции по-умолчанию.

Общий вывод: по сравнению с commander довольно минималистично, но вполне удобно.

yargs

В отличие от minimist и commander yargs предлагает весьма пространную документацию, доступную по ссылке: http://yargs.js.org/docs/.

Как обычно начнём с минимального примера:

const yargs = require('yargs')

console.dir(yargs.argv)
node yargs.js
{ _: [], '$0': 'yargs.js' }

Здесь мы видим пустой список необработанных опций, а также имя файла нашей программы.

Рассмотрим пример посложней:

node yargs.js -abc --help --size=large 1 2 3
{ _: [ 1, 2, 3 ],
  a: true,
  b: true,
  c: true,
  help: true,
  size: 'large',
  '$0': 'yargs.js' }

Здесь поинтереснее будет: во-первых, переданные опции восприняты верно; во-вторых, для их обработки мы не написали ни строчки кода.

Но уже здесь видно, что опция --help без предварительной настройки по предназначению не обрабатывается.

Рассмотрим теперь как использовать yargs в более сложных случаях на следующем примере:

const yargs = require('yargs')

yargs
  .usage('Usage: $0 -abc [--list 1,2,3] --size large|meduim|small [--help]')
  .version('1.0.0')
  .demand(['size'])
  .choices('size', ['large', 'medium', 'small'])
  .default('list', [], 'List of values')
  .describe('list', 'value list')
  .array('list')
  .help('help')
  .alias('help', 'h')
  .example('$0 --size=medium')
  .epilog('(c) 2016 My Name')

console.dir(yargs.argv)
node yargs.js -h

Получаем:

Usage: yargs.js -abc [--list 1,2,3] --size large|meduim|small [--help]

Options:
  --version   Show version number                                      [boolean]
  --list      value list                       [array] [default: List of values]
  --help, -h  Show help                                                [boolean]
  --size                        [required] [choices: "large", "medium", "small"]

Examples:
  yargs.js --size=medium
(c) 2016 My Name

В этом примере мы задали текст, который будет выводиться с опцией help. Опции help мы также указали синоним h. А ещё указали версию программы, которая будет выводиться с опцией version.

Опция size обязательная, более того, для неё задан список допустимых значений.

node yargs.js --size large
{ _: [],
  version: false,
  help: false,
  h: false,
  size: 'large',
  list: [],
  '$0': 'yargs.js' }

Если size передать значение, не соответствующее ни одному из списка, то получим сообщение об ошибке:

node yargs.js --size=middle
...
Invalid values:
  Argument: size, Given: "middle", Choices: "large", "medium", "small"

Для опции list указано значение по умолчанию. Эта опция также трактуется как массив значений:

node yargs.js --list 1 2 3 --size=large
{ _: [],
  version: false,
  help: false,
  h: false,
  list: [ 1, 2, 3 ],
  size: 'large',
  '$0': 'yargs.js' }

Резюме

Пакеты commander и minimist выделяются минимальным числом зависимостей, в то время как yargs поражает не только числом своих зависимостей, но и числом своих возможностей.

Какой пакет лучше, очевидно, сказать нельзя. По мне, minimist вполне достаточен для простейших случаев, но в сложных ситуациях при его использовании придётся написать много кода обработки опций вручную. В этом случае лучше воспользоваться commander или yargs, на ваш вкус.

Все три рассматриваемые здесь пакета имеют определения типов на TypeScript, что позволяет иметь в Code работающий IntelliSense.

Немного аналитики

Спустя три месяца с момента написания и публикации этой статьи на Habrahabr в период с сентября по декабрь 2016 произошли некоторые интересные изменения в сводной таблице.

Самый главный вывод, какой я бы хотел сделать, это то, что стоит задуматься, а нужно ли в своих новых проектах использовать популярный minimist, ведь он достаточно давно не разрабатывается?

Для удобства сравнения сводных таблиц привожу их здесь в виде картинок:

Расклад голосования на Habrahabr таков. Проголосовало 72 читателя, воздержалось 65. Из них отдали свои голоса следующим образом:

  1. yargs 31% (22)

  2. commander 29% (21)

  3. minimist 21% (15)

  4. process.argv 8% (6)

  5. другой пакет 7% (5)

  6. optimist 4% (3)

Налицо наибольшая популярность у yargs и commander, при этом minimist также достаточно популерен.

Обновление от 8 февраля 2019

Сводная таблица обновлена, преобразована в формат Markdown и дополнена пакетами has-flag, clp и clap. Результаты голосования остались примерно такими же, какими были при последнем обновлении от 30 декабря 2016 года.

Обновление от 7 января 2020

Обновлена сводная таблица. Рейтинг пакетов существенно не изменился. Результаты голосования также показывают, что за год предпочтения ощутимо не поменялись.

Примечание

Эта статья в немного другой редакции была первоначально опубликована (13 сентября 2016) на Habrahabr: https://habrahabr.ru/post/309936/

(c) Симоненко Евгений, 2016