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

  • Tutorial

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


(Материал статьи на 7 января 2020 года по прежнему актуален, сводная таблица обновлена и дополнена.)


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


(Из-за узкого формата страницы пришлось одну таблицу разбить на две: одна с информацией с NPM, другая с GitHub.)


# 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

# 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, но не в этот раз.


commander


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


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


const commander = require('commander')

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


  • короткие опции, например, -s;
  • длинные опции, например, --source;
  • альтернативные названия опций, например, --source и -s;
  • дополнительные параметры;
  • значения по-умолчанию для дополнительных параметров;
  • обработчики для дополнительных параметров;
  • субкоманды, например, install package;
  • автоматическое формирование подсказки;
  • настройку подстказки.

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


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 предлагает весьма пространную документацию, доступную по ссылке.


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


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.


Архив


В первоначальной редакции от 13 сентября 2016 года сводная таблица в начале статьи была следующей:


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


Обновление


Добавлены в таблицу ещё три пакета, о которых сообщили в комментариях читатели:



И два пакета, которые удалось обнаружить благодаря сервису npms.io:



Соответственно, обновлённая таблица ниже:


Обновлённая сводная таблица


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


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


Таблица за декабрь 2016


  • Рейтинг почти всех модулей вырос за исключением малопопулярных
    stdio, getoptie, argentum.
  • Вперёд "рванул" yargs и вышел на второе место, сместив на третье minimist.
    Это вполне объяснимо: minimist не развивается уже больше года, в то время как
    для yargs регулярно выходят новые версии.
  • Пакет command-line-args обошёл stdio.
  • Из 14 пакетов только два улучшили свои позиции. Но скорость роста рейтинга
    разная, поэтому через несколько месяцев можно ожидать выявления новых фаворитов.
  • Не совершенствовались следующие модули: minimist, optimist (deprecated),
    nomnom (deprecated), stdio, getoptie и argentum. Вероятно эти модули скоро можно
    будет объявлять deprecated.
    Самый главный вывод, который хотелось бы сделать, это то, что стоит задуматься, а
    нужно ли в своих новых проектах использовать популярный minimist, ведь он
    достаточно давно не разрабатывается?

Расклад голосования на 30 декабря 2016 таков. Проголосовало 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


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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Какой пакет для разбора опций командной строки в Node.js используете Вы? (Если несколько, то выберите наиболее предпочтительный вариант.)

  • 31,9%commander30
  • 19,2%minimist18
  • 30,8%yargs29
  • 3,2%optimist3
  • 7,4%process.argv7
  • 7,4%другой7

