JSPM

  • Created
  • Published
  • Downloads 7622
  • Score
    100M100P100Q128417F
  • License LicenseRef-LICENSE

Docopt implementation for node

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

⚠️ This project is nearing completion. It is not yet on npm, and a last few wrinkles are being ironed out. It will arrive shortly.

< neodoc >

Build Status

About | Installation | Usage | Goals | Status | Contributing | License


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:

  • Documentation and implementation are always in sync
  • Great discoverability for the user
  • Provokes thinking about the user first
  • No awkward, unreadable EDSL

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.

Installation

npm install --save neodoc

Usage

Basic usage example. For more detail on what neodoc can do, have a look at testcases.docopt in the repo.

  1. Given this node.js program:

    #!/usr/bin/env node
    
    import neodoc from 'neodoc';
    const argv = neodoc.run(`
      Naval Fate.
    
      Usage:
        naval_fate ship new <name>...
        naval_fate ship <name> move <x> <y> [--speed=<kn>]
        naval_fate ship shoot <x> <y>
        naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
        naval_fate -h | --help
        naval_fate --version
    
      Options:
        -h --help     Show this screen.
        --version     Show version.
        --speed=<kn>  Speed in knots [default: 10].
        --moored      Moored (anchored) mine.
        --drifting    Drifting mine.
    `);
    
    console.log(argv);
  2. We can provide various input:

    $ ./prog ship new foo bar baz
      {'<name>': ['foo', 'bar', 'baz'],
       'NAME': ['foo', 'bar', 'baz'],
       'new': true,
       'ship': true}
    
    $ ./prog ship foo move 10 10 --speed
      Naval Fate.
    
      Usage:
        naval_fate ship new <name>...
        naval_fate ship <name> move <x> <y> [--speed=<kn>]
        naval_fate ship shoot <x> <y>
        naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
        naval_fate -h | --help
        naval_fate --version
    
      Options:
        -h --help     Show this screen.
        --version     Show version.
        --speed=<kn>  Speed in knots [default: 10].
        --moored      Moored (anchored) mine.
        --drifting    Drifting mine.
    
      Trailing input: --speed:
      > ship foo move 10 10 --speed
                            ^^^^^^^

Project goals

Overview of the short- and longterm 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.

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.

  • No abbreviations: --ver does not match --verbose. (mis-feature in the original implementation)

  • Alias matches. If --verbose yields a value, so will -v (given that's the assigned alias). Likewise, FOO yields value <foo> as well as FOO, and <foo> yields FOO and <foo>.

  • Flags are optional, always. There's no reason to force the user to explicitely 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, i.e.:

    Usage: prog [<name> <type>]

    will fail prog foo, but pass prog foo bar. The rational 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).

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

  • Environment variables. Options can fall back to environment variables, if they are not explicitely defined. The order of evaluation is:

    1. User input (per process.argv)
    2. Environment variables (per [env: ...] tag)
    3. Option defaults (per [default: ...] tag)
  • Program usage lines can span multiple lines:

    Usage: prog
           prog <a> <b>
           prog --input-file --output-file
                --mute

Project status

Beta Target

The work to be done to release a usable product with some known caveats. This release will serve as way to collect feedback and plan improvements for subsequent releases based on the feedback.

  • 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) not yet implemented
    • - (stdin)
    • [options]

Post-Beta Roadmap

Overview of where docopt is headed, ordered (somewhat) by estimated priority.

  • 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.
  • Read options from config file
  • Allow for --foo[=<bar>] syntax (git style).
  • Add type validation via tags, e.g.: [type: string]
  • Auto-infer types when not specified (e.g. numbers, strings, booleans)
  • Allow flag negation sintax --[no-]foo: --foo, --no-foo, -f, +f

Wishlist

A list of things that are desired, but have not found a place on the roadmap.

  • Fix all purescript warnings
  • Custom validations (see [type: ...] tag)
  • Provide typescript typings
  • Read options from config file
  • 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"
  • Consider +o syntax: -o, --option and it's negation: --no-option or +o.
  • Make commands first class citizens, enabling easy subcommands, inheriting options and all that.
  • Provide warnings. This would mean a largish refactor to use either a custom monad, or the Writer monad, stacked on top of the Either monad.
  • Allow boolean negation. Let --foo be an option of type boolean, implicitly allow it's negation --no-foo.
  • Refactor Language.Docopt.ParserGen.Parser.genBranchParser to use manual recursive iteration, rather than a fold, like in Language.Docopt.Solver.
  • Rewrite recursive functions to be in tail position, using purescript-tailrec and consider trampolining using purescript-free.

Contributing

Contributions are highly encouraged and welcome. Bug fixes and clean ups need not 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.

License

<neodoc> is released under the MIT LICENSE. See file LICENSE for a more detailed description of it's terms.


Implementation overview

A quick overview of the implementation for potential contributors

The project can roughly be broken up into 4 distinct areas of work:

  1. Scanning the docopt text:
    1. Derive at least 1 usage section
    2. Derive 0 or more description sections
  2. Parsing the Specification
    1. Lex and parse the usage section
    2. Lex and parse any description sections
  3. Solve the parsed Specification into it's canonical, unambigious form
  4. Generate a parser from the Specification
  5. Lex and parse the user input
    1. Lex and parse the user input
    2. Transform into a usable form

Dev Notes

Reading options from config files

Options should fall back values read from file.

Options:

  • Can this be a developer-provided file or lookup rather than on the command line?
  • Associate some option or positional argument with a config file lookup. This would end in some "special" code, where the parser is run twice (first, prove the associated option / positional arg was parsed, ignoring any failures for the time being, then run again with the config file default values).

Ideas for a future past the initial relase

  • Write syntax plugin for vim
  • First class commands description sections (and [options...])
    • Consider the following:
      Usage:
          git branch [options...] [branch-options...]
          #            ^                ^
          #            |                |
          #            |                `- Denotes common options
          #            |
          #            `- denotes options specific to `branch` command
      
      Common Options: # <-- Identify as match for `options` by substring
        -h, --help  Show this help and exit
      
      Branch Options: # <-- Identify as match for `branch-options` by substring
        -D <name>  Delete the branch identified by <name>
    • Or the following (multiple usage):
      Usage:
          git branch
      
      Git-branch usage: # <-- Identify as match for `branch` by substring
          git branch [options...] [-D]
      
      Git-branch options: # <-- Identify as match for `branch` by substring
          -D <name>  Delete the branch identified by <name>
      This approach is slightly more verbose, but might be easier for normalization for large projects across different files?