pax_global_header00006660000000000000000000000064131277671630014527gustar00rootroot0000000000000052 comment=05bd8dd95703ce2284169928b52dfe28204b72e7 coa-2.0.0/000077500000000000000000000000001312776716300122705ustar00rootroot00000000000000coa-2.0.0/.editorconfig000066400000000000000000000002361312776716300147460ustar00rootroot00000000000000# EditorConfig is awesome: http://EditorConfig.org root = true [*] end_of_line = lf insert_final_newline = true [*.js] indent_style = space indent_size = 4 coa-2.0.0/.eslintignore000066400000000000000000000001071312776716300147710ustar00rootroot00000000000000.idea *.iml node_modules/ !node_modules/coa*.js coverage/ .nyc_output/ coa-2.0.0/.eslintrc000066400000000000000000000027021312776716300141150ustar00rootroot00000000000000{ "extends" : ["pedant/es6"], "env": { "es6": true, "node": true, "mocha": true }, "parserOptions" : { "ecmaVersion" : 6 }, "rules" : { "curly" : "off", "no-cond-assign": "off", "semi" : "error", "indent" : ["error", 4], "no-mixed-spaces-and-tabs" : "error", "max-len" : ["error", { "code" : 120 }], "eol-last" : "error", "key-spacing" : ["error", { "beforeColon" : true, "afterColon" : true, "mode" : "strict" }], "object-curly-spacing" : ["error", "always"], "keyword-spacing" : ["error", { "before" : true, "after" : true, "overrides" : { "if" : { "after" : false }, "for" : { "after" : false }, "while" : { "after" : false }, "switch" : { "after" : false }, "catch" : { "after" : false } } }], "array-bracket-spacing" : ["error", "never"], "arrow-parens" : ["error", "as-needed"], "func-call-spacing" : "error", "space-before-blocks" : ["error", "always"], "padded-blocks": ["error", "never"], "quotes" : ["error", "single", { "avoidEscape" : true }], "camelcase" : ["error", { "properties" : "never" }], "no-trailing-spaces" : "error", "comma-dangle" : ["error", "never"] } } coa-2.0.0/.gitignore000066400000000000000000000001071312776716300142560ustar00rootroot00000000000000.idea *.iml node_modules/ !node_modules/coa*.js coverage/ .nyc_output/ coa-2.0.0/.travis.yml000066400000000000000000000003171312776716300144020ustar00rootroot00000000000000sudo: false language: node_js matrix: include: - node_js: "4" - node_js: "6" env: COVERALLS=1 - node_js: "7" after_success: - if [ "x$COVERALLS" = "x1" ]; then npm run coveralls; fi coa-2.0.0/LICENSE000066400000000000000000000021211312776716300132710ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-present Sergey Berezhnoy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. coa-2.0.0/README.md000066400000000000000000000307051312776716300135540ustar00rootroot00000000000000# Command-Option-Argument Yet another parser for command line options. [![NPM Status][npm-img]][npm] [![Travis Status][test-img]][travis] [![AppVeyor Status][appveyor-img]][appveyor] [![Coverage Status][coverage-img]][coveralls] [![Dependency Status][dependency-img]][david] [npm]: https://www.npmjs.org/package/coa [npm-img]: https://img.shields.io/npm/v/coa.svg [travis]: https://travis-ci.org/veged/coa [test-img]: https://img.shields.io/travis/veged/coa.svg [appveyor]: https://ci.appveyor.com/project/zxqfox/coa [appveyor-img]: https://ci.appveyor.com/api/projects/status/github/veged/coa?svg=true [coveralls]: https://coveralls.io/r/veged/coa [coverage-img]: https://img.shields.io/coveralls/veged/coa.svg [david]: https://david-dm.org/veged/coa [dependency-img]: http://img.shields.io/david/veged/coa.svg ## What is it? COA is a parser for command line options that aim to get maximum profit from formalization your program API. Once you write definition in terms of commands, options and arguments you automaticaly get: * Command line help text * Program API for use COA-based programs as modules * Shell completion ### Other features * Rich types for options and arguments, such as arrays, boolean flags and required * Commands can be async throught using promising (powered by [Q](https://github.com/kriskowal/q)) * Easy submoduling some existing commands to new top-level one * Combined validation and complex parsing of values ### TODO * Localization * Shell-mode * Configs * Aliases * Defaults ## Examples ````javascript require('coa').Cmd() // main (top level) command declaration .name(process.argv[1]) // set top level command name from program name .title('My awesome command line util') // title for use in text messages .helpful() // make command "helpful", i.e. options -h --help with usage message .opt() // add some option .name('version') // name for use in API .title('Version') // title for use in text messages .short('v') // short key: -v .long('version') // long key: --version .flag() // for options without value .act(function(opts) { // add action for option // return message as result of action return JSON.parse(require('fs').readFileSync(__dirname + '/package.json')) .version; }) .end() // end option chain and return to main command .cmd().name('subcommand').apply(require('./subcommand').COA).end() // load subcommand from module .cmd() // inplace subcommand declaration .name('othercommand').title('Awesome other subcommand').helpful() .opt() .name('input').title('input file, required') .short('i').long('input') .val(function(v) { // validator function, also for translate simple values return require('fs').createReadStream(v) }) .req() // make option required .end() // end option chain and return to command .end() // end subcommand chain and return to parent command .run(process.argv.slice(2)); // parse and run on process.argv ```` ````javascript // subcommand.js exports.COA = function() { this .title('Awesome subcommand').helpful() .opt() .name('output').title('output file') .short('o').long('output') .output() // use default preset for "output" option declaration .end() }; ```` ## API reference ### Cmd Command is a top level entity. Commands may have options and arguments. #### Cmd.api Returns object containing all its subcommands as methods to use from other programs.
**@returns** *{Object}* #### Cmd.name Set a canonical command identifier to be used anywhere in the API.
**@param** *String* `_name` command name
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.title Set a long description for command to be used anywhere in text messages.
**@param** *String* `_title` command title
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.cmd Create new or add existing subcommand for current command.
**@param** *COA.Cmd* `[cmd]` existing command instance
**@returns** *COA.Cmd* new or added subcommand instance #### Cmd.opt Create option for current command.
**@returns** *COA.Opt* `new` option instance #### Cmd.arg Create argument for current command.
**@returns** *COA.Opt* `new` argument instance #### Cmd.act Add (or set) action for current command.
**@param** *Function* `act` action function, invoked in the context of command instance and has the parameters:
- *Object* `opts` parsed options
- *Array* `args` parsed arguments
- *Object* `res` actions result accumulator
It can return rejected promise by Cmd.reject (in case of error) or any other value treated as result.
**@param** *{Boolean}* [force=false] flag for set action instead add to existings
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.apply Apply function with arguments in context of command instance.
**@param** *Function* `fn`
**@param** *Array* `args`
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.comp Set custom additional completion for current command.
**@param** *Function* `fn` completion generation function, invoked in the context of command instance. Accepts parameters:
- *Object* `opts` completion options
It can return promise or any other value treated as result.
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.helpful Make command "helpful", i.e. add -h --help flags for print usage.
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.completable Adds shell completion to command, adds "completion" subcommand, that makes all the magic.
Must be called only on root command.
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.usage Build full usage text for current command instance.
**@returns** *String* `usage` text #### Cmd.run Parse arguments from simple format like NodeJS process.argv and run ahead current program, i.e. call process.exit when all actions done.
**@param** *Array* `argv`
**@returns** *COA.Cmd* `this` instance (for chainability) #### Cmd.invoke Invoke specified (or current) command using provided options and arguments.
**@param** *String|Array* `cmds` subcommand to invoke (optional)
**@param** *Object* `opts` command options (optional)
**@param** *Object* `args` command arguments (optional)
**@returns** *Q.Promise* #### Cmd.reject Return reject of actions results promise.
Use in .act() for return with error.
**@param** *Object* `reason` reject reason
You can customize toString() method and exitCode property of reason object.
**@returns** *Q.promise* rejected promise #### Cmd.end Finish chain for current subcommand and return parent command instance.
**@returns** *COA.Cmd* `parent` command ### Opt Option is a named entity. Options may have short and long keys for use from command line.
**@namespace**
**@class** Presents option #### Opt.name Set a canonical option identifier to be used anywhere in the API.
**@param** *String* `_name` option name
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.title Set a long description for option to be used anywhere in text messages.
**@param** *String* `_title` option title
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.short Set a short key for option to be used with one hyphen from command line.
**@param** *String* `_short`
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.long Set a short key for option to be used with double hyphens from command line.
**@param** *String* `_long`
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.flag Make an option boolean, i.e. option without value.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.arr Makes an option accepts multiple values.
Otherwise, the value will be used by the latter passed.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.req Makes an option req.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.only Makes an option to act as a command, i.e. program will exit just after option action.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.val Set a validation (or value) function for argument.
Value from command line passes through before becoming available from API.
Using for validation and convertion simple types to any values.
**@param** *Function* `_val` validating function, invoked in the context of option instance and has one parameter with value from command line
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.def Set a default value for option. Default value passed through validation function as ordinary value.
**@param** *Object* `_def`
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.input Make option value inputting stream. It's add useful validation and shortcut for STDIN. **@returns** *{COA.Opt}* `this` instance (for chainability) #### Opt.output Make option value outputing stream.
It's add useful validation and shortcut for STDOUT.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.act Add action for current option command. This action is performed if the current option is present in parsed options (with any value).
**@param** *Function* `act` action function, invoked in the context of command instance and has the parameters:
- *Object* `opts` parsed options
- *Array* `args` parsed arguments
- *Object* `res` actions result accumulator
It can return rejected promise by Cmd.reject (in case of error) or any other value treated as result.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.comp Set custom additional completion for current option.
**@param** *Function* `fn` completion generation function, invoked in the context of command instance. Accepts parameters:
- *Object* `opts` completion options
It can return promise or any other value treated as result.
**@returns** *COA.Opt* `this` instance (for chainability) #### Opt.end Finish chain for current option and return parent command instance.
**@returns** *COA.Cmd* `parent` command ### Arg Argument is a unnamed entity.
From command line arguments passed as list of unnamed values. #### Arg.name Set a canonical argument identifier to be used anywhere in text messages.
**@param** *String* `_name` argument name
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.title Set a long description for argument to be used anywhere in text messages.
**@param** *String* `_title` argument title
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.arr Makes an argument accepts multiple values.
Otherwise, the value will be used by the latter passed.
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.req Makes an argument req.
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.val Set a validation (or value) function for argument.
Value from command line passes through before becoming available from API.
Using for validation and convertion simple types to any values.
**@param** *Function* `_val` validating function, invoked in the context of argument instance and has one parameter with value from command line
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.def Set a default value for argument. Default value passed through validation function as ordinary value.
**@param** *Object* `_def`
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.output Make argument value outputing stream.
It's add useful validation and shortcut for STDOUT.
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.comp Set custom additional completion for current argument.
**@param** *Function* `fn` completion generation function, invoked in the context of command instance. Accepts parameters:
- *Object* `opts` completion options
It can return promise or any other value treated as result.
**@returns** *COA.Arg* `this` instance (for chainability) #### Arg.end Finish chain for current option and return parent command instance.
**@returns** *COA.Cmd* `parent` command coa-2.0.0/README.ru.md000066400000000000000000000541541312776716300142050ustar00rootroot00000000000000# Command-Option-Argument [![build status](https://secure.travis-ci.org/veged/coa.png)](http://travis-ci.org/veged/coa) ## Что это? COA — парсер параметров командной строки, позволяющий извлечь максимум пользы от формального API вашей программы. Как только вы опишете определение в терминах команд, параметров и аргументов, вы автоматически получите: * Справку для командной строки * API для использования программы как модуля в COA-совместимых программах * Автодополнение для командной строки ### Прочие возможности * Широкий выбор настроек для параметров и аргументов, включая множественные значения, логические значения и обязательность параметров * Возможность асинхронного исполнения команд, используя промисы (используется библиотека [Q](https://github.com/kriskowal/q)) * Простота использования существующих команд как подмодулей для новых команд * Комбинированная валидация и анализ сложных значений ## Примеры ````javascript require('coa').Cmd() // декларация команды верхнего уровня .name(process.argv[1]) // имя команды верхнего уровня, берем из имени программы .title('Жутко полезная утилита для командной строки') // название для использования в справке и сообщениях .helpful() // добавляем поддержку справки командной строки (-h, --help) .opt() // добавляем параметр .name('version') // имя параметра для использования в API .title('Version') // текст для вывода в сообщениях .short('v') // короткое имя параметра: -v .long('version') // длинное имя параметра: --version .flag() // параметр не требует ввода значения .act(function(opts) { // действия при вызове аргумента // результатом является вывод текстового сообщения return JSON.parse(require('fs').readFileSync(__dirname + '/package.json')) .version; }) .end() // завершаем определение параметра и возвращаемся к определению верхнего уровня .cmd().name('subcommand').apply(require('./subcommand').COA).end() // загрузка подкоманды из модуля .cmd() // добавляем еще одну подкоманду .name('othercommand').title('Еще одна полезная подпрограмма').helpful() .opt() .name('input').title('input file, required') .short('i').long('input') .val(function(v) { // функция-валидатор, также может использоваться для трансформации значений параметров return require('fs').createReadStream(v) }) .req() // параметр является обязательным .end() // завершаем определение параметра и возвращаемся к определению команды .end() // завершаем определение подкоманды и возвращаемся к определению команды верхнего уровня .run(process.argv.slice(2)); // разбираем process.argv и запускаем ```` ````javascript // subcommand.js exports.COA = function() { this .title('Полезная подпрограмма').helpful() .opt() .name('output').title('output file') .short('o').long('output') .output() // использовать стандартную настройку для параметра вывода .end() }; ```` ## API ### Cmd Команда — сущность верхнего уровня. У команды могут быть определены параметры и аргументы. #### Cmd.api Возвращает объект, который можно использовать в других программах. Подкоманды являются методами этого объекта.
**@returns** *{Object}* #### Cmd.name Определяет канонический идентификатор команды, используемый в вызовах API.
**@param** *String* `_name` имя команды
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.title Определяет название команды, используемый в текстовых сообщениях.
**@param** *String* `_title` название команды
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.cmd Создает новую подкоманду или добавляет ранее определенную подкоманду к текущей команде.
**@param** *COA.Cmd* `[cmd]` экземпляр ранее определенной подкоманды
**@returns** *COA.Cmd* экземпляр новой или ранее определенной подкоманды #### Cmd.opt Создает параметр для текущей команды.
**@returns** *COA.Opt* `new` экземпляр параметра #### Cmd.arg Создает аргумент для текущей команды.
**@returns** *COA.Opt* `new` экземпляр аргумента #### Cmd.act Добавляет (или создает) действие для текущей команды.
**@param** *Function* `act` функция, выполняемая в контексте экземпляра текущей команды и принимающая следующие параметры:
- *Object* `opts` параметры команды
- *Array* `args` аргументы команды
- *Object* `res` объект-аккумулятор результатов
Функция может вернуть проваленный промис из Cmd.reject (в случае ошибки) или любое другое значение, рассматриваемое как результат.
**@param** *{Boolean}* [force=false] флаг, назначающий немедленное исполнение вместо добавления к списку существующих действий
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.apply Исполняет функцию с переданными аргументами в контексте экземпляра текущей команды.
**@param** *Function* `fn`
**@param** *Array* `args`
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.comp Назначает кастомную функцию автодополнения для текущей команды.
**@param** *Function* `fn` функция-генератор автодополнения, исполняемая в контексте текущей команды. Принимает параметры:
- *Object* `opts` параметры
Может возвращать промис или любое другое значение, рассматриваемое как результат исполнения команды.
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.helpful Ставит флаг поддержки справки командной строки, т.е. вызов команды с параметрами -h --help выводит справку по работе с командой.
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.completable Добавляет поддержку автодополнения командной строки. Добавляется подкоманда "completion", которая выполняет все необходимые действия.
Может быть добавлен только для главной команды.
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.usage Возвращает текст справки по использованию команды для текущего экземпляра.
**@returns** *String* `usage` Текст справки по использованию #### Cmd.run Разбирает аргументы из значения, возвращаемого NodeJS process.argv, и запускает текущую программу, т.е. вызывает process.exit после завершения всех действий.
**@param** *Array* `argv`
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов) #### Cmd.invoke Исполняет переданную (или текущую) команду с указанными параметрами и аргументами.
**@param** *String|Array* `cmds` подкоманда для исполнения (необязательно)
**@param** *Object* `opts` параметры, передаваемые команде (необязательно)
**@param** *Object* `args` аргументы, передаваемые команде (необязательно)
**@returns** *Q.Promise* #### Cmd.reject Проваливает промисы, возращенные в действиях.
Используется в .act() для возврата с ошибкой.
**@param** *Object* `reason` причина провала
Вы можете определить метод toString() и свойство toString() объекта причины провала.
**@returns** *Q.promise* проваленный промис #### Cmd.end Завершает цепочку методов текущей подкоманды и возвращает экземпляр родительской команды.
**@returns** *COA.Cmd* `parent` родительская команда ### Opt Параметр — именованная сущность. У параметра может быть определено короткое или длинное имя для использования из командной строки.
**@namespace**
**@class** Переданный параметр #### Opt.name Определяет канонический идентификатор параметра, используемый в вызовах API.
**@param** *String* `_name` имя параметра
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.title Определяет описание для параметра, используемое в текстовых сообщениях.
**@param** *String* `_title` название параметра
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.short Назначает ключ для короткого имени параметра, передаваемого из командной строки с одинарным дефисом (например, `-v`).
**@param** *String* `_short`
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.long Назначает ключ для длинного имени параметра, передаваемого из командной строки с двойным дефисом (например, `--version`).
**@param** *String* `_long`
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.flag Помечает параметр как логический, т.е. параметр не имеющий значения.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.arr Помечает параметр как принимающий множественные значения.
Иначе будет использовано последнее переданное значение параметра.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.req Помечает параметр как обязательный.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.only Интерпретирует параметр как команду, т.е. программа будет завершена сразу после выполнения параметра.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.val Назначает функцию валидации (или трансформации значения) для значения параметра.
Значение, полученное из командной строки, передается в функцию-валидатор прежде чем оно станет доступно из API.
Используется для валидации и трансформации введенных данных.
**@param** *Function* `_val` функция валидации, исполняемая в контексте экземпляра параметра и принимающая в качестве единственного параметра значение, полученное из командной строки
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.def Назначает значение параметра по умолчанию. Это значение также передается в функцию валидации как обычное значение.
**@param** *Object* `_def`
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.input Помечает параметр как принимающий ввод пользователя.
Позволяет использовать валидацию для STDIN.
**@returns** *{COA.Opt}* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.output Помечает параметр как вывод.
Позволяет использовать валидацию для STDOUT.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.act Добавляет (или создает) действие для текущего параметра команды. Это действие будет выполнено, если текущий параметр есть в списке полученных параметров (с любым значением).
**@param** *Function* `act` функция, выполняемая в контексте экземпляра текущей команды и принимающая следующие параметры:
- *Object* `opts` параметры команды
- *Array* `args` аргументы команды
- *Object* `res` объект-аккумулятор результатов
Функция может вернуть проваленный промис из Cmd.reject (в случае ошибки) или любое другое значение, рассматриваемое как результат.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.comp Назначает кастомную функцию автодополнения для текущей команды.
**@param** *Function* `fn` функция-генератор автодоплнения, исполняемая в контексте экземпляра команды. Принимает параметры:
- *Object* `opts` параметры автодополнения
Может возвращать промис или любое другое значение, рассматриваемое как результат исполнения команды.
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов) #### Opt.end Завершает цепочку методов текущего параметра и возвращает экземпляр родительской команды.
**@returns** *COA.Cmd* `parent` родительская команда ### Arg Аргумент — неименованная сущность.
Аргументы передаются из командной строки как список неименованных значений. #### Arg.name Определяет канонический идентификатор аргумента, используемый в вызовах API.
**@param** *String* `_name` имя аргумента
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.title Определяет описание для аргумента, используемое в текстовых сообщениях.
**@param** *String* `_title` описание аргумента
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.arr Помечает аргумент как принимающий множественные значения.
Иначе будет использовано последнее переданное значение аргумента.
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.req Помечает аргумент как обязательный.
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.val Назначает функцию валидации (или трансформации значения) для аргумента.
Значение, полученное из командной строки, передается в функцию-валидатор прежде чем оно станет доступно из API.
Используется для валидации и трансформации введенных данных.
**@param** *Function* `_val` функция валидации, исполняемая в контексте экземпляра аргумента и принимающая в качестве единственного параметра значение, полученное из командной строки
**@returns** *COA.Opt* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.def Назначает дефолтное значение для аргумента. Дефолтное значение передается в функцию валидации как обычное значение.
**@param** *Object* `_def`
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.output Помечает параметр как вывод.
Позволяет назначить валидацию для STDOUT.
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.comp Назначает кастомную функцию автодополнения для текущего аргумента.
**@param** *Function* `fn` функция-генератор автодоплнения, исполняемая в контексте текущей команды. Принимает параметры:
- *Object* `opts` параметры Может возвращать промис или любое другое значение, рассматриваемое как результат исполнения команды.
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов) #### Arg.end Завершает цепочку методов текущего аргумента и возвращает экземпляр родительской команды.
**@returns** *COA.Cmd* `parent` родительская команда coa-2.0.0/appveyor.yml000066400000000000000000000003161312776716300146600ustar00rootroot00000000000000version: "{build}" environment: nodejs_version: "6" install: - ps: Install-Product node $env:nodejs_version - node --version - npm --version - npm install test_script: - npm test build: off coa-2.0.0/index.js000066400000000000000000000000431312776716300137320ustar00rootroot00000000000000module.exports = require('./lib'); coa-2.0.0/lib/000077500000000000000000000000001312776716300130365ustar00rootroot00000000000000coa-2.0.0/lib/arg.js000066400000000000000000000022221312776716300141430ustar00rootroot00000000000000'use strict'; const CoaParam = require('./coaparam'), Color = require('./color'); /** * Argument * * Unnamed entity. From command line arguments passed as list of unnamed values. * * @class Arg * @extends CoaParam */ module.exports = class Arg extends CoaParam { /** * @constructs * @param {COA.Cmd} cmd - parent command */ constructor(cmd) { super(cmd); this._cmd._args.push(this); } _saveVal(args, val) { this._val && (val = this._val(val)); const name = this._name; this._arr ? (args[name] || (args[name] = [])).push(val) : (args[name] = val); return val; } _parse(arg, args) { return this._saveVal(args, arg); } _checkParsed(opts, args) { return !args.hasOwnProperty(this._name); } _usage() { const res = []; res.push(Color('lpurple', this._name.toUpperCase()), ' : ', this._title); this._req && res.push(' ', Color('lred', '(required)')); return res.join(''); } _requiredText() { return `Missing required argument:\n ${this._usage()}`; } }; coa-2.0.0/lib/cmd.js000066400000000000000000000321751312776716300141470ustar00rootroot00000000000000'use strict'; const UTIL = require('util'), PATH = require('path'), EOL = require('os').EOL, Q = require('q'), CoaObject = require('./coaobject'), Color = require('./color'), Opt = require('./opt'), Arg = require('./arg'), completion = require('./completion'); /** * Command * * Top level entity. Commands may have options and arguments. * * @namespace * @class Cmd * @extends CoaObject */ class Cmd extends CoaObject { /** * @constructs * @param {COA.Cmd} [cmd] parent command */ constructor(cmd) { super(cmd); this._parent(cmd); this._cmds = []; this._cmdsByName = {}; this._opts = []; this._optsByKey = {}; this._args = []; this._api = null; this._ext = false; } static create(cmd) { return new Cmd(cmd); } /** * Returns object containing all its subcommands as methods * to use from other programs. * * @returns {Object} */ get api() { // Need _this here because of passed arguments into _api const _this = this; this._api || (this._api = function () { return _this.invoke.apply(_this, arguments); }); const cmds = this._cmdsByName; Object.keys(cmds).forEach(cmd => { this._api[cmd] = cmds[cmd].api; }); return this._api; } _parent(cmd) { this._cmd = cmd || this; this.isRootCmd || cmd._cmds.push(this) && this._name && (this._cmd._cmdsByName[this._name] = this); return this; } get isRootCmd() { return this._cmd === this; } /** * Set a canonical command identifier to be used anywhere in the API. * * @param {String} name - command name * @returns {COA.Cmd} - this instance (for chainability) */ name(name) { super.name(name); this.isRootCmd || (this._cmd._cmdsByName[name] = this); return this; } /** * Create new or add existing subcommand for current command. * * @param {COA.Cmd} [cmd] existing command instance * @returns {COA.Cmd} new subcommand instance */ cmd(cmd) { return cmd? cmd._parent(this) : new Cmd(this); } /** * Create option for current command. * * @returns {COA.Opt} new option instance */ opt() { return new Opt(this); } /** * Create argument for current command. * * @returns {COA.Opt} new argument instance */ arg() { return new Arg(this); } /** * Add (or set) action for current command. * * @param {Function} act - action function, * invoked in the context of command instance * and has the parameters: * - {Object} opts - parsed options * - {String[]} args - parsed arguments * - {Object} res - actions result accumulator * It can return rejected promise by Cmd.reject (in case of error) * or any other value treated as result. * @param {Boolean} [force=false] flag for set action instead add to existings * @returns {COA.Cmd} - this instance (for chainability) */ act(act, force) { if(!act) return this; (!this._act || force) && (this._act = []); this._act.push(act); return this; } /** * Make command "helpful", i.e. add -h --help flags for print usage. * * @returns {COA.Cmd} - this instance (for chainability) */ helpful() { return this.opt() .name('help') .title('Help') .short('h') .long('help') .flag() .only() .act(function() { return this.usage(); }) .end(); } /** * Adds shell completion to command, adds "completion" subcommand, * that makes all the magic. * Must be called only on root command. * * @returns {COA.Cmd} - this instance (for chainability) */ completable() { return this.cmd() .name('completion') .apply(completion) .end(); } /** * Allow command to be extendable by external node.js modules. * * @param {String} [pattern] Pattern of node.js module to find subcommands at. * @returns {COA.Cmd} - this instance (for chainability) */ extendable(pattern) { this._ext = pattern || true; return this; } _exit(msg, code) { return process.once('exit', function() { msg && console[code === 0 ? 'log' : 'error'](msg); process.exit(code || 0); }); } /** * Build full usage text for current command instance. * * @returns {String} usage text */ usage() { const res = []; this._title && res.push(this._fullTitle()); res.push('', 'Usage:'); this._cmds.length && res.push([ '', '', Color('lred', this._fullName()), Color('lblue', 'COMMAND'), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]') ].join(' ')); (this._opts.length + this._args.length) && res.push([ '', '', Color('lred', this._fullName()), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]') ].join(' ')); res.push( this._usages(this._cmds, 'Commands'), this._usages(this._opts, 'Options'), this._usages(this._args, 'Arguments') ); return res.join(EOL); } _usage() { return Color('lblue', this._name) + ' : ' + this._title; } _usages(os, title) { if(!os.length) return; return ['', title + ':'] .concat(os.map(o => ` ${o._usage()}`)) .join(EOL); } _fullTitle() { return `${this.isRootCmd? '' : this._cmd._fullTitle() + EOL}${this._title}`; } _fullName() { return `${this.isRootCmd? '' : this._cmd._fullName() + ' '}${PATH.basename(this._name)}`; } _ejectOpt(opts, opt) { const pos = opts.indexOf(opt); if(pos === -1) return; return opts[pos]._arr? opts[pos] : opts.splice(pos, 1)[0]; } _checkRequired(opts, args) { if(this._opts.some(opt => opt._only && opts.hasOwnProperty(opt._name))) return; const all = this._opts.concat(this._args); let i; while(i = all.shift()) if(i._req && i._checkParsed(opts, args)) return this.reject(i._requiredText()); } _parseCmd(argv, unparsed) { unparsed || (unparsed = []); let i, optSeen = false; while(i = argv.shift()) { i.indexOf('-') || (optSeen = true); if(optSeen || !/^\w[\w-_]*$/.test(i)) { unparsed.push(i); continue; } let pkg, cmd = this._cmdsByName[i]; if(!cmd && this._ext) { if(this._ext === true) { pkg = i; let c = this; while(true) { // eslint-disable-line pkg = c._name + '-' + pkg; if(c.isRootCmd) break; c = c._cmd; } } else if(typeof this._ext === 'string') pkg = ~this._ext.indexOf('%s')? UTIL.format(this._ext, i) : this._ext + i; let cmdDesc; try { cmdDesc = require(pkg); } catch(e) { // Dummy } if(cmdDesc) { if(typeof cmdDesc === 'function') { this.cmd().name(i).apply(cmdDesc).end(); } else if(typeof cmdDesc === 'object') { this.cmd(cmdDesc); cmdDesc.name(i); } else throw new Error('Error: Unsupported command declaration type, ' + 'should be a function or COA.Cmd() object'); cmd = this._cmdsByName[i]; } } if(cmd) return cmd._parseCmd(argv, unparsed); unparsed.push(i); } return { cmd : this, argv : unparsed }; } _parseOptsAndArgs(argv) { const opts = {}, args = {}, nonParsedOpts = this._opts.concat(), nonParsedArgs = this._args.concat(); let res, i; while(i = argv.shift()) { if(i !== '--' && i[0] === '-') { const m = i.match(/^(--\w[\w-_]*)=(.*)$/); if(m) { i = m[1]; this._optsByKey[i]._flag || argv.unshift(m[2]); } const opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i]); if(!opt) return this.reject(`Unknown option: ${i}`); if(Q.isRejected(res = opt._parse(argv, opts))) return res; continue; } i === '--' && (i = argv.splice(0)); Array.isArray(i) || (i = [i]); let a; while(a = i.shift()) { let arg = nonParsedArgs.shift(); if(!arg) return this.reject(`Unknown argument: ${a}`); arg._arr && nonParsedArgs.unshift(arg); if(Q.isRejected(res = arg._parse(a, args))) return res; } } return { opts : this._setDefaults(opts, nonParsedOpts), args : this._setDefaults(args, nonParsedArgs) }; } _setDefaults(params, desc) { for(const item of desc) item._def && !params.hasOwnProperty(item._name) && item._saveVal(params, item._def); return params; } _processParams(params, desc) { const notExists = []; for(const item of desc) { const n = item._name; if(!params.hasOwnProperty(n)) { notExists.push(item); continue; } const vals = Array.isArray(params[n])? params[n] : [params[n]]; delete params[n]; let res; for(const v of vals) if(Q.isRejected(res = item._saveVal(params, v))) return res; } return this._setDefaults(params, notExists); } _parseArr(argv) { return Q.when(this._parseCmd(argv), p => Q.when(p.cmd._parseOptsAndArgs(p.argv), r => ({ cmd : p.cmd, opts : r.opts, args : r.args }))); } _do(inputPromise) { return Q.when(inputPromise, input => { return [this._checkRequired] .concat(input.cmd._act || []) .reduce((res, act) => Q.when(res, prev => act.call(input.cmd, input.opts, input.args, prev)), undefined); }); } /** * Parse arguments from simple format like NodeJS process.argv * and run ahead current program, i.e. call process.exit when all actions done. * * @param {String[]} argv - arguments * @returns {COA.Cmd} - this instance (for chainability) */ run(argv) { argv || (argv = process.argv.slice(2)); const cb = code => res => res? this._exit(res.stack || res.toString(), (res.hasOwnProperty('exitCode')? res.exitCode : code) || 0) : this._exit(); Q.when(this.do(argv), cb(0), cb(1)).done(); return this; } /** * Invoke specified (or current) command using provided * options and arguments. * * @param {String|String[]} [cmds] - subcommand to invoke (optional) * @param {Object} [opts] - command options (optional) * @param {Object} [args] - command arguments (optional) * @returns {Q.Promise} */ invoke(cmds, opts, args) { cmds || (cmds = []); opts || (opts = {}); args || (args = {}); typeof cmds === 'string' && (cmds = cmds.split(' ')); if(arguments.length < 3 && !Array.isArray(cmds)) { args = opts; opts = cmds; cmds = []; } return Q.when(this._parseCmd(cmds), p => { if(p.argv.length) return this.reject(`Unknown command: ${cmds.join(' ')}`); return Q.all([ this._processParams(opts, this._opts), this._processParams(args, this._args) ]).spread((_opts, _args) => this._do({ cmd : p.cmd, opts : _opts, args : _args }) .fail(res => (res && res.exitCode === 0)? res.toString() : this.reject(res))); }); } } /** * Convenient function to run command from tests. * * @param {String[]} argv - arguments * @returns {Q.Promise} */ Cmd.prototype.do = function(argv) { return this._do(this._parseArr(argv || [])); }; module.exports = Cmd; coa-2.0.0/lib/coaobject.js000066400000000000000000000051351312776716300153310ustar00rootroot00000000000000'use strict'; const Q = require('q'); /** * COA Object * * Base class for all COA-related objects * * --------|-----|-----|----- * | Cmd | Opt | Arg * --------|-----|-----|----- * name | ✓ | ✓ | ✓ * title | ✓ | ✓ | ✓ * comp | ✓ | ✓ | ✓ * reject | ✓ | ✓ | ✓ * end | ✓ | ✓ | ✓ * apply | ✓ | ✓ | ✓ * * @class CoaObject */ module.exports = class CoaObject { constructor(cmd) { this._cmd = cmd; this._name = null; this._title = null; this._comp = null; } /** * Set a canonical identifier to be used anywhere in the API. * * @param {String} name - command, option or argument name * @returns {COA.CoaObject} - this instance (for chainability) */ name(name) { this._name = name; return this; } /** * Set a long description to be used anywhere in text messages. * @param {String} title - human readable entity title * @returns {COA.CoaObject} - this instance (for chainability) */ title(title) { this._title = title; return this; } /** * Set custom additional completion for current object. * * @param {Function} comp - completion generation function, * invoked in the context of object instance. * Accepts parameters: * - {Object} opts - completion options * It can return promise or any other value threated as a result. * @returns {COA.CoaObject} - this instance (for chainability) */ comp(comp) { this._comp = comp; return this; } /** * Apply function with arguments in a context of object instance. * * @param {Function} fn - body * @param {Array.<*>} args... - arguments * @returns {COA.CoaObject} - this instance (for chainability) */ apply(fn) { arguments.length > 1? fn.apply(this, [].slice.call(arguments, 1)) : fn.call(this); return this; } /** * Return reject of actions results promise with error code. * Use in .act() for return with error. * @param {Object} reason - reject reason * You can customize toString() method and exitCode property * of reason object. * @returns {Q.promise} rejected promise */ reject(reason) { return Q.reject(reason); } /** * Finish chain for current subcommand and return parent command instance. * @returns {COA.Cmd} parent command */ end() { return this._cmd; } }; coa-2.0.0/lib/coaparam.js000066400000000000000000000061311312776716300151600ustar00rootroot00000000000000'use strict'; const fs = require('fs'); const CoaObject = require('./coaobject'); /** * COA Parameter * * Base class for options and arguments * * --------|-----|-----|----- * | Cmd | Opt | Arg * --------|-----|-----|----- * arr | | ✓ | ✓ * req | | ✓ | ✓ * val | | ✓ | ✓ * def | | ✓ | ✓ * input | | ✓ | ✓ * output | | ✓ | ✓ * * @class CoaParam * @extends CoaObject */ module.exports = class CoaParam extends CoaObject { constructor(cmd) { super(cmd); this._arr = false; this._req = false; this._val = undefined; this._def = undefined; } /** * Makes a param accepts multiple values. * Otherwise, the value will be used by the latter passed. * * @returns {COA.CoaParam} - this instance (for chainability) */ arr() { this._arr = true; return this; } /** * Makes a param required. * * @returns {COA.CoaParam} - this instance (for chainability) */ req() { this._req = true; return this; } /** * Set a validation (or value) function for param. * Value from command line passes through before becoming available from API. * Using for validation and convertion simple types to any values. * * @param {Function} val - validating function, * invoked in the context of option instance * and has one parameter with value from command line. * @returns {COA.CoaParam} - this instance (for chainability) */ val(val) { this._val = val; return this; } /** * Set a default value for param. * Default value passed through validation function as ordinary value. * * @param {*} def - default value of function generator * @returns {COA.CoaParam} - this instance (for chainability) */ def(def) { this._def = def; return this; } /** * Make option value inputting stream. * It's add useful validation and shortcut for STDIN. * * @returns {COA.CoaParam} - this instance (for chainability) */ input() { process.stdin.pause(); return this .def(process.stdin) .val(function(v) { if(typeof v !== 'string') return v; if(v === '-') return process.stdin; const s = fs.createReadStream(v, { encoding : 'utf8' }); s.pause(); return s; }); } /** * Make option value outputing stream. * It's add useful validation and shortcut for STDOUT. * * @returns {COA.CoaParam} - this instance (for chainability) */ output() { return this .def(process.stdout) .val(function(v) { if(typeof v !== 'string') return v; if(v === '-') return process.stdout; return fs.createWriteStream(v, { encoding : 'utf8' }); }); } }; coa-2.0.0/lib/color.js000066400000000000000000000006221312776716300145120ustar00rootroot00000000000000'use strict'; const colors = { black : '30', dgray : '1;30', red : '31', lred : '1;31', green : '32', lgreen : '1;32', brown : '33', yellow : '1;33', blue : '34', lblue : '1;34', purple : '35', lpurple : '1;35', cyan : '36', lcyan : '1;36', lgray : '37', white : '1;37' }; module.exports = (c, str) => `\x1B[${colors[c]}m${str}\x1B[m`; coa-2.0.0/lib/completion.js000066400000000000000000000136211312776716300155500ustar00rootroot00000000000000'use strict'; const constants = require('constants'); const fs = require('fs'); const path = require('path'); const Q = require('q'); const shell = require('./shell'); const escape = shell.escape; const unescape = shell.unescape; /** * Most of the code adopted from the npm package shell completion code. * See https://github.com/isaacs/npm/blob/master/lib/completion.js * * @returns {COA.CoaObject} */ module.exports = function completion() { return this .title('Shell completion') .helpful() .arg() .name('raw') .title('Completion words') .arr() .end() .act((opts, args) => { if(process.platform === 'win32') { const e = new Error('shell completion not supported on windows'); e.code = 'ENOTSUP'; e.errno = constants.ENOTSUP; return this.reject(e); } // if the COMP_* isn't in the env, then just dump the script if((process.env.COMP_CWORD == null) || (process.env.COMP_LINE == null) || (process.env.COMP_POINT == null)) { return dumpScript(this._cmd._name); } console.error('COMP_LINE: %s', process.env.COMP_LINE); console.error('COMP_CWORD: %s', process.env.COMP_CWORD); console.error('COMP_POINT: %s', process.env.COMP_POINT); console.error('args: %j', args.raw); // completion opts opts = getOpts(args.raw); // cmd const parsed = this._cmd._parseCmd(opts.partialWords); return Q.when(complete(parsed.cmd, parsed.opts), compls => { console.error('filtered: %j', compls); return console.log(compls.map(escape).join('\n')); }); }); }; function dumpScript(name) { const defer = Q.defer(); fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) { if(err) return defer.reject(err); d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^\#\!.*?\n/, ''); process.stdout.on('error', onError); process.stdout.write(d, () => defer.resolve()); }); return defer.promise; function onError(err) { // Darwin is a real dick sometimes. // // This is necessary because the "source" or "." program in // bash on OS X closes its file argument before reading // from it, meaning that you get exactly 1 write, which will // work most of the time, and will always raise an EPIPE. // // Really, one should not be tossing away EPIPE errors, or any // errors, so casually. But, without this, `. <(cmd completion)` // can never ever work on OS X. if(err.errno !== constants.EPIPE) return defer.reject(err); process.stdout.removeListener('error', onError); return defer.resolve(); } } function getOpts(argv) { // get the partial line and partial word, if the point isn't at the end // ie, tabbing at: cmd foo b|ar const line = process.env.COMP_LINE; const w = +process.env.COMP_CWORD; const point = +process.env.COMP_POINT; const words = argv.map(unescape); const word = words[w]; const partialLine = line.substr(0, point); const partialWords = words.slice(0, w); // figure out where in that last word the point is let partialWord = argv[w] || ''; let i = partialWord.length; while(partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) i--; partialWord = unescape(partialWord.substr(0, i)); partialWord && partialWords.push(partialWord); return { line, w, point, words, word, partialLine, partialWords, partialWord }; } function complete(cmd, opts) { let optWord, optPrefix, compls = []; // Complete on cmds if(opts.partialWord.indexOf('-')) compls = Object.keys(cmd._cmdsByName); // Complete on required opts without '-' in last partial word // (if required not already specified) // // Commented out because of uselessness: // -b, --block suggest results in '-' on cmd line; // next completion suggest all options, because of '-' //.concat Object.keys(cmd._optsByKey).filter (v) -> cmd._optsByKey[v]._req else { // complete on opt values: --opt=| case const m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/); if(m) { optWord = m[1]; optPrefix = optWord + '='; } else // complete on opts // don't complete on opts in case of --opt=val completion // TODO: don't complete on opts in case of unknown arg after commands // TODO: complete only on opts with arr() or not already used // TODO: complete only on full opts? compls = Object.keys(cmd._optsByKey); } // complete on opt values: next arg case opts.partialWords[opts.w - 1].indexOf('-') || (optWord = opts.partialWords[opts.w - 1]); // complete on opt values: completion let opt; optWord && (opt = cmd._optsByKey[optWord]) && !opt._flag && opt._comp && (compls = Q.join(compls, Q.when(opt._comp(opts), (c, o) => c.concat(o.map(v => (optPrefix || '') + v))))); // TODO: complete on args values (context aware, custom completion?) // custom completion on cmds cmd._comp && (compls = Q.join(compls, Q.when(cmd._comp(opts)), (c, o) => c.concat(o))); // TODO: context aware custom completion on cmds, opts and args // (can depend on already entered values, especially options) return Q.when(compls, complitions => { console.error('partialWord: %s', opts.partialWord); console.error('compls: %j', complitions); return compls.filter(c => c.indexOf(opts.partialWord) === 0); }); } coa-2.0.0/lib/completion.sh000066400000000000000000000024131312776716300155430ustar00rootroot00000000000000#!/usr/bin/env bash ###-begin-{{cmd}}-completion-### # # {{cmd}} command completion script # # Installation: {{cmd}} completion >> ~/.bashrc (or ~/.zshrc) # Or, maybe: {{cmd}} completion > /usr/local/etc/bash_completion.d/{{cmd}} # COMP_WORDBREAKS=${COMP_WORDBREAKS/=/} COMP_WORDBREAKS=${COMP_WORDBREAKS/@/} export COMP_WORDBREAKS if complete &>/dev/null; then _{{cmd}}_completion () { local si="$IFS" IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \ COMP_LINE="$COMP_LINE" \ COMP_POINT="$COMP_POINT" \ {{cmd}} completion -- "${COMP_WORDS[@]}" \ 2>/dev/null)) || return $? IFS="$si" } complete -F _{{cmd}}_completion {{cmd}} elif compctl &>/dev/null; then _{{cmd}}_completion () { local cword line point words si read -Ac words read -cn cword let cword-=1 read -l line read -ln point si="$IFS" IFS=$'\n' reply=($(COMP_CWORD="$cword" \ COMP_LINE="$line" \ COMP_POINT="$point" \ {{cmd}} completion -- "${words[@]}" \ 2>/dev/null)) || return $? IFS="$si" } compctl -K _{{cmd}}_completion {{cmd}} fi ###-end-{{cmd}}-completion-### coa-2.0.0/lib/index.js000066400000000000000000000004131312776716300145010ustar00rootroot00000000000000const Cmd = require('./cmd'), Opt = require('./opt'), Arg = require('./arg'), shell = require('./shell'); module.exports = { Cmd : Cmd.create, Opt : Opt.create, Arg : Arg.create, classes : { Cmd, Opt, Arg }, shell, require }; coa-2.0.0/lib/opt.js000066400000000000000000000075421312776716300142060ustar00rootroot00000000000000'use strict'; const Q = require('q'), CoaParam = require('./coaparam'), Color = require('./color'); /** * Option * * Named entity. Options may have short and long keys for use from command line. * * @namespace * @class Opt * @extends CoaParam */ module.exports = class Opt extends CoaParam { /** * @constructs * @param {COA.Cmd} cmd - parent command */ constructor(cmd) { super(cmd); this._short = null; this._long = null; this._flag = false; this._only = false; this._cmd._opts.push(this); } /** * Set a short key for option to be used with one hyphen from command line. * * @param {String} short - short name * @returns {COA.Opt} - this instance (for chainability) */ short(short) { this._short = short; this._cmd._optsByKey[`-${short}`] = this; return this; } /** * Set a short key for option to be used with double hyphens from command line. * * @param {String} long - long name * @returns {COA.Opt} - this instance (for chainability) */ long(long) { this._long = long; this._cmd._optsByKey[`--${long}`] = this; return this; } /** * Make an option boolean, i.e. option without value. * * @returns {COA.Opt} - this instance (for chainability) */ flag() { this._flag = true; return this; } /** * Makes an option to act as a command, * i.e. program will exit just after option action. * * @returns {COA.Opt} - this instance (for chainability) */ only() { this._only = true; return this; } /** * Add action for current option command. * This action is performed if the current option * is present in parsed options (with any value). * * @param {Function} act - action function, * invoked in the context of command instance * and has the parameters: * - {Object} opts - parsed options * - {Array} args - parsed arguments * - {Object} res - actions result accumulator * It can return rejected promise by Cmd.reject (in case of error) * or any other value treated as result. * @returns {COA.Opt} - this instance (for chainability) */ act(act) { // Need function here for arguments const opt = this; this._cmd.act(function(opts) { if(!opts.hasOwnProperty(opt._name)) return; const res = act.apply(this, arguments); if(!opt._only) return res; return Q.when(res, out => this.reject({ toString : () => out.toString(), exitCode : 0 })); }); return this; } _saveVal(opts, val) { this._val && (val = this._val(val)); const name = this._name; this._arr ? (opts[name] || (opts[name] = [])).push(val) : (opts[name] = val); return val; } _parse(argv, opts) { return this._saveVal(opts, this._flag ? true : argv.shift()); } _checkParsed(opts) { return !opts.hasOwnProperty(this._name); } _usage() { const res = [], nameStr = this._name.toUpperCase(); if(this._short) { res.push('-', Color('lgreen', this._short)); this._flag || res.push(' ' + nameStr); res.push(', '); } if(this._long) { res.push('--', Color('green', this._long)); this._flag || res.push('=' + nameStr); } res.push(' : ', this._title); this._req && res.push(' ', Color('lred', '(required)')); return res.join(''); } _requiredText() { return `Missing required option:\n ${this._usage()}`; } }; coa-2.0.0/lib/shell.js000066400000000000000000000005061312776716300145040ustar00rootroot00000000000000module.exports = { escape, unescape }; function unescape(w) { w = w.charAt(0) === '"' ? w.replace(/^"|([^\\])"$/g, '$1') : w.replace(/\\ /g, ' '); return w.replace(/\\("|'|\$|`|\\)/g, '$1'); } function escape(w) { w = w.replace(/(["'$`\\])/g,'\\$1'); return w.match(/\s+/) ? `"${w}"` : w; } coa-2.0.0/node_modules/000077500000000000000000000000001312776716300147455ustar00rootroot00000000000000coa-2.0.0/node_modules/coa-test-obj.js000066400000000000000000000005061312776716300175730ustar00rootroot00000000000000var COA = require('..'); module.exports = COA.Cmd() .opt() .name('opt') .long('opt') .end() .arg() .name('arg1') .end() .arg() .name('arg2') .arr() .end() .act(function(opts, args) { return { opts: opts, args: args }; }) .end(); coa-2.0.0/node_modules/coa-test.js000066400000000000000000000005611312776716300170240ustar00rootroot00000000000000module.exports = function() { return this .opt() .name('opt') .long('opt') .end() .arg() .name('arg1') .end() .arg() .name('arg2') .arr() .end() .act(function(opts, args) { return { opts: opts, args: args }; }); }; coa-2.0.0/package.json000066400000000000000000000024201312776716300145540ustar00rootroot00000000000000{ "name": "coa", "description": "Command-Option-Argument: Yet another parser for command line options.", "version": "2.0.0", "homepage": "http://github.com/veged/coa", "author": "Sergey Berezhnoy (http://github.com/veged)", "maintainers": [ "Sergey Berezhnoy (http://github.com/veged)", "Sergey Belov (http://github.com/arikon)" ], "contributors": [ "Sergey Belov (http://github.com/arikon)" ], "files": [ "lib/", "index.js", "README.ru.md" ], "repository": { "type": "git", "url": "git://github.com/veged/coa.git" }, "directories": { "lib": "./lib" }, "dependencies": { "q": "^1.1.2" }, "devDependencies": { "chai": "~1.7.2", "coveralls": "^2.11.16", "eslint": "^3.15.0", "eslint-config-pedant": "^0.8.0", "mocha": "~1.21.4", "nyc": "^10.1.2" }, "scripts": { "clean": "rm -r .nyc_output coverage", "coverage": "nyc --reporter=text --reporter=html mocha; echo; echo 'Open coverage/index.html file in your browser'", "coveralls": "nyc report --reporter=text-lcov | coveralls", "lint": "eslint .", "pretest": "npm run lint", "test": "nyc mocha" }, "engines": { "node": ">= 4.0" }, "license": "MIT" } coa-2.0.0/test/000077500000000000000000000000001312776716300132475ustar00rootroot00000000000000coa-2.0.0/test/coa.js000066400000000000000000000343741312776716300143620ustar00rootroot00000000000000/* eslint-disable padded-blocks */ var assert = require('chai').assert, COA = require('..'); /** * Mocha BDD interface. */ /** @name describe @function */ /** @name it @function */ /** @name before @function */ /** @name after @function */ /** @name beforeEach @function */ /** @name afterEach @function */ describe('Opt', function() { describe('Unknown option', function() { var cmd = COA.Cmd(); it('should fail', function() { return cmd.do(['-a']) .then(assert.fail, emptyFn); }); }); describe('Short options', function() { var cmd = COA.Cmd() .opt() .name('a') .short('a') .end() .opt() .name('b') .short('b') .end() .act(function(opts) { return opts; }); it('should return passed values', function() { return cmd.do(['-a', 'a', '-b', 'b']) .then(function(res) { assert.deepEqual(res, { a : 'a', b : 'b' }); }); }); }); describe('Long options', function() { var cmd = COA.Cmd() .opt() .name('long1') .long('long1') .end() .opt() .name('long2') .long('long2') .end() .act(function(opts) { return opts; }); it('should return passed values', function() { return cmd.do(['--long1', 'long value', '--long2=another long value']) .then(function(res) { assert.deepEqual(res, { long1 : 'long value', long2 : 'another long value' }); }); }); }); describe('Array option', function() { var cmd = COA.Cmd() .opt() .name('a') .short('a') .arr() .end() .act(function(opts) { return opts; }); it('should return array of passed values', function() { return cmd.do(['-a', '1', '-a', '2']) .then(function(res) { assert.deepEqual(res, { a : ['1', '2'] }); }); }); }); describe('Required option', function() { var cmd = COA.Cmd() .opt() .name('a') .short('a') .req() .end() .act(function(opts) { return opts; }); it('should fail if not specified', function() { return cmd.do() .then(assert.fail, emptyFn); }); it('should return passed value if specified', function() { return cmd.do(['-a', 'test']) .then(function(opts) { assert.equal(opts.a, 'test'); }); }); }); describe('Option with default value', function() { var cmd = COA.Cmd() .opt() .name('a') .short('a') .def('aaa') .end() .act(function(opts) { return opts; }); it('should return default value if not specified', function() { return cmd.do() .then(function(opts) { assert.equal(opts.a, 'aaa'); }); }); it('should return passed value if specified', function() { return cmd.do(['-a', 'test']) .then(function(opts) { assert.equal(opts.a, 'test'); }); }); }); describe('Validated / transformed option', function() { var cmd = COA.Cmd() .opt() .name('a') .short('a') .val(function(v) { if(v === 'invalid') return this.reject('fail'); return { value : v }; }) .end() .act(function(opts) { return opts; }); it('should fail if custom checks suppose to do so', function() { return cmd.do(['-a', 'invalid']) .then(assert.fail, emptyFn); }); it('should return transformed value', function() { return cmd.do(['-a', 'test']) .then(function(opts) { assert.deepEqual(opts.a, { value : 'test' }); }); }); }); describe('Act in option', function() { var cmd = COA.Cmd() .opt() .name('a') .short('a') .act(opts => ({ b : 'b' + opts.a })) .act((opts, args, res) => ({ z : 'z' + opts.a + res.b })) .end(); it('should return transformed value by act', () => cmd.do(['-a', 'est']).then(opts => assert.deepEqual(opts, { z : 'zestbest' }))); }); describe('Only option (--version case)', function() { var ver = require('../package.json').version, cmd = COA.Cmd() .opt() .name('version') .long('version') .flag() .only() .act(function() { return ver; }) .end() .opt() .name('req') .short('r') .req() .end(); it('should process the only() option', function() { return cmd.do(['--version']) .then(assert.fail, function(res) { assert.equal(res, ver); }); }); }); it('input()'); it('output()'); }); describe('Arg', function() { describe('Unknown arg', function() { var cmd = COA.Cmd(); it('should fail', function() { return cmd.do(['test']) .then(assert.fail, emptyFn); }); }); describe('Unknown arg after known', function() { var cmd = COA.Cmd() .arg() .name('a') .end(); it('should fail', function() { return cmd.do(['test', 'unknown']) .then(assert.fail, emptyFn); }); }); describe('Array arg', function() { var cmd = COA.Cmd() .arg() .name('a') .arr() .end() .act(function(opts, args) { return args; }); it('should return array of passed values', function() { return cmd.do(['value 1', 'value 2']) .then(function(args) { assert.deepEqual(args, { a : ['value 1', 'value 2'] }); }); }); }); describe('Required arg', function() { var cmd = COA.Cmd() .arg() .name('a') .req() .end() .act(function(opts, args) { return args; }); it('should fail if not specified', function() { return cmd.do() .then(assert.fail, emptyFn); }); it('should return passed value if specified', function() { return cmd.do(['value']) .then(function(args) { assert.equal(args.a, 'value'); }); }); }); describe('Args after options', function() { var cmd = COA.Cmd() .opt() .name('opt') .long('opt') .end() .arg() .name('arg1') .end() .arg() .name('arg2') .arr() .end() .act(function(opts, args) { return { opts : opts, args : args }; }); it('should return passed values', function() { return cmd.do(['--opt', 'value', 'value', 'value 1', 'value 2']) .then(function(o) { assert.deepEqual(o, { opts : { opt : 'value' }, args : { arg1 : 'value', arg2 : ['value 1', 'value 2'] } }); }); }); }); describe('Raw args', function() { var cmd = COA.Cmd() .arg() .name('raw') .arr() .end() .act(function(opts, args) { return args; }); it('should return passed arg values', function() { return cmd.do(['--', 'raw', 'arg', 'values']) .then(function(args) { assert.deepEqual(args, { raw : ['raw', 'arg', 'values'] }); }); }); }); }); describe('Cmd', function() { describe('Action', function() { it('should declare few acts and pass result thru', function() { return COA.Cmd() .act(() => 12) .act((opts, args, res) => `${res} 34`) .do() .then(res => assert.equal(res, '12 34')); }); it('should not fail on empty act', function() { return COA.Cmd().act().do(); }); it('should drop actions on force', function() { return COA.Cmd() .act(() => { throw new Error('Should be rewritten'); }) .act(() => 42, true) .do().then(res => assert.equal(res, 42)); }); }); var doTest = function(o) { assert.deepEqual(o, { opts : { opt : 'value' }, args : { arg1 : 'value', arg2 : ['value 1', 'value 2'] } }); }, invokeOpts = { opt : 'value' }, invokeArgs = { arg1 : 'value', arg2 : ['value 1', 'value 2'] }; describe('Subcommand', function() { var cmd = COA.Cmd() .cmd() .name('command') .opt() .name('opt') .long('opt') .end() .arg() .name('arg1') .end() .arg() .name('arg2') .arr() .end() .act(function(opts, args) { return { opts : opts, args : args }; }) .end(); describe('when specified on command line', function() { it('should be invoked and accept passed opts and args', function() { return cmd.do(['command', '--opt', 'value', 'value', 'value 1', 'value 2']) .then(doTest); }); }); describe('when invoked using api', function() { it('should be invoked and accept passed opts and args', function() { return cmd.api.command(invokeOpts, invokeArgs) .then(doTest); }); }); describe('when invoked using invoke()', function() { it('should be invoked and accept passed opts and args', function() { return cmd.invoke('command', invokeOpts, invokeArgs) .then(doTest); }); }); describe('when unexisting command invoked using invoke()', function() { it('should fail', function() { return cmd.invoke('unexistent') .then(assert.fail, emptyFn); }); }); }); describe('External subcommand', function() { describe('default scheme: cmd.extendable()', function() { describe('when described as a function', function() { var cmd = COA.Cmd() .name('coa') .extendable(); it('should be invoked and accept passed opts and args', function() { return cmd.do(['test', '--opt', 'value', 'value', 'value 1', 'value 2']) .then(doTest); }); }); describe('when described as an COA.Cmd() object', function() { var cmd = COA.Cmd() .name('coa') .extendable(); it('should be invoked and accept passed opts and args', function() { return cmd.do(['test-obj', '--opt', 'value', 'value', 'value 1', 'value 2']) .then(doTest); }); }); describe('2nd level subcommand', function() { var cmd = COA.Cmd() .name('coa') .cmd() .name('test') .extendable() .end(); it('should be invoked and accept passed opts and args', function() { return cmd.do(['test', 'obj', '--opt', 'value', 'value', 'value 1', 'value 2']) .then(doTest); }); }); }); describe("common prefix: cmd.extendable('coa-')", function() { describe('when described as a function', function() { var cmd = COA.Cmd() .name('coa') .extendable('coa-'); it('should be invoked and accept passed opts and args', function() { return cmd.do(['test', '--opt', 'value', 'value', 'value 1', 'value 2']) .then(doTest); }); }); }); describe("format string: cmd.extendable('coa-%s')", function() { describe('when described as a function', function() { var cmd = COA.Cmd() .name('coa') .extendable('coa-%s'); it('should be invoked and accept passed opts and args', function() { return cmd.do(['test', '--opt', 'value', 'value', 'value 1', 'value 2']) .then(doTest); }); }); }); }); it('name()'); it('title()'); it('helpful()'); }); function emptyFn() { // empty function } coa-2.0.0/test/mocha.opts000066400000000000000000000000351312776716300152430ustar00rootroot00000000000000--reporter spec --timeout 20 coa-2.0.0/test/shell-test.js000066400000000000000000000030341312776716300156710ustar00rootroot00000000000000/* eslint-disable padded-blocks */ var assert = require('chai').assert, shell = require('..').shell; /** * Mocha BDD interface. */ /** @name describe @function */ /** @name it @function */ /** @name before @function */ /** @name after @function */ /** @name beforeEach @function */ /** @name afterEach @function */ describe('shell', function() { describe('escape()', function() { var escape = shell.escape; it('Should wrap values with spaces in double quotes', function() { assert.equal(escape('asd abc'), '"asd abc"'); }); it('Should escape double quote "', function() { assert.equal(escape('"asd'), '\\"asd'); }); it("Should escape single quote '", function() { assert.equal(escape("'asd"), "\\'asd"); }); it('Should escape backslash \\', function() { assert.equal(escape('\\asd'), '\\\\asd'); }); it('Should escape dollar $', function() { assert.equal(escape('$asd'), '\\$asd'); }); it('Should escape backtick `', function() { assert.equal(escape('`asd'), '\\`asd'); }); }); describe('unescape()', function() { var unescape = shell.unescape; it('Should strip double quotes at the both ends', function() { assert.equal(unescape('"asd"'), 'asd'); }); it('Should not strip escaped double quotes at the both ends', function() { assert.equal(unescape('\\"asd\\"'), '"asd"'); }); }); }); coa-2.0.0/tests/000077500000000000000000000000001312776716300134325ustar00rootroot00000000000000coa-2.0.0/tests/api-h.js000066400000000000000000000002771312776716300147740ustar00rootroot00000000000000require('..').Cmd() .name('bla') .title('Bla bla bla') .helpful() .invoke({ help : true }) .then(function(res) { console.log(res); }) .done(); // Q.done() coa-2.0.0/tests/h.js000066400000000000000000000002261312776716300142170ustar00rootroot00000000000000var argv = process.argv.slice(2); require('..').Cmd() .name('bla') .title('Bla bla bla') .helpful() .run(argv.length? argv : ['-h']);