Комментарии 34

    –1

    Я противник этих ваших заклинаний вида -xa -o2 --lala -- haha, поэтому использую тривиальную функцию, которая разбирает аргументы вида flag1 flag2 key1=value1 key1=value2 key2=value3=value4 и всё.

      0
      Вероятно, даже такая функция не совсем тривиальна? Допустимые символы, кавычки, апострофы, обратные слеши, регулярные выражения? Покажете?
        –1
        var args = {}
        process.argv.slice(2).forEach( param => {
            var values = param.split( '=' )
            var key = values.shift()
            args[ key ] = ( args[ key ] || [] ).concat( values )
        } )

        Надо бы оформить в виде пакета, да. :-D

          +1

          key1="foo=bar" распарсит неправильно.
          Плюс, нет никаких страховок от опечаток: например, лишний пробел до знака = (например, foo =bar) добавит в результат поле с пустой строкой в качестве ключа.

            –1

            Всё правильно распарсит. Если на входе не предполагается коллекция, то нужно будет список значений сджойнить в строку. А вы где-нибудь вообще видели защиту от таких опечаток?

              +1
              Всё правильно распарсит.

              Точно? Я вот хочу, чтобы ключу foo (см. ниже) соответствовала строка "bar=baz": Для сравнения я взял yargs:


              $ node yargs.js --foo="bar=baz"
              { _: [], foo: 'bar=baz', '$0': '/usr/bin/nodejs yargs.js' }
              
              $ node custom.js foo="bar=baz" # это ваш метод
              { foo: [ 'bar', 'baz' ] }

              Т.е. если понадобится передать знак равно в значения какого-нибудь из аргументов, ваш способ выдаст не тот результат, на который рассчитываешь.


              А вы где-нибудь вообще видели защиту от таких опечаток?

              Именно защиту — нет. Однако, используя минусы в аргументах можно однозначно идентифицировать ключ:


              $ node yargs.js --foo =bar
              { _: [], foo: '=bar', '$0': '/usr/bin/nodejs yargs.js' }
              
              $ node custom.js foo =bar
              { foo: [], '': [ 'bar' ] }
                –2

                У вас есть два варианта:


                console.log( args.foo.join( '=' ) )

                var args = {}
                process.argv.slice(2).forEach( param => {
                    var values = param.split( '=' )
                    var key = values.shift()
                    args[ key ] = ( args[ key ] || [] ).concat( values.join( '=' ) )
                } )

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

                  +2
                  Вы просто привыкли к этому кактусу и научились получать от этого удовольствие.

                  Так можно на все ответить. Например, я привык к удобству коммандера, который позволяет создавать суб-команды, вставлять описание, иметь встроенные команды (типа, help), удобные action. И все это из коробки. Конечно, все это излишество, но тогда и ваши ключи тоже излишество, просто передавайте строку, а лучше gzip-ованную, и пользователь и программист пусть сами разбираются.
                    0

                    Необходимость писать два дефиса перед каждым ключом — так себе удобство.


                    var args = {}
                    process.argv.slice(2).forEach( param => {
                        var values = param.split( '=' )
                        var key = values.shift()
                        args[ key ] = ( args[ key ] || [] ).concat( values.join( '=' ) )
                    } )
                    
                    var actions = {
                        'help help' : args => `help\t\tPrints help (default action)\n` ,
                        'help' : args => `\nSuper-puper server!\n${ actions['actions']() }${ actions['options']() }`,
                        'actions help' : args => `actions\t\tPrints all available actions\n` ,
                        'actions' : args => {
                            var res = `\nActions:\n\n`
                            Object.keys( actions ).forEach( path => {
                                if( !/ help$/.test( path ) ) return
                                res += actions[ path ]( args )
                            } )
                            return res
                        } ,
                        'options help' : args => `options\t\tPrints common options\n` ,
                        'options' : args => {
                            var res = `\nCommon options:\n\n`
                            res += `host=localhost\thost to bind server\n`
                            res += `port=80\t\tport to bind server\n`
                            return res
                        } ,
                        'server actions help' : args => `server actions\tPrints all actions supported by server\n` ,
                        'server actions' : args => {
                            var res = `\nServer actions:\n\n`
                            Object.keys( actions ).forEach( path => {
                                if( !/^server .+ help$/.test( path ) ) return
                                res += actions[ path ]( args )
                            } )
                            return res
                        } ,
                        'server start help' : args => `server start\tStarts server\n` ,
                        'server start' : args => {
                            return `Server started at ${ args.port || 80 } port`
                        } ,
                        'server stop help' : args => `server stop\tStops server\n` ,
                        'server stop' : args => {
                            return `Server stopped`
                        } ,
                    }
                    
                    var keys = Object.keys( args )
                    while( keys.length ) {
                        var path = keys.join(' ')
                        var handler = actions[ path ]
                        if( handler ) break
                        keys.pop()
                    }
                    if( !keys.length ) handler = actions[ 'help' ]
                    
                    console.log( handler( args ) )

                    > node .
                    
                    Super-puper server!                                         
                    
                    Actions:                                                    
                    
                    help            Prints help (default action)                
                    actions         Prints all available actions                
                    options         Prints common options                       
                    server actions  Prints all actions supported by server      
                    server start    Starts server                               
                    server stop     Stops server                                
                    
                    Common options:                                             
                    
                    host=localhost  host to bind server                         
                    port=80         port to bind server                         
                    0
                    node program --foo=1
                    


                    module.exports = new Proxy({}, {
                    	get (target, name) {
                    		for (let option of process.argv) {
                    			let [key, value] = option.split('=');
                    
                    			if (key === `--${name}`) {
                    				return value;
                    			}
                    		}
                    	}
                    });
                    


                    let options = require('./options');
                    
                    options.foo; // 1
                    


                    Правда тут есть нюанс с пустыми опциями, но это всего-лишь нюанс…
                      0

                      Ваш вариант отбрасывает всё, что после второго символа равенства в параметре. Вообще говоря, нет смысла использовать прокси в данном случае.

                      0
                      У вас есть два варианта:

                      Так себе варианты. А если требуется передать список, который может содержать произвольные символы, в т.ч. и равно? Т.е. такое:


                      node program.js --foo=bar --foo=baz --foo="a=b"

                      Ваш вариант просто сольёт всё в 1 строку, хотя я ожидаю массив: { foo: [ 'bar', 'baz', 'a=b' ] }


                      Вы просто привыкли к этому кактусу и научились получать от этого удовольствие.

                      Да. И не только я получаю, опции с 1 и 2-мя минусами (короткие и длинные) и знаками равно — вроде как стандарт в мире nix и все его придерживаются. Он довольно гибкий и удобный. И, по-моему, даже на win избавляются от слешей в опциях и используют минусы.

                        –1

                        Первый вариант сольёт, второй — не сольёт.


                        node program.js foo=bar foo=baz foo="a=b"

                        вроде как стандарт в мире nix и все его придерживаются.

                        То, что это стандарт де факто не делает его менее кривым. Я сторонник правильных решений и эволюции стандартов, а не форматирования мозгов под древние костыли.

                          0
                          Первый вариант сольёт, второй — не сольёт.

                          Это до тех пор, пока не захочется писать список так, как вы указали сначала:


                          $ node prog.js foo=bar foo=baz=qux foo="a=b"
                          { foo: [ 'bar', 'baz=qux', 'a=b' ] }
                          
                          $ node prog.js foo=baz=qux="a=b"
                          { foo: [ 'baz=qux=a=b' ] }

                          То, что это стандарт де факто не делает его менее кривым. Я сторонник правильных решений и эволюции стандартов, а не форматирования мозгов под древние костыли.

                          В чём заключается кривость стандарта? Да и ваш предложенный вариант намного хуже. Строки с символом = по-прежнему вызывают боль. Если аргумент будет списком произвольных строк, то придётся дублировать ключ:  foo=b foo="c=d", что убивает читаемость. Вариант -foo b "c=d" выглядит менее громоздко (к слову, вместо двух минусов для длинных ключей можно использовать 1, как в java, например).

                            0
                            Это до тех пор, пока не захочется писать список так, как вы указали сначала:

                            Очевидно, во втором варианте так писать будет нельзя. Вы что доказать-то пытаетесь?


                            В чём заключается кривость стандарта?

                            В минусах, двойных минусах, двойных минусах отключающих обработку двойных минусов, в однобуквенных ключах, в отсутствии группировки ключа и соответствующего ему его значения.


                            Если аргумент будет списком произвольных строк, то придётся дублировать ключ

                            В "стандартном" варианте его тоже придётся дублировать.


                            Вариант -foo b "c=d" выглядит менее громоздко

                            Это нигде не поддерживается.


                            вместо двух минусов для длинных ключей можно использовать 1

                            Ага, давайте запутаем всех окончательно. Есть негласное соглашение — однобуквенные ключи предварять одним дефисом, а словесные — двумя.


                            можно использовать 1, как в java, например

                            JVM — вообще чудесный пример костылей:


                            -XX:+HeapDumpOnOutOfMemoryError -Djava.rmi.server.hostname=example.org
                              0

                              del

                                0
                                Вы что доказать-то пытаетесь?

                                Нет, просто вы критикуете стандартный подход, а я — ваш. Конечно, ваш подход хорош для простых случаев, когда всяких передаваемых опций мало и они тривиальны. Однако, с ростом числа и типа опций ваш подход вызывает лишь проблемы.


                                Очень многие консольные программы используют такой синтаксис:


                                %программа% %команда/действие% %аргументы команды/действия% %доп. опции%
                                # Например:
                                git commit -m "message"
                                npm install foo -S -G
                                mount /dev/... /mnt/... -o loop

                                Команды и их аргументы идут без минусов. А минус(ы) перед аргументом указывают, что это лишь дополнение к команде. И это очень удобно по крайней мере по 2-м причинам: Во-первых, сразу можно отличить где сама команда, а где доп. флаги к ней. Во-вторых, позволяет вставлять опции в любое место при наборе команды:


                                npm install --save foo bar --only=prod
                                npm install foo --only=prod --save bar
                                npm install --only=prod foo bar --save
                                npm i foo bar -S # сокращённый вариант

                                Тут сразу видно что где: save и only — не названия пакетов, а опции к основной команде install, prod — значение опции only, а foo и bar — не название опций, а имена пакетов.


                                Но ок, допустим, я хочу реализовать некую альтернативу npm и использовать ваш метод передачи аргументов. Сравним:


                                npm install --only=prod foo bar@~2.7.1 qux --save
                                mpn install=foo=bar@~2.7.1=qux only=prod save # На сколько я понял, ваша версия выглядеть будет так 

                                Пока вроде нормально, хотя можно запутаться, что есть команда, а что дополнение к ней. А знак равно в качестве разделителя в списках выглядит странно, что, опять же, затрудняет восприятие. Пробелы или запятые выглядят привычнее для многих, да и визуально они лучше разбивают строку, в отличие от =.


                                А теперь пример посложнее. В аргументах команды install можно указывать версию, и там используется знак равно. Очень хочется увидеть, как тогда будет выглядеть аналог такой команды:


                                npm i --only=prod sax@">=0.1.0 <0.2.0" foo bar@==2.7.1 baz@>=1.0.0 -S -E
                                  0
                                  Очень многие консольные программы используют такой синтаксис

                                  То есть отошли в своё время от стандарта, посчитав, что совмещать несколько команд в одной программе проще, чем делать по отдельной программе на каждое действие, как было принято в *nix.


                                  npm i --only=prod sax@">=0.1.0 <0.2.0" foo bar@==2.7.1 baz@>=1.0.0 -S -E

                                  i — это install или info или init?
                                  S, E и G — это что за параметры? Чем они отличаются от s, e и g? npm -l ничего про них не говорит, гугл тоже молчит.


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


                                  npm install env=prod mod=sax@">=0.1.0 <0.2.0" mod=foo mod=bar@"=2.7.1" mod=baz@">=1.0.0"
                                    0

                                    Но формат версий можно сделать менее кривым, что позволит использовать и первый вариант:


                                    npm install=sax[0.1.0~0.2.0)=foo=bar[2.7.1]=baz[1.0.0~) env=prod

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


                                    npm install=express save
                                      0
                                      То есть отошли в своё время от стандарта, посчитав, что совмещать несколько команд в одной программе проще, чем делать по отдельной программе на каждое действие, как было принято в *nix.

                                      В unix-way — это когда 1 программа на 1 задачу.
                                      Вот есть гит. Гит выполняет 1 задачу — управляет репозиторием. Для каждой его команды надо было делать отдельную программу? И для каждой npm-команды надо было отдельную программу писать?
                                      Хотя вы вполне можете это сделать через alias'ы в баше (или его аналогах).


                                      S, E и G — это что за параметры? Чем они отличаются от s, e и g? npm -l ничего про них не говорит, гугл тоже молчит.

                                      Ну что-то совсем несерьёзно. Во-первых, мы говорим не про имена опций и что они означают, а про их формат. А во-вторых, научитесь пользоваться хелпом. Вроде на js пишете много, а вопросы задаёте, как будто ноду в первый раз видите:


                                      $ npm i --help # или npm install --help
                                      ...
                                      aliases: i, install <- ВОТ ТУТ ПОЯСНЯЕТСЯ, ЧТО ТАКОЕ i
                                      common options: [--save|--save-dev|--save-optional] [--save-exact]
                                      
                                      $ npm help i # или npm help install
                                      # Тут откроется полная справка по команде. Там перечислены в том числе её опции, и алиасы для них
                                      o -S, --save: Package will appear in your dependencies.
                                      
                                             o -D, --save-dev: Package will appear in your devDependencies.
                                      
                                             o -O, --save-optional: Package will appear in your optionalDependencies.  When using any of the above  options
                                               to save dependencies to your package.json, there are two additional, optional flags:
                                      
                                             o -E,  --save-exact:  Saved  dependencies  will  be  configured  with an exact version rather than using npm's
                                               default semver range operator.

                                      npm install env=prod mod=sax@">=0.1.0 <0.2.0" mod=foo mod=bar@"=2.7.1" mod=baz@">=1.0.0"

                                      И чем это лучше обычной версии (ну кроме того, что её придумали вы?)


                                      Но формат версий можно сделать менее кривым, что позволит использовать и первый вариант:

                                      Отлично! Вместо того, чтобы улучшить свой код и сделать его более универсальным, и реализовать наконец-таки экран знака равно, вы предлагаете заменить semver на свой никому неизвестный формат.
                                      Чем теперь semver не угодил? Фатальный недостаток?

                                        0
                                        Вот есть гит. Гит выполняет 1 задачу — управляет репозиторием. Для каждой его команды надо было делать отдельную программу?

                                        Именно так и было 10 лет назад


                                        Ну что-то совсем несерьёзно. Во-первых, мы говорим не про имена опций и что они означают, а про их формат.

                                        Так тут и проблема в этом коротком формате.


                                        А во-вторых, научитесь пользоваться хелпом.

                                        Ага, не догадался, что npm install --help или npm help install — совершенно разные вещи, вот дурак, наверно. :-)


                                        И чем это лучше обычной версии (ну кроме того, что её придумали вы?)

                                        Тут понятно за что отвечает каждый параметр, хотя способ указания версии всё портит, да.


                                        Чем теперь semver не угодил?

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

              0
              Эм. а почему в табличке нет argparse, у которого с 330k загрузок сутки?
                +1
                Не знал об этом пакете. В табличку добавить нетрудно. Постараюсь сделать завтра на свежую голову.
                  +1

                  Добавил в раздел "Обновление" (в конце статьи) расширенную таблицу с рекомендованным Вами пакетом.

                  0

                  Пользуясь случаем пропиарю свою библиотеку argentum. В общем больше напоминает minimist, но имеет отличия.


                  • Умеет приводить кебаб к кэмел кейсу: --some-option -> {someOption: true}
                  • Парсит вложенные значения: --obj.prop=true -> {obj: {prop: true}}
                  • Приводит true, false и числа к JS-типам (опционально) --value=1.2 -> {value: 1.2}
                  • Поддерживает массивы: --arr[] 1 2 3 -> {arr: [1, 2, 3]}
                  • Удаляет полученные значения из переданного массива: var argv = ['--a=1', 'hello']; argentum.parse(argv); argv; // -> ['hello']

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

                    0

                    Включил Ваш пакет в расширенную таблицу в конце статьи.

                      0

                      Спасибо.

                    0

                    А мне как-то ближе подход стандартного POSIX'ового getopt(3), для себя написал getoptie, который умеет всё то же, что и getopt(3), но помимо этого ещё и берёт на себя проверки конфликтующих опций, несколько вхождений одной опции, и необязательные опции, например


                    • "a:b:(C|D)"-a и -b обязательные и требуют аргументы, а -C и -D конфликтуют между собой и не могут задавать одновременно
                    • "ab:[c:d:]"-a обязательный и без аргумента, -b обязательный с аргументом, -c и -d оба необязательные и требуют аргумента
                    • "[v*]"-v опциональный и может быть указан много раз, например, это используется для управление уровнем подробности лога

                    длинные опции не поддерживаются и генерация help'а тоже не поддерживается, т.е. это просто парсер аргументов и ничего больше

                      0

                      Включил Ваш пакет в расширенную таблицу в конце статьи.

                      0

                      Добавил в таблицу ещё пять пакетов. Обновлённая таблица в конце статьи, в разделе "Обновления".

                        +1

                        Добавил в конец статьи раздел "Немного аналитики", где можно прочитать о прошедших за три месяца изменениях в популярности рассматриваемых пакетов.

                          0

                          Обновил сводную таблицу и переделал её в формат Markdown. В топе по прежнему commander, yargs и minimist. Остальные пакеты также практически не изменили своё положение.

                            0

                            Сделал обновление сводной таблицы. Ощутимых изменений в рейтинге пакетов не наблюдается.

                              0

                              Использую commander но после прочтение статьи захотелось теперь попробовать yargs пусть и с 14 зависимостями.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое