optionator-0.8.2/ 000775 000000 000000 00000000000 12770561512 013705 5 ustar 00root root 000000 000000 optionator-0.8.2/.gitignore 000664 000000 000000 00000000040 12770561512 015667 0 ustar 00root root 000000 000000 *.swp
node_modules
coverage
*.t
optionator-0.8.2/CHANGELOG.md 000664 000000 000000 00000003353 12770561512 015522 0 ustar 00root root 000000 000000 # 0.8.2
- fix bug #18 - detect missing value when flag is last item
- update dependencies
# 0.8.1
- update `fast-levenshtein` dependency
# 0.8.0
- update `levn` dependency - supplying a float value to an option with type `Int` now throws an error, instead of silently converting to an `Int`
# 0.7.1
- fix bug with use of `defaults` and `concatRepeatedArrays` or `mergeRepeatedObjects`
# 0.7.0
- added `concatrepeatedarrays` option: `oneValuePerFlag`, only allows one array value per flag
- added `typeAliases` option
- added `parseArgv` which takes an array and parses with the first two items sliced off
- changed enum help style
- bug fixes (#12)
- use of `concatRepeatedArrays` and `mergeRepeatedObjects` at the top level is deprecated, use it as either a per-option option, or set them in the `defaults` object to set them for all objects
# 0.6.0
- added `defaults` lib-option flag, allowing one to set default properties for all options
- added `concatRepeatedArrays` and `mergeRepeatedObjects` as option level properties, allowing you to turn this feature on for specific options only
# 0.5.0
- `Boolean` flags with `default: 'true'`, and no short aliases, will by default show the `--no` version in help
# 0.4.0
- add `mergeRepeatedObjects` setting
# 0.3.0
- add `concatRepeatedArrays` setting
- add `overrideRequired` option setting
- use just Levenshtein string compare algo rather than Levenshtein Damerau to due dependency license issue
# 0.2.2
- bug fixes
# 0.2.1
- improved interpolation
- added changelog
# 0.2.0
- add dependency checks to options - added `dependsOn` as an option property
- add interpolation for `prepend` and `append` text with new `generateHelp` option, `interpolate`
# 0.1.1
- update dependencies
# 0.1.0
- initial release
optionator-0.8.2/LICENSE 000664 000000 000000 00000002036 12770561512 014713 0 ustar 00root root 000000 000000 Copyright (c) George Zahariev
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.
optionator-0.8.2/Makefile 000664 000000 000000 00000001406 12770561512 015346 0 ustar 00root root 000000 000000 default: all
SRC = $(shell find src -name "*.ls" -type f | sort)
LIB = $(SRC:src/%.ls=lib/%.js)
LS = node_modules/livescript
LSC = node_modules/.bin/lsc
MOCHA = node_modules/.bin/mocha
MOCHA2 = node_modules/.bin/_mocha
ISTANBUL = node_modules/.bin/istanbul
package.json: package.json.ls
$(LSC) --compile package.json.ls
lib:
mkdir lib/
lib/%.js: src/%.ls lib
$(LSC) --compile --output lib "$<"
.PHONY: build test coverage dev-install loc clean
all: build
build: $(LIB) package.json
test: build
$(MOCHA) --reporter dot --ui tdd --compilers ls:$(LS)
coverage: build
$(ISTANBUL) cover $(MOCHA2) -- --reporter dot --ui tdd --compilers ls:$(LS)
dev-install: package.json
npm install .
loc:
wc -l $(SRC)
clean:
rm -f package.json
rm -rf lib
rm -rf coverage
optionator-0.8.2/README.md 000664 000000 000000 00000035124 12770561512 015171 0 ustar 00root root 000000 000000 # Optionator
Optionator is a JavaScript option parsing and help generation library used by [eslint](http://eslint.org), [Grasp](http://graspjs.com), [LiveScript](http://livescript.net), [esmangle](https://github.com/estools/esmangle), [escodegen](https://github.com/estools/escodegen), and [many more](https://www.npmjs.com/browse/depended/optionator).
For an online demo, check out the [Grasp online demo](http://www.graspjs.com/#demo).
[About](#about) · [Usage](#usage) · [Settings Format](#settings-format) · [Argument Format](#argument-format)
## Why?
The problem with other option parsers, such as `yargs` or `minimist`, is they just accept all input, valid or not.
With Optionator, if you mistype an option, it will give you an error (with a suggestion for what you meant).
If you give the wrong type of argument for an option, it will give you an error rather than supplying the wrong input to your application.
$ cmd --halp
Invalid option '--halp' - perhaps you meant '--help'?
$ cmd --count str
Invalid value for option 'count' - expected type Int, received value: str.
Other helpful features include reformatting the help text based on the size of the console, so that it fits even if the console is narrow, and accepting not just an array (eg. process.argv), but a string or object as well, making things like testing much easier.
## About
Optionator uses [type-check](https://github.com/gkz/type-check) and [levn](https://github.com/gkz/levn) behind the scenes to cast and verify input according the specified types.
MIT license. Version 0.8.2
npm install optionator
For updates on Optionator, [follow me on twitter](https://twitter.com/gkzahariev).
## Usage
`require('optionator');` returns a function. It has one property, `VERSION`, the current version of the library as a string. This function is called with an object specifying your options and other information, see the [settings format section](#settings-format). This in turn returns an object with three properties, `parse`, `parseArgv`, `generateHelp`, and `generateHelpForOption`, which are all functions.
```js
var optionator = require('optionator')({
prepend: 'Usage: cmd [options]',
append: 'Version 1.0.0',
options: [{
option: 'help',
alias: 'h',
type: 'Boolean',
description: 'displays help'
}, {
option: 'count',
alias: 'c',
type: 'Int',
description: 'number of things',
example: 'cmd --count 2'
}]
});
var options = optionator.parseArgv(process.argv);
if (options.help) {
console.log(optionator.generateHelp());
}
...
```
### parse(input, parseOptions)
`parse` processes the `input` according to your settings, and returns an object with the results.
##### arguments
* input - `[String] | Object | String` - the input you wish to parse
* parseOptions - `{slice: Int}` - all options optional
- `slice` specifies how much to slice away from the beginning if the input is an array or string - by default `0` for string, `2` for array (works with `process.argv`)
##### returns
`Object` - the parsed options, each key is a camelCase version of the option name (specified in dash-case), and each value is the processed value for that option. Positional values are in an array under the `_` key.
##### example
```js
parse(['node', 't.js', '--count', '2', 'positional']); // {count: 2, _: ['positional']}
parse('--count 2 positional'); // {count: 2, _: ['positional']}
parse({count: 2, _:['positional']}); // {count: 2, _: ['positional']}
```
### parseArgv(input)
`parseArgv` works exactly like `parse`, but only for array input and it slices off the first two elements.
##### arguments
* input - `[String]` - the input you wish to parse
##### returns
See "returns" section in "parse"
##### example
```js
parseArgv(process.argv);
```
### generateHelp(helpOptions)
`generateHelp` produces help text based on your settings.
##### arguments
* helpOptions - `{showHidden: Boolean, interpolate: Object}` - all options optional
- `showHidden` specifies whether to show options with `hidden: true` specified, by default it is `false`
- `interpolate` specify data to be interpolated in `prepend` and `append` text, `{{key}}` is the format - eg. `generateHelp({interpolate:{version: '0.4.2'}})`, will change this `append` text: `Version {{version}}` to `Version 0.4.2`
##### returns
`String` - the generated help text
##### example
```js
generateHelp(); /*
"Usage: cmd [options] positional
-h, --help displays help
-c, --count Int number of things
Version 1.0.0
"*/
```
### generateHelpForOption(optionName)
`generateHelpForOption` produces expanded help text for the specified with `optionName` option. If an `example` was specified for the option, it will be displayed, and if a `longDescription` was specified, it will display that instead of the `description`.
##### arguments
* optionName - `String` - the name of the option to display
##### returns
`String` - the generated help text for the option
##### example
```js
generateHelpForOption('count'); /*
"-c, --count Int
description: number of things
example: cmd --count 2
"*/
```
## Settings Format
When your `require('optionator')`, you get a function that takes in a settings object. This object has the type:
{
prepend: String,
append: String,
options: [{heading: String} | {
option: String,
alias: [String] | String,
type: String,
enum: [String],
default: String,
restPositional: Boolean,
required: Boolean,
overrideRequired: Boolean,
dependsOn: [String] | String,
concatRepeatedArrays: Boolean | (Boolean, Object),
mergeRepeatedObjects: Boolean,
description: String,
longDescription: String,
example: [String] | String
}],
helpStyle: {
aliasSeparator: String,
typeSeparator: String,
descriptionSeparator: String,
initialIndent: Int,
secondaryIndent: Int,
maxPadFactor: Number
},
mutuallyExclusive: [[String | [String]]],
concatRepeatedArrays: Boolean | (Boolean, Object), // deprecated, set in defaults object
mergeRepeatedObjects: Boolean, // deprecated, set in defaults object
positionalAnywhere: Boolean,
typeAliases: Object,
defaults: Object
}
All of the properties are optional (the `Maybe` has been excluded for brevities sake), except for having either `heading: String` or `option: String` in each object in the `options` array.
### Top Level Properties
* `prepend` is an optional string to be placed before the options in the help text
* `append` is an optional string to be placed after the options in the help text
* `options` is a required array specifying your options and headings, the options and headings will be displayed in the order specified
* `helpStyle` is an optional object which enables you to change the default appearance of some aspects of the help text
* `mutuallyExclusive` is an optional array of arrays of either strings or arrays of strings. The top level array is a list of rules, each rule is a list of elements - each element can be either a string (the name of an option), or a list of strings (a group of option names) - there will be an error if more than one element is present
* `concatRepeatedArrays` see description under the "Option Properties" heading - use at the top level is deprecated, if you want to set this for all options, use the `defaults` property
* `mergeRepeatedObjects` see description under the "Option Properties" heading - use at the top level is deprecated, if you want to set this for all options, use the `defaults` property
* `positionalAnywhere` is an optional boolean (defaults to `true`) - when `true` it allows positional arguments anywhere, when `false`, all arguments after the first positional one are taken to be positional as well, even if they look like a flag. For example, with `positionalAnywhere: false`, the arguments `--flag --boom 12 --crack` would have two positional arguments: `12` and `--crack`
* `typeAliases` is an optional object, it allows you to set aliases for types, eg. `{Path: 'String'}` would allow you to use the type `Path` as an alias for the type `String`
* `defaults` is an optional object following the option properties format, which specifies default values for all options. A default will be overridden if manually set. For example, you can do `default: { type: "String" }` to set the default type of all options to `String`, and then override that default in an individual option by setting the `type` property
#### Heading Properties
* `heading` a required string, the name of the heading
#### Option Properties
* `option` the required name of the option - use dash-case, without the leading dashes
* `alias` is an optional string or array of strings which specify any aliases for the option
* `type` is a required string in the [type check](https://github.com/gkz/type-check) [format](https://github.com/gkz/type-check#type-format), this will be used to cast the inputted value and validate it
* `enum` is an optional array of strings, each string will be parsed by [levn](https://github.com/gkz/levn) - the argument value must be one of the resulting values - each potential value must validate against the specified `type`
* `default` is a optional string, which will be parsed by [levn](https://github.com/gkz/levn) and used as the default value if none is set - the value must validate against the specified `type`
* `restPositional` is an optional boolean - if set to `true`, everything after the option will be taken to be a positional argument, even if it looks like a named argument
* `required` is an optional boolean - if set to `true`, the option parsing will fail if the option is not defined
* `overrideRequired` is a optional boolean - if set to `true` and the option is used, and there is another option which is required but not set, it will override the need for the required option and there will be no error - this is useful if you have required options and want to use `--help` or `--version` flags
* `concatRepeatedArrays` is an optional boolean or tuple with boolean and options object (defaults to `false`) - when set to `true` and an option contains an array value and is repeated, the subsequent values for the flag will be appended rather than overwriting the original value - eg. option `g` of type `[String]`: `-g a -g b -g c,d` will result in `['a','b','c','d']`
You can supply an options object by giving the following value: `[true, options]`. The one currently supported option is `oneValuePerFlag`, this only allows one array value per flag. This is useful if your potential values contain a comma.
* `mergeRepeatedObjects` is an optional boolean (defaults to `false`) - when set to `true` and an option contains an object value and is repeated, the subsequent values for the flag will be merged rather than overwriting the original value - eg. option `g` of type `Object`: `-g a:1 -g b:2 -g c:3,d:4` will result in `{a: 1, b: 2, c: 3, d: 4}`
* `dependsOn` is an optional string or array of strings - if simply a string (the name of another option), it will make sure that that other option is set, if an array of strings, depending on whether `'and'` or `'or'` is first, it will either check whether all (`['and', 'option-a', 'option-b']`), or at least one (`['or', 'option-a', 'option-b']`) other options are set
* `description` is an optional string, which will be displayed next to the option in the help text
* `longDescription` is an optional string, it will be displayed instead of the `description` when `generateHelpForOption` is used
* `example` is an optional string or array of strings with example(s) for the option - these will be displayed when `generateHelpForOption` is used
#### Help Style Properties
* `aliasSeparator` is an optional string, separates multiple names from each other - default: ' ,'
* `typeSeparator` is an optional string, separates the type from the names - default: ' '
* `descriptionSeparator` is an optional string , separates the description from the padded name and type - default: ' '
* `initialIndent` is an optional int - the amount of indent for options - default: 2
* `secondaryIndent` is an optional int - the amount of indent if wrapped fully (in addition to the initial indent) - default: 4
* `maxPadFactor` is an optional number - affects the default level of padding for the names/type, it is multiplied by the average of the length of the names/type - default: 1.5
## Argument Format
At the highest level there are two types of arguments: named, and positional.
Name arguments of any length are prefixed with `--` (eg. `--go`), and those of one character may be prefixed with either `--` or `-` (eg. `-g`).
There are two types of named arguments: boolean flags (eg. `--problemo`, `-p`) which take no value and result in a `true` if they are present, the falsey `undefined` if they are not present, or `false` if present and explicitly prefixed with `no` (eg. `--no-problemo`). Named arguments with values (eg. `--tseries 800`, `-t 800`) are the other type. If the option has a type `Boolean` it will automatically be made into a boolean flag. Any other type results in a named argument that takes a value.
For more information about how to properly set types to get the value you want, take a look at the [type check](https://github.com/gkz/type-check) and [levn](https://github.com/gkz/levn) pages.
You can group single character arguments that use a single `-`, however all except the last must be boolean flags (which take no value). The last may be a boolean flag, or an argument which takes a value - eg. `-ba 2` is equivalent to `-b -a 2`.
Positional arguments are all those values which do not fall under the above - they can be anywhere, not just at the end. For example, in `cmd -b one -a 2 two` where `b` is a boolean flag, and `a` has the type `Number`, there are two positional arguments, `one` and `two`.
Everything after an `--` is positional, even if it looks like a named argument.
You may optionally use `=` to separate option names from values, for example: `--count=2`.
If you specify the option `NUM`, then any argument using a single `-` followed by a number will be valid and will set the value of `NUM`. Eg. `-2` will be parsed into `NUM: 2`.
If duplicate named arguments are present, the last one will be taken.
## Technical About
`optionator` is written in [LiveScript](http://livescript.net/) - a language that compiles to JavaScript. It uses [levn](https://github.com/gkz/levn) to cast arguments to their specified type, and uses [type-check](https://github.com/gkz/type-check) to validate values. It also uses the [prelude.ls](http://preludels.com/) library.
optionator-0.8.2/package.json.ls 000664 000000 000000 00000001327 12770561512 016613 0 ustar 00root root 000000 000000 name: 'optionator'
version: '0.8.2'
author: 'George Zahariev '
description: 'option parsing and help generation'
homepage: 'https://github.com/gkz/optionator'
keywords:
'options'
'flags'
'option parsing'
'cli'
files:
'lib'
'README.md'
'LICENSE'
main: './lib/'
bugs: 'https://github.com/gkz/optionator/issues'
license: 'MIT'
engines:
node: '>= 0.8.0'
repository:
type: 'git'
url: 'git://github.com/gkz/optionator.git'
scripts:
test: "make test"
dependencies:
'prelude-ls': '~1.1.2'
'deep-is': '~0.1.3'
wordwrap: '~1.0.0'
'type-check': '~0.3.2'
levn: '~0.3.0'
'fast-levenshtein': '~2.0.4'
dev-dependencies:
livescript: '~1.5.0'
mocha: '~3.0.2'
istanbul: '~0.4.1'
optionator-0.8.2/src/ 000775 000000 000000 00000000000 12770561512 014474 5 ustar 00root root 000000 000000 optionator-0.8.2/src/help.ls 000664 000000 000000 00000015421 12770561512 015767 0 ustar 00root root 000000 000000 {id, find, sort, min, max, map, unlines} = require 'prelude-ls'
{name-to-raw, dasherize, natural-join} = require './util'
require! wordwrap
get-pre-text = (
{option: main-name, short-names = [], long-names = [], type, description}:option,
{alias-separator, type-separator, initial-indent}
max-width
) ->
if option.negate-name
main-name = "no-#main-name"
long-names = (map (-> "no-#it"), long-names) if long-names
names = if main-name.length is 1
[main-name] ++ short-names ++ long-names
else
short-names ++ [main-name] ++ long-names
names-string = (map name-to-raw, names).join alias-separator
names-string-len = names-string.length
type-separator-string = if main-name is 'NUM' then '::' else type-separator
type-separator-string-len = type-separator-string.length
if max-width? and not option.boolean
and initial-indent + names-string-len + type-separator-string-len + type.length > max-width
wrap = wordwrap (initial-indent + names-string-len + type-separator-string-len), max-width
"#names-string#type-separator-string#{ wrap type .replace /^\s+/ ''}"
else
"#names-string#{ if option.boolean then '' else "#type-separator-string#type" }"
set-help-style-defaults = (help-style) !->
help-style.alias-separator ?= ', '
help-style.type-separator ?= ' '
help-style.description-separator ?= ' '
help-style.initial-indent ?= 2
help-style.secondary-indent ?= 4
help-style.max-pad-factor ?= 1.5
generate-help-for-option = (get-option, {stdout, help-style = {}}) ->
set-help-style-defaults help-style
(option-name) ->
max-width = if stdout?.isTTY then stdout.columns - 1 else null
wrap = if max-width then wordwrap max-width else id
try
option = get-option(dasherize option-name)
catch
return e.message
pre = get-pre-text option, help-style
default-string = if option.default and not option.negate-name
"\ndefault: #{option.default}"
else
''
rest-positional-string = if option.rest-positional then 'Everything after this option is considered a positional argument, even if it looks like an option.' else ''
description = option.long-description or option.description and sentencize option.description
full-description = if description and rest-positional-string
"#description #rest-positional-string"
else if description or rest-positional-string
that
else
''
pre-description = 'description:'
description-string = if not full-description
''
else if max-width and full-description.length - 1 - pre-description.length > max-width
"\n#pre-description\n#{ wrap full-description }"
else
"\n#pre-description #full-description"
example-string = if option.example
examples = [].concat that
if examples.length > 1
"\nexamples:\n#{ unlines examples }"
else
"\nexample: #{examples.0}"
else
''
seperator = if default-string or description-string or example-string then "\n#{ '=' * pre.length }" else ''
"#pre#seperator#default-string#description-string#example-string"
generate-help = ({options, prepend, append, help-style = {}, stdout}) ->
set-help-style-defaults help-style
{
alias-separator, type-separator, description-separator,
max-pad-factor, initial-indent, secondary-indent
} = help-style
({show-hidden, interpolate} = {}) ->
max-width = if stdout?.isTTY then stdout.columns - 1 else null
output = []
out = -> output.push it ? ''
if prepend
out (if interpolate then interp prepend, interpolate else prepend)
out!
data = []
option-count = 0
total-pre-len = 0
pre-lens = []
for item in options when show-hidden or not item.hidden
if item.heading
data.push {type: 'heading', value: that}
else
pre = get-pre-text item, help-style, max-width
desc-parts = []
desc-parts.push that if item.description?
desc-parts.push "either: #{ natural-join that }" if item.enum
desc-parts.push "default: #{item.default}" if item.default and not item.negate-name
desc = desc-parts.join ' - '
data.push {type: 'option', pre, desc: desc, desc-len: desc.length}
pre-len = pre.length
option-count++
total-pre-len += pre-len
pre-lens.push pre-len
sorted-pre-lens = sort pre-lens
max-pre-len = sorted-pre-lens[*-1]
pre-len-mean = initial-indent + total-pre-len / option-count
x = if option-count > 2 then min pre-len-mean * max-pad-factor, max-pre-len else max-pre-len
for pre-len in sorted-pre-lens by -1
if pre-len <= x
pad-amount = pre-len
break
desc-sep-len = description-separator.length
if max-width?
full-wrap-count = 0
partial-wrap-count = 0
for item in data when item.type is 'option'
{pre, desc, desc-len} = item
if desc-len is 0
item.wrap = 'none'
else
pre-len = (max pad-amount, pre.length) + initial-indent + desc-sep-len
total-len = pre-len + desc-len
if total-len > max-width
if desc-len / 2.5 > max-width - pre-len
full-wrap-count++
item.wrap = 'full'
else
partial-wrap-count++
item.wrap = 'partial'
else
item.wrap = 'none'
initial-space = ' ' * initial-indent
wrap-all-full = option-count > 1 and full-wrap-count + partial-wrap-count * 0.5 > option-count * 0.5
for item, i in data
if item.type is 'heading'
out! unless i is 0
out "#{item.value}:"
else
{pre, desc, desc-len, wrap} = item
if max-width?
if wrap-all-full or wrap is 'full'
wrap = wordwrap (initial-indent + secondary-indent), max-width
out "#initial-space#pre\n#{ wrap desc }"
continue
else if wrap is 'partial'
wrap = wordwrap (initial-indent + desc-sep-len + max pad-amount, pre.length), max-width
out "#initial-space#{ pad pre, pad-amount }#description-separator#{ wrap desc .replace /^\s+/, ''}"
continue
if desc-len is 0
out "#initial-space#pre"
else
out "#initial-space#{ pad pre, pad-amount }#description-separator#desc"
if append
out!
out (if interpolate then interp append, interpolate else append)
unlines output
function pad str, num
len = str.length
pad-amount = (num - len)
"#str#{ ' ' * (if pad-amount > 0 then pad-amount else 0)}"
function sentencize str
first = str.char-at 0 .to-upper-case!
rest = str.slice 1
period = if /[\.!\?]$/.test str then '' else '.'
"#first#rest#period"
function interp string, object
string.replace /{{([a-zA-Z$_][a-zA-Z$_0-9]*)}}/g, (, key) -> object[key] ? "{{#key}}"
module.exports = {generate-help, generate-help-for-option}
optionator-0.8.2/src/index.ls 000664 000000 000000 00000027213 12770561512 016150 0 ustar 00root root 000000 000000 VERSION = '0.8.2'
{id, map, compact, any, group-by, partition, chars, is-it-NaN, keys, Obj, camelize} = require 'prelude-ls'
deep-is = require 'deep-is'
{closest-string, name-to-raw, dasherize, natural-join} = require './util'
{generate-help, generate-help-for-option} = require './help'
{parsed-type-check, parse-type} = require 'type-check'
{parsed-type-parse: parse-levn} = require 'levn'
camelize-keys = (obj) -> {[(camelize key), value] for key, value of obj}
parse-string = (string) ->
assign-opt = '--?[a-zA-Z][-a-z-A-Z0-9]*='
regex = //
(?:#assign-opt)?(?:'(?:\\'|[^'])+'|"(?:\\"|[^"])+")
| [^'"\s]+
//g
replace-regex = //^(#assign-opt)?['"]([\s\S]*)['"]$//
result = map (.replace replace-regex, '$1$2'), (string.match regex or [])
result
main = (lib-options) ->
opts = {}
defaults = {}
required = []
if typeof! lib-options.stdout is 'Undefined'
lib-options.stdout = process.stdout
lib-options.positional-anywhere ?= true
lib-options.type-aliases ?= {}
lib-options.defaults ?= {}
if lib-options.concat-repeated-arrays?
lib-options.defaults.concat-repeated-arrays = lib-options.concat-repeated-arrays
if lib-options.merge-repeated-objects?
lib-options.defaults.merge-repeated-objects = lib-options.merge-repeated-objects
traverse = (options) !->
throw new Error 'No options defined.' unless typeof! options is 'Array'
for option in options when not option.heading?
name = option.option
throw new Error "Option '#name' already defined." if opts[name]?
for k, v of lib-options.defaults
option[k] ?= v
option.boolean ?= true if option.type is 'Boolean'
unless option.parsed-type?
throw new Error "No type defined for option '#name'." unless option.type
try
type = if lib-options.type-aliases[option.type]? then that else option.type
option.parsed-type = parse-type type
catch
throw new Error "Option '#name': Error parsing type '#{option.type}': #{e.message}"
if option.default
try
defaults[name] = parse-levn option.parsed-type, option.default
catch
throw new Error "Option '#name': Error parsing default value '#{option.default}' for type '#{option.type}': #{e.message}"
if option.enum and not option.parsed-possiblities
parsed-possibilities = []
parsed-type = option.parsed-type
for possibility in option.enum
try
parsed-possibilities.push parse-levn parsed-type, possibility
catch
throw new Error "Option '#name': Error parsing enum value '#possibility' for type '#{option.type}': #{e.message}"
option.parsed-possibilities = parsed-possibilities
if option.depends-on
if that.length
[raw-depends-type, ...depends-opts] = [].concat option.depends-on
depends-type = raw-depends-type.to-lower-case!
if depends-opts.length
if depends-type in <[ and or ]>
option.depends-on = [depends-type, ...depends-opts]
else
throw new Error "Option '#name': If you have more than one dependency, you must specify either 'and' or 'or'"
else
if depends-type.to-lower-case! in <[ and or ]>
option.depends-on = null
else
option.depends-on = ['and', raw-depends-type] # if only one dependency, doesn't matter and/or
else
option.depends-on = null
required.push name if option.required
opts[name] = option
if option.concat-repeated-arrays?
cra = option.concat-repeated-arrays
if 'Boolean' is typeof! cra
option.concat-repeated-arrays = [cra, {}]
else if cra.length is 1
option.concat-repeated-arrays = [cra.0, {}]
else if cra.length isnt 2
throw new Error "Invalid setting for concatRepeatedArrays"
if option.alias or option.aliases
throw new Error "-NUM option can't have aliases." if name is 'NUM'
option.aliases ?= [].concat option.alias if option.alias
for alias in option.aliases
throw new Error "Option '#alias' already defined." if opts[alias]?
opts[alias] = option
[short-names, long-names] = partition (.length is 1), option.aliases
option.short-names ?= short-names
option.long-names ?= long-names
if (not option.aliases or option.short-names.length is 0)
and option.type is 'Boolean' and option.default is 'true'
option.negate-name = true
traverse lib-options.options
get-option = (name) ->
opt = opts[name]
unless opt?
possibly-meant = closest-string (keys opts), name
throw new Error "Invalid option '#{ name-to-raw name}'#{ if possibly-meant then " - perhaps you meant '#{ name-to-raw possibly-meant }'?" else '.'}"
opt
parse = (input, {slice} = {}) ->
obj = {}
positional = []
rest-positional = false
override-required = false
prop = null
set-value = (name, value) !->
opt = get-option name
if opt.boolean
val = value
else
try
cra = opt.concat-repeated-arrays
if cra? and cra.0 and cra.1.one-value-per-flag
and opt.parsed-type.length is 1 and opt.parsed-type.0.structure is 'array'
val = [parse-levn opt.parsed-type.0.of, value]
else
val = parse-levn opt.parsed-type, value
catch
throw new Error "Invalid value for option '#name' - expected type #{opt.type}, received value: #value."
if opt.enum and not any (-> deep-is it, val), opt.parsed-possibilities
throw new Error "Option #name: '#val' not one of #{ natural-join opt.enum }."
current-type = typeof! obj[name]
if obj[name]?
if opt.concat-repeated-arrays? and opt.concat-repeated-arrays.0 and current-type is 'Array'
obj[name] ++= val
else if opt.merge-repeated-objects and current-type is 'Object'
obj[name] <<< val
else
obj[name] = val
else
obj[name] = val
rest-positional := true if opt.rest-positional
override-required := true if opt.override-required
set-defaults = !->
for name, value of defaults
unless obj[name]?
obj[name] = value
check-required = !->
return if override-required
for name in required
throw new Error "Option #{ name-to-raw name} is required." unless obj[name]
mutually-exclusive-error = (first, second) ->
throw new Error "The options #{ name-to-raw first } and #{ name-to-raw second } are mutually exclusive - you cannot use them at the same time."
check-mutually-exclusive = !->
rules = lib-options.mutually-exclusive
return unless rules
for rule in rules
present = null
for element in rule
if typeof! element is 'Array'
for opt in element
if opt of obj
if present?
mutually-exclusive-error present, opt
else
present = opt
break
else
if element of obj
if present?
mutually-exclusive-error present, element
else
present = element
check-dependency = (option) ->
depends-on = option.depends-on
return true if not depends-on or option.dependencies-met
[type, ...target-option-names] = depends-on
for target-option-name in target-option-names
target-option = obj[target-option-name]
if target-option and check-dependency target-option
return true if type is 'or' # we only need one dependency to be met for "or"
else if type is 'and'
throw new Error "The option '#{option.option}' did not have its dependencies met."
if type is 'and'
true # no errors with "and", thus we're good
else # type is 'or' - no dependencies were met, thus no good
throw new Error "The option '#{option.option}' did not meet any of its dependencies."
check-dependencies = !->
for name of obj
check-dependency opts[name]
check-prop = !->
if prop
throw new Error "Value for '#prop' of type '#{ get-option prop .type}' required."
switch typeof! input
| 'String'
args = parse-string input.slice slice ? 0
| 'Array'
args = input.slice (slice ? 2) # slice away "node" and "filename" by default
| 'Object'
obj = {}
for key, value of input when key isnt '_'
option = get-option (dasherize key)
if parsed-type-check option.parsed-type, value
obj[option.option] = value
else
throw new Error "Option '#{option.option}': Invalid type for '#value' - expected type '#{option.type}'."
check-mutually-exclusive!
check-dependencies!
set-defaults!
check-required!
return (camelize-keys obj) <<< {_: input._ or []}
| otherwise
throw new Error "Invalid argument to 'parse': #input."
for arg in args
if arg is '--'
rest-positional := true
else if rest-positional
positional.push arg
else
if arg.match /^(--?)([a-zA-Z][-a-zA-Z0-9]*)(=)?(.*)?$/
result = that
check-prop!
short = result.1.length is 1
arg-name = result.2
using-assign = result.3?
val = result.4
throw new Error "No value for '#arg-name' specified." if using-assign and not val?
if short
flags = chars arg-name
len = flags.length
for flag, i in flags
opt = get-option flag
name = opt.option
if rest-positional
positional.push flag
else if i is len - 1
if using-assign
val-prime = if opt.boolean then parse-levn [type: 'Boolean'], val else val
set-value name, val-prime
else if opt.boolean
set-value name, true
else
prop := name
else if opt.boolean
set-value name, true
else
throw new Error "Can't set argument '#flag' when not last flag in a group of short flags."
else
negated = false
if arg-name.match /^no-(.+)$/
negated = true
noed-name = that.1
opt = get-option noed-name
else
opt = get-option arg-name
name = opt.option
if opt.boolean
val-prime = if using-assign then parse-levn [type: 'Boolean'], val else true
if negated
set-value name, not val-prime
else
set-value name, val-prime
else
throw new Error "Only use 'no-' prefix for Boolean options, not with '#noed-name'." if negated
if using-assign
set-value name, val
else
prop := name
else if arg.match /^-([0-9]+(?:\.[0-9]+)?)$/
opt = opts.NUM
throw new Error 'No -NUM option defined.' unless opt
set-value opt.option, that.1
else
if prop
set-value prop, arg
prop := null
else
positional.push arg
rest-positional := true if not lib-options.positional-anywhere
check-prop!
check-mutually-exclusive!
check-dependencies!
set-defaults!
check-required!
(camelize-keys obj) <<< {_: positional}
parse: parse
parse-argv: -> parse it, slice: 2
generate-help: generate-help lib-options
generate-help-for-option: generate-help-for-option get-option, lib-options
main <<< {VERSION}
module.exports = main
optionator-0.8.2/src/util.ls 000664 000000 000000 00000001440 12770561512 016010 0 ustar 00root root 000000 000000 {map, sort-by}:prelude = require 'prelude-ls'
fl = require 'fast-levenshtein'
closest-string = (possibilities, input) ->
return unless possibilities.length
distances = possibilities |> map ->
[longer, shorter] = if input.length > it.length then [input, it] else [it, input]
{string: it, distance: fl.get longer, shorter}
{string, distance} = sort-by (.distance), distances .0
string
name-to-raw = (name) -> if name.length is 1 or name is 'NUM' then "-#name" else "--#name"
dasherize = (string) ->
if /^[A-Z]/.test string
string
else
prelude.dasherize string
natural-join = (array) ->
if array.length < 3
array.join ' or '
else
"#{ array.slice 0, -1 .join ', ' }, or #{array[*-1]}"
module.exports = {closest-string, name-to-raw, dasherize, natural-join}
optionator-0.8.2/test/ 000775 000000 000000 00000000000 12770561512 014664 5 ustar 00root root 000000 000000 optionator-0.8.2/test/help.ls 000664 000000 000000 00000032672 12770561512 016166 0 ustar 00root root 000000 000000 optionator = require '..'
{strict-equal: equal} = require 'assert'
q = (expected, options, args) ->
{generate-help} = optionator options
help-text = generate-help args
try
equal help-text, expected
catch
console.log '# Result:'
console.log help-text
console.log '# Expected:'
console.log expected
throw e
qo = (expected, option-name, options) ->
{generate-help-for-option} = optionator options
help-text = generate-help-for-option option-name
try
equal help-text, expected
catch
console.log '# Result:'
console.log help-text
console.log '# Expected:'
console.log expected
throw e
suite 'help' ->
help-option =
option: 'help'
type: 'Boolean'
description: 'recieve help - print this info'
count-option =
option: 'count'
type: 'Number'
description: 'count of stuff that is to be counted'
obj-option =
option: 'obj'
type: '{x: Number, y: Boolean, z: Object}'
description: 'an object full of things and stuff'
test 'single basic option' ->
q ' --help recieve help - print this info', options: [help-option]
test 'prepend/append' ->
q '''
cmd
--help recieve help - print this info
version 0.1.0
''', {
prepend: 'cmd'
append: 'version 0.1.0'
options: [help-option]
}
test 'heading' ->
q '''
Options:
--help recieve help - print this info
''', {
options:
* heading: 'Options'
* help-option
}
test 'heading with prepend' ->
q '''
cmd
Options:
--help recieve help - print this info
''', {
prepend: 'cmd'
options:
* heading: 'Options'
* help-option
}
test 'two options' ->
q ' --help recieve help - print this info
\n --count Number count of stuff that is to be counted', {
options: [help-option, count-option]
}
test 'headings' ->
q '''
Options:
--help recieve help - print this info
More Options:
--count Number count of stuff that is to be counted
''', {
options:
* heading: 'Options'
* help-option
* heading: 'More Options'
* count-option
}
test 'greatly differnt lengths' ->
q '''
cmd
--help recieve help - print this info
--count Number count of stuff that is to be counted
--obj {x: Number, y: Boolean, z: Object} an object full of things and stuff
''', {
prepend: 'cmd'
options: [help-option, count-option, obj-option]
}
test 'short main name' ->
q ' -h help me', options: [{
option: 'h'
type: 'Boolean'
description: 'help me'
}]
test 'one alias' ->
q ' -h, -H, --help help me', options: [{
option: 'help'
alias: ['h' 'H']
type: 'Boolean'
description: 'help me'
}]
test 'enum type' ->
q ' --size String shirt size - either: small, medium, or large', options: [{
option: 'size'
type: 'String'
enum: <[ small medium large ]>
description: 'shirt size'
}]
test 'enum type, just two' ->
q ' --size String shirt size - either: small or large', options: [{
option: 'size'
type: 'String'
enum: <[ small large ]>
description: 'shirt size'
}]
test 'default' ->
q ' --count Number count of stuff that is to be counted - default: 2', options: [{
option: 'count'
type: 'Number'
description: 'count of stuff that is to be counted'
default: '2'
}]
test 'default with no description' ->
q ' --count Number default: 2', options: [{
option: 'count'
type: 'Number'
default: '2'
}]
test 'default - boolean with true when no short alias' ->
q ' --no-colour', options: [{
option: 'colour'
type: 'Boolean'
default: 'true'
}]
test 'default - boolean with true when no short alias but long aliases' ->
q ' --no-colour, --no-color', options: [{
option: 'colour'
type: 'Boolean'
alias: 'color'
default: 'true'
}]
test 'default - boolean with true with short alias' ->
q ' -c, --colour default: true', options: [{
option: 'colour'
alias: 'c'
type: 'Boolean'
default: 'true'
}]
test 'many aliases' ->
q ' -h, -H, --halp, --help halp me', options: [{
option: 'halp'
alias: ['help' 'h' 'H']
type: 'Boolean'
description: 'halp me'
}]
test 'aliases prop predefined' ->
q ' -h, -H, --halp, --help halp me', options: [{
option: 'halp'
aliases: ['help' 'h' 'H']
type: 'Boolean'
description: 'halp me'
}]
test 'NUM' ->
q ' -NUM::Int the number', options: [{
option: 'NUM'
type: 'Int'
description: 'the number'
}]
test 'show hidden' ->
opts =
options:
* option: 'hidden'
type: 'Boolean'
description: 'magic'
hidden: true
* option: 'visible'
type: 'Boolean'
description: 'boring'
q ' --visible boring', opts
q ' --hidden magic\n --visible boring', opts, {+show-hidden}
suite 'interpolation' ->
opts =
prepend: 'usage {{x}}'
options: [{heading: 'Options'}]
append: 'version {{version}}'
test 'none' ->
q '''
usage {{x}}
Options:
version {{version}}
''', opts
test 'partial' ->
q '''
usage cmd
Options:
version {{version}}
''', opts, {interpolate: {x: 'cmd'}}
test 'basic' ->
q '''
usage cmd
Options:
version 2
''', opts, {interpolate: {x: 'cmd', version: 2}}
test 'with empty string' ->
q '''
usage
Options:
version
''', opts, {interpolate: {x: '', version: ''}}
test 'more than once, with number' ->
opts =
prepend: 'usage {{$0}}, {{$0}}'
options: [{heading: 'Options'}]
append: '{{$0}} and {{$0}}'
q '''
usage xx, xx
Options:
xx and xx
''', opts, {interpolate: {$0: 'xx'}}
test 'no stdout' ->
q '''
cmd
--obj {x: Number, y: Boolean, z: Object} an object full of things and stuff
''', {
prepend: 'cmd'
options: [obj-option]
stdout: null
}
test 'no description' ->
q '''
cmd
--help
''', {
prepend: 'cmd'
options: [{
option: 'help'
type: 'Boolean'
}]
}
suite 'wrapping' ->
test 'basic with max-width' ->
q '''
cmd
--help recieve help - print this info
''', {
prepend: 'cmd'
options: [help-option]
stdout: {isTTY: true, columns: 250}
}
test 'partial single' ->
q '''
cmd
--obj {x: Number, y: Boolean, z: Object} an object full of
things and stuff
''', {
prepend: 'cmd'
options: [obj-option]
stdout: {isTTY: true, columns: 68}
}
test 'full single' ->
q '''
cmd
--obj {x: Number, y: Boolean, z: Object}
an object full of things and stuff
''', {
prepend: 'cmd'
options: [obj-option]
stdout: {isTTY: true, columns: 50}
}
test 'partial several' ->
q '''
cmd
Options:
--help recieve help - print this info
--count Number count of stuff that is to be counted
--obj {x: Number, y: Boolean, z: Object} an object full of things
and stuff
''', {
prepend: 'cmd'
options:
* heading: 'Options'
* help-option
* count-option
* obj-option
stdout: {isTTY: true, columns: 70}
}
test 'full several' ->
q '''
cmd
Options:
--help recieve help - print this info
--count Number count of stuff that is to be counted
--obj {x: Number, y: Boolean, z: Object}
an object full of things and stuff
''', {
prepend: 'cmd'
options:
* heading: 'Options'
* help-option
* count-option
* obj-option
stdout: {isTTY: true, columns: 55}
}
test 'partial all' ->
q '''
cmd
--help recieve help - print this
info
--count Number count of stuff that is to
be counted
''', {
prepend: 'cmd'
options:
* help-option
* count-option
stdout: {isTTY: true, columns: 46}
}
test 'full all' ->
q '''
cmd
--help
recieve help -
print this info
--count Number
count of stuff
that is to be
counted
''', {
prepend: 'cmd'
options:
* help-option
* count-option
stdout: {isTTY: true, columns: 26}
}
test 'type' ->
q '''
cmd
--obj {x: Number, y:
Boolean, z: Object}
an object full of things
and stuff
''', {
prepend: 'cmd'
options: [obj-option]
stdout: {isTTY: true, columns: 32}
}
suite 'for option' ->
opts =
options:
* option: 'times-num'
type: 'Number'
description: 'times to do something.'
example: '--times-num 23'
* option: 'input'
alias: 'i'
type: 'OBJ::Object'
description: 'the input that you want'
example: '--input "x: 52, y: [1,2,3]"'
default: '{a: 1}'
* option: 'nope'
type: 'Boolean'
description: 'nothing at all'
long-description: 'really nothing at all'
* option: 'nope2'
type: 'Boolean'
test 'times' ->
qo '''
--times-num Number
==================
description: Times to do something.
example: --times-num 23
''', 'times-num', opts
test 'input' ->
qo '''
-i, --input OBJ::Object
=======================
default: {a: 1}
description: The input that you want.
example: --input "x: 52, y: [1,2,3]"
''', 'input', opts
qo '''
-i, --input OBJ::Object
=======================
default: {a: 1}
description: The input that you want.
example: --input "x: 52, y: [1,2,3]"
''', 'i', opts
test 'no example - long description' ->
qo '''
--nope
======
description: really nothing at all
''', 'nope', opts
test 'long description text with max width' ->
opts =
options: [
option: 'long'
type: 'String'
description: 'it goes on and on my friends, some people started singing it not knowing what it was'
]
stdout: {isTTY: true, columns: 50}
qo '''
--long String
=============
description:
It goes on and on my friends, some people
started singing it not knowing what it was.
''', 'long', opts
opts.stdout = null
qo '''
--long String
=============
description: It goes on and on my friends, some people started singing it not knowing what it was.
''', 'long', opts
test 'multiple examples' ->
qo '''
--op
====
description: The thing.
examples:
cmd --op
cmd --no-op
''', 'op', {options: [{
option: 'op'
type: 'Boolean'
description: 'the thing'
example:
'cmd --op'
'cmd --no-op'
}]}
test 'rest positional' ->
opts =
options: [{
option: 'rest'
type: 'Boolean'
description: 'The rest'
rest-positional: true
}]
stdout: {isTTY: false}
qo '''
--rest
======
description: The rest. Everything after this option is considered a positional argument, even if it looks like an option.
''', 'rest', opts
# no description
delete opts.options.0.description
qo '''
--rest
======
description: Everything after this option is considered a positional argument, even if it looks like an option.
''', 'rest', opts
test 'no description or rest positional' ->
qo '--nope2', 'nope2', opts
test 'invalid option' ->
qo "Invalid option '--FAKE' - perhaps you meant '-i'?", 'FAKE', opts
suite 'help style settings' ->
test 'all different' ->
opts =
help-style:
alias-separator: '|'
type-separator: ': '
description-separator: ' > '
initial-indent: 1
secondary-indent: 2
max-pad-factor: 10
prepend: 'cmd'
options:
* option: 'help'
alias: 'h'
type: 'Boolean'
description: 'recieve help - print this info'
* count-option
* obj-option
q '''
cmd
-h|--help > recieve help - print this info
--count: Number > count of stuff that is to be counted
--obj: {x: Number, y: Boolean, z: Object} > an object full of things and stuff
''', opts
optionator-0.8.2/test/tests.ls 000664 000000 000000 00000042133 12770561512 016371 0 ustar 00root root 000000 000000 optionator = require '..'
{strict-equal: equal, deep-equal, throws}:assert = require 'assert'
q = (args, options, more = {}, parse-options) ->
more <<< {options}
{parse} = optionator more
parse args, parse-options
eq = (expected-options, expected-positional, args, options, more, parse-options) ->
result = q args, options, more, parse-options
deep-equal result._, expected-positional
delete result._
deep-equal result, expected-options
suite 'misc' ->
test 'version' ->
equal optionator.VERSION, (require '../package.json').version
suite 'boolean flags' ->
opts =
* option: 'help'
alias: 'h'
type: 'Boolean'
* option: 'match'
alias: ['m', 't']
type: 'Boolean'
test 'long' ->
eq {help: true}, [], '--help', opts
test 'short' ->
eq {help: true}, [], '-h', opts
eq {match: true}, [], '-m', opts
eq {match: true}, [], '-m', opts
test 'short with --' ->
eq {help: true}, [], '--h', opts
test 'multiple' ->
eq {help: true, match: true}, [], '-hm', opts
test 'negative' ->
eq {match: false}, [], '--no-match', opts
test 'redefine' ->
eq {match: false}, [], '--match --no-match', opts
eq {match: true}, [], '--no-match --match', opts
test 'using = true' ->
eq {match: true}, [], '--match=true', opts
eq {match: true}, [], '--no-match=false', opts
test 'using = true, short' ->
eq {match: true}, [], '-m=true', opts
test 'using = negated' ->
eq {match: false}, [], '--match=false', opts
eq {match: false}, [], '--no-match=true', opts
test 'using = false, short' ->
eq {match: false}, [], '-m=false', opts
suite 'argument' ->
opts =
* option: 'context'
alias: 'C'
type: 'Number'
* option: 'name'
alias: 'A'
type: 'String'
* option: 'destroy'
alias: 'd'
type: 'Boolean'
test 'simple long' ->
eq {context: 2}, [], '--context 2', opts
test 'simple short' ->
eq {context: 2}, [], '-C 2', opts
test 'grouped short, when last' ->
eq {destroy: true, context: 2}, [], '-dC 2', opts
test 'multiple' ->
eq {context: 2, name: 'Arnie'}, [], '--context 2 --name Arnie', opts
test 'with boolean flag' ->
eq {context: 2, destroy: true}, [], '--destroy --context 2', opts
eq {context: 2, destroy: true}, [], '--context 2 --destroy', opts
test 'using =' ->
eq {context: 2}, [], '--context=2', opts
test 'using = complex' ->
eq {name: 'Arnie S'}, [], '--name="Arnie S"', opts
test 'using = no value' ->
throws (-> q '--context=', opts), /No value for 'context' specified/
test 'using = short' ->
eq {context: 2}, [], '-C=2', opts
eq {context: 2, destroy: true}, [], '-dC=2', opts
test 'using = short no value' ->
throws (-> q '-C=', opts), /No value for 'C' specified/
test 'value for prop required' ->
throws (-> q '--context', opts), /Value for 'context' of type 'Number' required./
throws (-> q '--context --destroy', opts), /Value for 'context' of type 'Number' required./
test 'can\'t set flag val when not last' ->
throws (-> q '-Cd 2', opts), /Can't set argument 'C' when not last flag in a group of short flags./
test 'no- prefix only on boolean options' ->
throws (-> q '--no-context 2', opts), /Only use 'no-' prefix for Boolean options, not with 'context'./
test 'redefine' ->
eq {context: 5}, [], '--context 4 --context 5', opts
eq {context: 5}, [], '-C 4 --context 5', opts
test 'invalid type' ->
throws (-> q '--ends 20-11', [option: 'ends', type: 'Date']), /expected type Date/
throws (-> q '--pair "true"', [option: 'pair', type: '(Boolean, Number)']), /expected type \(Boolean, Number\)/
throws (-> q '--pair "true, 2, hider"', [option: 'pair', type: '(Boolean, Number)']), /expected type \(Boolean, Number\)/
throws (-> q '--props "x:1,fake:yo"', [option: 'props', type: '{x:Number}']), /expected type {x:Number}/
suite 'enum' ->
enum-opt = [option: 'size', type: 'String', enum: <[ small medium large ]>]
test 'enum' ->
eq {size: 'medium'}, [], '--size medium', enum-opt
test 'invalid enum' ->
throws (-> q '--size Hello', enum-opt), /Option size: 'Hello' not one of small, medium, or large/
suite 'argument names' ->
opts =
* option: 'after-context'
type: 'Number'
* option: 'is-JSON'
type: 'Boolean'
* option: 'HiThere'
type: 'Boolean'
* option: 'context2'
type: 'Number'
test 'dash to camel' ->
eq {after-context: 99}, [], '--after-context 99', opts
eq {is-JSON: true}, [], '--is-JSON', opts
test 'preserve PascalCase' ->
eq {HiThere: true}, [], '--HiThere', opts
test 'numbers' ->
eq {context2: 1}, [], '--context2 1', opts
suite '-NUM' ->
test 'no -NUM option defined' ->
throws (-> q '-1', []), /No -NUM option defined./
test 'no aliases allowed' ->
throws (-> q '', [option: 'NUM', type: 'Number', alias: 'n']), /-NUM option can't have aliases/
suite 'number' ->
opts = [{option: 'NUM', type: 'Number'}]
test '0' ->
eq {NUM: 0}, [], '-0', opts
test '1' ->
eq {NUM: 1}, [], '-1', opts
test 'multi digit' ->
eq {NUM: 10}, [], '-10', opts
test 'float' ->
eq {NUM: 1.0}, [], '-1.0', opts
suite 'float' ->
opts = [{option: 'NUM', type: 'Float'}]
test 'float basic' ->
eq {NUM: 1.2}, [], '-1.2', opts
test 'float from int' ->
eq {NUM: 1.0}, [], '-1', opts
suite 'int' ->
opts = [{option: 'NUM', type: 'Int'}]
test 'int basic' ->
eq {NUM: 1}, [], '-1', opts
suite 'positional' ->
opts =
* option: 'flag'
alias: 'f'
type: 'Boolean'
* option: 'cc'
type: 'Number'
* option: 'help'
alias: 'h'
type: 'Boolean'
rest-positional: true
* option: 'help-two'
alias: 'H'
type: 'String'
rest-positional: true
test 'basic' ->
eq {}, ['boom'], 'boom', opts
test 'anywhere' ->
eq {flag: true, cc: 42}, ['boom', '2', 'hi'], 'boom --flag 2 --cc 42 hi', opts
test 'not anywhere' ->
eq {flag: true, cc: 42}, ['hi', '--xx'], '--flag --cc 42 hi --xx', opts, {positional-anywhere: false}
test '--' ->
eq {flag: true}, ['--flag', '2', 'boom'], '--flag -- --flag 2 boom', opts
test 'rest positional boolean' ->
eq {help: true}, ['--flag', '2', 'boom'], '--help --flag 2 boom', opts
test 'rest positional value' ->
eq {help-two: 'lalala'}, ['--flag', '2', 'boom'], '--help-two lalala --flag 2 boom', opts
test 'rest positional flags simple' ->
eq {help: true}, ['--flag', '2', 'boom'], '-h --flag 2 boom', opts
eq {help-two: 'lalala'}, ['--flag', '2', 'boom'], '-H lalala --flag 2 boom', opts
test 'rest positional flags grouped' ->
eq {help: true, flag: true}, ['--cc', '2', 'boom'], '-fh --cc 2 boom', opts
eq {help-two: 'lalala', flag: true}, ['--cc', '2', 'boom'], '-fH lalala --cc 2 boom', opts
test 'rest positional flags grouped complex' ->
eq {help: true}, ['f', '--cc', '2', 'boom'], '-hf --cc 2 boom', opts
suite 'defaults' ->
test 'basic' ->
opt = [option: 'go', type: 'String', default: 'boom']
eq {go: 'boom'}, [], '', opt
eq {go: 'haha'}, [], '--go haha', opt
test 'array' ->
opt = [option: 'list', type: 'Array', default: '1,2']
eq {list: [1,2]}, [], '', opt
eq {list: [8,9]}, [], '--list 8,9', opt
suite 'array/object input' ->
opts =
* option: 'el'
type: 'Number'
* option: 'hasta-la-vista'
alias: 'h'
type: 'String'
* option: 'is-JSON'
type: 'Boolean'
* option: 'test'
type: 'RegExp'
* option: 'HiThere'
type: 'Boolean'
* option: 'day'
type: 'Date'
* option: 'list'
alias: 'l'
type: '[Int]'
* option: 'pair'
type: '(Int,String)'
* option: 'map'
type: '{a:Int,b:Boolean}'
test 'array' ->
eq {el: 5}, [], ['node', 'cmd.js', '--el', '5'], opts
test 'object' ->
eq {el: 5}, [], {el: 5}, opts
test 'object set positional' ->
eq {el: 5}, ['haha'], {el: 5, _:['haha']}, opts
test 'object - camelCase keys' ->
eq {hasta-la-vista: 'baby'}, [], {hasta-la-vista: 'baby'}, opts
eq {is-JSON: true}, [], {is-JSON: true}, opts
test 'object - dashed-case keys' ->
eq {hasta-la-vista: 'baby'}, [], {'hasta-la-vista': 'baby'}, opts
test 'object - PascalCase keys' ->
eq {HiThere: true}, [], {HiThere: true}, opts
test 'object -aliases' ->
eq {hasta-la-vista: 'lala', list: [1,2,3]}, [], {h: 'lala', l: [1,2,3]}, opts
test 'regexp object' ->
eq {test: /I'll be back/g}, [], {test: /I'll be back/g}, opts
test 'date object' ->
eq {day: new Date '2011-11-11'}, [], {day: new Date '2011-11-11'}, opts
test 'array object' ->
eq {list: [1,2,3]}, [], {list: [1,2,3]}, opts
test 'tuple object' ->
eq {pair: [1, '52']}, [], {pair: [1, '52']}, opts
test 'object object' ->
eq {map: {a: 1, b: true}}, [], {map: {a: 1, b: true}}, opts
test 'invalid object' ->
throws (-> q {el: 'hi'}, opts), /Option 'el': Invalid type for 'hi' - expected type 'Number'/
suite 'slicing' ->
test 'string slice' ->
eq {b: 2}, ['c'], 'cmd -b 2 c', [{option: 'b', type: 'Number'}], , {slice: 3}
test 'array slice' ->
eq {b: 2}, ['c'], ['cmd' '-b' '2' 'c'], [{option: 'b', type: 'Number'}], , {slice: 1}
suite 'parse-argv' ->
test 'slices two' ->
{parse-argv} = optionator do
options: [
option: 'a'
type: 'Boolean'
]
o = parse-argv <[ a b c d -a ]>
deep-equal o._, <[ c d ]>
equal o.a, true
suite 'errors in defining options' ->
test 'no options defined' ->
throws (-> q ''), /No options defined/
test 'option already defined' ->
throws (-> q '', [option: 'opt', type: '*'; option: 'opt', type: '*']), /Option 'opt' already defined/
throws (-> q '', [option: 'opt', type: '*'; option: 'top', type: '*', alias: 'opt'])
, /Option 'opt' already defined/
test 'no type defined' ->
throws (-> q '', [option: 'opt']), /No type defined for option 'opt'./
test 'error parsing type' ->
throws (-> q '', [option: 'opt', type: '[Int']), /Option 'opt': Error parsing type '\[Int'/
test 'error parsing default value' ->
throws (-> q '', [option: 'opt', type: 'Number', default: 'hi'])
, /Option 'opt': Error parsing default value 'hi' for type 'Number':/
test 'error parsing enum value' ->
throws (-> q '', [option: 'opt', type: 'Number', enum: ['hi']])
, /Option 'opt': Error parsing enum value 'hi' for type 'Number':/
suite 'errors parsing options' ->
test 'invalid argument to parse' ->
throws (-> q 2, []), /Invalid argument to 'parse': 2./
test 'invalid option' ->
opts = [option: 'rake', type: 'Boolean'; option: 'kare', type: 'Boolean']
throws (-> q '--fake', opts), /Invalid option '--fake' - perhaps you meant '--rake'\?/
throws (-> q '--arket', opts), /Invalid option '--arket' - perhaps you meant '--rake'\?/
throws (-> q '-k', opts), /Invalid option '-k' - perhaps you meant '--rake'\?/
test 'invalid option - no additional help' ->
throws (-> q '--fake', []), /Invalid option '--fake'/
test 'is required' ->
opts = [option: 'req-opt', type: 'Boolean', required: true]
eq {reqOpt: true}, [], {+reqOpt}, opts
throws (-> q '', opts), /Option --req-opt is required/
test 'override required' ->
opts =
* option: 'req-opt'
type: 'Boolean'
required: true
* option: 'help'
type: 'Boolean'
override-required: true
throws (-> q '', opts), /Option --req-opt is required/
eq {help: true}, [], '--help', opts
test 'is mutually exclusive' ->
opts =
* option: 'aa-aa'
type: 'Boolean'
* option: 'bb'
type: 'Boolean'
* option: 'cc'
type: 'Boolean'
* option: 'dd'
type: 'Boolean'
* option: 'ee'
type: 'Boolean'
more =
mutually-exclusive:
<[ aa-aa bb ]>
[<[ bb cc ]> <[ dd ee ]>]
throws (-> q '--aa-aa --bb', opts, more), /The options --aa-aa and --bb are mutually exclusive - you cannot use them at the same time/
throws (-> q '--bb --ee', opts, more), /The options --bb and --ee are mutually exclusive - you cannot use them at the same time/
throws (-> q '--cc --dd', opts, more), /The options --cc and --dd are mutually exclusive - you cannot use them at the same time/
throws (-> q {aaAa: true, bb: true}, opts, more), /The options --aa-aa and --bb are mutually exclusive - you cannot use them at the same time/
suite 'concat repeated arrays' ->
opts =
* option: 'nums'
alias: 'n'
type: '[Number]'
* option: 'x'
type: 'Number'
more = {+concat-repeated-arrays}
test 'basic' ->
eq {nums: [1,2,3]}, [], '-n 1 -n 2 -n 3', opts, more
test 'overwrites non-array' ->
eq {x: 3}, [], '-x 1 -x 2 -x 3', opts, more
test 'per option' ->
opts =
* option: 'x'
type: '[Number]'
concat-repeated-arrays: true
* option: 'y'
type: '[Number]'
eq {x: [1, 2, 3]}, [], '-x 1 -x 2 -x 3', opts
eq {y: [3]}, [], '-y 1 -y 2 -y 3', opts
test 'using defaults' ->
opts =
* option: 'x'
type: '[Number]'
* option: 'y'
type: '[Number]'
concat-repeated-arrays: false
more =
defaults: {+concat-repeated-arrays}
eq {x: [1, 2, 3]}, [], '-x 1 -x 2 -x 3', opts, more
eq {y: [3]}, [], '-y 1 -y 2 -y 3', opts, more
test 'one value per flag' ->
opts =
* option: 'x'
type: '[String]'
concat-repeated-arrays: [true, {+one-value-per-flag}]
...
eq {x: ['a,b', 'c,d', 'e,f']}, [], '-x "a,b" -x "c,d" -x "e,f"', opts
test 'set with array, len is 1' ->
opts =
* option: 'x'
type: '[String]'
concat-repeated-arrays: [true]
...
eq {x: <[ a b c d ]>}, [], '-x "a,b" -x "c,d"', opts
test 'invalid setting' ->
opts =
* option: 'x'
type: '[String]'
concat-repeated-arrays: []
...
throws (-> q '', opts), /Invalid setting for concatRepeatedArrays/
suite 'merge repeated objects' ->
opts =
* option: 'config'
alias: 'c'
type: 'Object'
* option: 'x'
type: 'Number'
more = {+merge-repeated-objects}
test 'basic' ->
eq {config: {a: 1, b: 2, c: 3}}, [], '-c a:1 -c b:2 -c c:3', opts, more
test 'same properties' ->
eq {config: {a: 3}}, [], '-c a:1 -c a:2 -c a:3', opts, more
test 'multiple properties in one go' ->
eq {config: {a: 1, b: 2, c: 3, d: 4}}, [], '-c "a:1,b:2" -c "c: 3, d: 4"', opts, more
test 'overwrites non-array' ->
eq {x: 3}, [], '-x 1 -x 2 -x 3', opts, more
opts2 =
* option: 'c'
type: 'Object'
merge-repeated-objects: true
* option: 'd'
type: 'Object'
test 'per option' ->
eq {c: {a: 1, b: 2, c: 3}}, [], '-c a:1 -c b:2 -c c:3', opts2
eq {d: {c: 3}}, [], '-d a:1 -d b:2 -d c:3', opts2
suite 'dependency check' ->
opts =
* option: 'aa'
type: 'Boolean'
* option: 'bb'
type: 'Boolean'
depends-on: ['or', 'aa', 'dd']
* option: 'cc'
type: 'Boolean'
depends-on: ['and', 'aa', 'dd']
* option: 'dd'
type: 'Boolean'
* option: 'ff'
type: 'Boolean'
depends-on: 'aa'
* option: 'gg'
type: 'Boolean'
depends-on: ['aa']
test '"and" should pass' ->
eq {+cc, +aa, +dd}, [], '--cc --aa --dd', opts
test '"and" should fail' ->
throws (-> q '--cc', opts), /The option 'cc' did not have its dependencies met/
throws (-> q '--cc --aa', opts), /The option 'cc' did not have its dependencies met/
throws (-> q '--cc --dd', opts), /The option 'cc' did not have its dependencies met/
test '"or" should pass' ->
eq {+bb, +aa}, [], '--bb --aa', opts
eq {+bb, +dd}, [], '--bb --dd', opts
test '"or" should fail' ->
throws (-> q '--bb', opts), /The option 'bb' did not meet any of its dependencies/
test 'single dependency, as string' ->
eq {+ff, +aa}, [], '--ff --aa', opts
test 'single dependency, in array' ->
eq {+gg, +aa}, [], '--gg --aa', opts
test 'just "and"' ->
opts = [
option: 'xx'
type: 'Boolean'
depends-on: ['and']
]
eq {+xx}, [], '--xx', opts
test 'empty array' ->
opts = [
option: 'xx'
type: 'Boolean'
depends-on: []
]
eq {+xx}, [], '--xx', opts
test 'not using "and" or "or"' ->
opts = [
option: 'fail'
type: 'Boolean'
depends-on: ['blerg', 'grr']
]
throws (-> q '--fail', opts), /Option 'fail': If you have more than one dependency, you must specify either 'and' or 'or'/
suite 'option defaults' ->
opts =
* option: 'a'
* option: 'b'
more =
defaults:
type: 'Number'
test 'basic' ->
eq {a: 5}, [], '-a 5', opts, more
eq {b: 5}, [], '-b 5', opts, more
suite 'heading' ->
opts =
* option: 'aaa'
type: 'Number'
* heading: 'mooo'
* option: 'bbb'
type: 'String'
* option: 'ccc'
type: 'Boolean'
test 'basic' ->
eq {aaa: 5}, [], '--aaa 5', opts
eq {bbb: 'hi'}, [], '--bbb hi', opts
eq {ccc: true}, [], '--ccc', opts
suite 'type-aliases' ->
opts =
* option: 'x'
type: 'Path'
* option: 'y'
type: 'Rules'
type-aliases =
Path: 'String'
Rules: '[String]'
test 'basic' ->
eq {x: 'a', y: <[ c d ]>}, [], '-x a -y c,d', opts, {type-aliases}