Pull to refresh

Comments 34

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

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

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

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

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

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

Точно? Я вот хочу, чтобы ключу 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' ] }

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


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( '=' ) )
} )

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

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

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

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


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                         
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


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

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

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

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


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

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


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

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

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


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

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

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

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

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


$ 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, например).

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

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


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

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


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

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


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

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


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

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


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

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


-XX:+HeapDumpOnOutOfMemoryError -Djava.rmi.server.hostname=example.org
Вы что доказать-то пытаетесь?

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


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


%программа% %команда/действие% %аргументы команды/действия% %доп. опции%
# Например:
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
Очень многие консольные программы используют такой синтаксис

То есть отошли в своё время от стандарта, посчитав, что совмещать несколько команд в одной программе проще, чем делать по отдельной программе на каждое действие, как было принято в *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"

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


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

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


npm install=express save
То есть отошли в своё время от стандарта, посчитав, что совмещать несколько команд в одной программе проще, чем делать по отдельной программе на каждое действие, как было принято в *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 не угодил? Фатальный недостаток?

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

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


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

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


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

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


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

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


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

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

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

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

Пользуясь случаем пропиарю свою библиотеку 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']

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

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

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


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

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

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

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

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

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

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

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

Sign up to leave a comment.

Articles