Package Exports
- neodoc
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (neodoc) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
<neodoc>
Beautiful, handcrafted command line interfacesAbout | Installation | Usage | Goals | Status | Contributing | License | Playground new
About
<neodoc> is a revised implementation of the docopt language for node. In brief, it offers a unique way to author command lines by writing the command line's help text first and then deriving a matching parser from it, which can then be applied to user input. The advantages are numerous:
- No boilerplate
- Full control over beautiful, hand-crafted help texts
- Documentation comes first - hence your users come first
- Documentation is always right - your help-text is necessarily correct
- Version-controlled help-text - the help-text becomes a regular part of your codebase
This implementation features error reporting, both for users and developers, reading values from environment variables, type coercion and much more. For an (in-)comprehensive comparison to the original, click here. To take neodoc for a ride, click here.
Features
- Derive command line interface from help text
- Helpful error messages for developer and user
- Options-first parsing to compose large programs (see example below)
- Fallback to alternate values:
- User input -> Environment -> Defaults
- Convenient, concise and widely accepted POSIX-style syntax
-f[=ARG], --foo[=ARG]
options<arg>
,ARG
positionalsclone
,pull
, etc. commands[<arg>]
optional groupings(<arg>])
required groupings[-f ARG]
POSIX-style flags-f[=ARG]...
repeating elements- 99% compatible with a typical
git <command> --help
output - A full overview of the language is still pending, but things should be intuitive enough to be figured out. To test your intuition, check out the playground. Please do submit an issue if you find something surprising or counter-intuitive.
Installation
npm install --save neodoc
Usage
neodoc.run(helpText, opts)
Parse and apply the given docopt help text. If no options are provided, apply
it to process.argv
and process.env
. The result is a mapping of key -> value,
where the key is the canonical form of the option and its alias, if available.
Options:
opts.env
- Overrideprocess.env
opts.argv
- Overrideprocess.argv
opts.optionsFirst
- Parse until the firstcommand
or<positional>
argument, then collect the rest into an array, given the help indicates another, repeatable, positional argument, e.g. :[options] <ommand> [<args>...]
opts.smartOptions
- Enable parsing groups that "look like" options as options. For example:[-f ARG...]
means[-f=ARG...]
opts.stopAt
- Stop parsing at the given options, i.e.[ -n ]
. It's value will be the rest of argv.
neodoc.parse(helpText, opts)
Parse the docopt specification. This is the canonical representation of the CLI as described by it's help text and can be used for building parsers etc.
Options:
opts.smartOptions
- Enable parsing groups that "look like" options as options. For example:[-f ARG...]
means[-f=ARG...]
Example
For more examples, check out the 'examples' folder. To see the git example in action, run:
node ./examples/git
#!/usr/bin/env node
const neodoc = require('neodoc');
const args = neodoc.run(`
usage: git [--version] [--help] [-C <path>] [-c <name=value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>...]
`, { optionsFirst: true, smartOptions: true });
if (args['<command>'] === 'remote') {
const remoteArgs = neodoc.run(`
usage:
git remote [-v | --verbose]
git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags]
[--mirror=<fetch|push>] <name> <url>
git remote rename <old> <new>
git remote remove <name>
git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
git remote set-branches [--add] <name> <branch>...
git remote set-url [--push] <name> <newurl> [<oldurl>]
git remote set-url --add [--push] <name> <newurl>
git remote set-url --delete [--push] <name> <url>
git remote [-v | --verbose] show [-n] <name>...
git remote prune [-n | --dry-run] <name>...
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
`, { argv: ['remote'].concat(args['<args>']), smartOptions: true })
// ...
} else { /* ... */ }
Project goals
Overview of the short- and long-term goals of this project
- Provide a declarative way to author command lines. Rather than deriving the help text from some EDSL, derive the CLI from a human readable, prose-like help text, located e.g. in the project's README. This guarantees documentation and implementation never diverge, because they simply can't.
- Provide very good error reporting for users of the CLI and at least decent error reporting for developers authoring the docopt text.
- The manually crafted docopt text must be readable, standardised, yet flexible and powerful enough to handle a fair set of use-cases.
- A solid interface for use in regular javascript. Purescript should merely be an implementation detail.
- Solid test coverage
- Sensible compatibility with original docopt.
- POSIX compatibility
Deviations from the original
This implementation tries to be compatible where sensible, but does cut ties when it comes down to it. The universal docopt test suite has been adjusted accordingly.
Better Error reporting. Let the user of your utility know why his input was rejected.
Optional arguments. Neodoc understands
--foo[=BAR]
(or-f[=<bar>]
) as an option that can be provided either with or without an argument.Alias matches. If
--verbose
yields a value, so will-v
(given that's the assigned alias). Likewise,FOO
yields value<foo>
as well asFOO
, and<foo>
yieldsFOO
and<foo>
.Flags are optional, always. There's no reason to force the user to explicitly pass an option that takes no argument. The absence of the flag speaks - the flag will be set to
false
. This is also the case for flags inside required groups. E.g.: The group(-a -b)
will match inputs-a -b
,-ab
,-ba
,-b -a
,-b
,-a
and the empty input.All arguments in a group are always required. This is regardless of whether or not the group itself is required or not - once you start matching into the group, elements in the group become required for the match to succeed. Consider:
Usage: prog [<name> <type>]
will fail
prog foo
, but passprog foo bar
. The rationale being that this is more general, since if the opposite behaviour (any match) was desired, it could be expressed as such:Usage: prog [[<name>] [<type>]]
note: this rule excludes flags/switches and options that have default values (or other fallback values).
No abbreviations:
--ver
does not match--verbose
. (mis-feature in the original implementation)There is no
null
in the resulting value map.null
simply means not matched - so the key is omitted from the resulting value map. (this is still under consideration)Smart-options. Options can be inferred from groups that "look like" options:
Usage: foo [-f FILE]
would then expand toUsage: foo [-f=FILE]
Environment variables. Options can fall back to environment variables, if they are not explicitly defined. The order of evaluation is:
- User input (per
process.argv
) - Environment variables (per
[env: ...]
tag) - Option defaults (per
[default: ...]
tag)
- User input (per
Project status
Neodoc is fully usable and well-tested. If you find there is something broken or unintuitive about neodoc, please do open an issue. The following gives on overview of where neodoc is headed.
Beta Target (complete)
- Scan the docopt text for usage sections and 0 or more description sections
- Lex and parse usage sections
- Lex and parse description sections
- Resolve ambiguities by combining the parsed usage and description sections
- Generate parser from the solved program specification
- Lex and parse user input on the CLI
- Transform the parsed args into something more useful
- Run against docopt test-suite 99% done
- Provide developer and user error reporting
- Provide seamless interface to be called from JS
- Read arguments from env vars
- Implement special arguments
-
--
(end of args) -
-
(stdin) -
[options]
-
Post-Beta Roadmap
Overview of where neodoc is headed, ordered (somewhat) by estimated priority.
- Implement "options-first"
- Improve scanner speed
- POSIX compatibility (opt-in/out)
- "Options-first" on by default (guideline 9)
- Required option arguments be separate from the option:
-f BAR
- Update notation in usage syntax (disallow no spaces)
- Optional option arguments be the same argument as the option:
-fBAR
- Update notation in usage syntax (disallow spaces)
- Denote options in singleton groups:
[-f BAR]
means[-f=BAR]
(no option description required) - Allow passing negative numbers as option arguments
- Implement guideline 8 (also see 11)
- Improve error messages
- Improve error messages when matching free groups / options (be more specific than "Trailing input")
- Implement optional option arguments:
-a [=foo]
. - Implement
--help
and--version
. The developer will be able to specify the option that will trigger the--help
and--version
convenience functions, with fallbacks to--help
and--version
.- Implement
--help
. If matched, print the docopt text. - Implement
--version
. If matched, print the npm package version.
- Implement
- Read options from config file
- Allow for
--foo[=<bar>]
syntax (git style). - Auto-infer types when not specified (e.g. numbers, strings, booleans)
- Implement
[-f BAR]
flag syntax ("smart-options") - Fix all purescript (pscid) warnings
Wishlist
A list of things that are desired, but have not found a place on the roadmap.
- Provide typescript typings
- Read options from config file
- Add ranges support:
-[0-9]
would expand to-0123456789
(or-1
,-2
...). For a usecase, see here - Read options from prompt (Add a
[prompt]
tag) - Syntax plugins (vim, ...)
- Put the "source" of a parsed option's value into the output, e.g. "env", "default", "user"
- Provide warnings
Contributing
Contributions are highly encouraged and welcome. Bug fixes and clean ups do no need much coordination, but if you're interested in contributing a feature, contact me at felixschlitter@gmail.com and we can get the ball rolling — There's plenty to do. To get up and running, run:
npm i && bower i && npm test
NB: Purescript is declared a devDependency in package.json, so no need to bring
your own. Also, during development, npm run watch
is extremely useful to get
immediate feedback.
Implementation overview
A quick overview of the implementation for potential contributors
The project can roughly be broken up into several distinct areas of work:
- Scanning the docopt text:
- Derive at least 1 usage section
- Derive 0 or more description sections
- Parsing the Specification
- Lex and parse the usage section
- Lex and parse any description sections
- Solve the parsed Specification into its canonical, unambigious form
- Generate a parser from the Specification
- Lex and parse the user input
- Lex and parse the user input
- Transform into a usable form
License
<neodoc> is released under the MIT LICENSE.
See file LICENSE
for a more detailed description of its terms.