mixa
v0.7.0
Published
Fast and simple command line argument parsing with subcommands
Downloads
14
Maintainers
Readme
M.I.X.A.
Table of Contents generated with DocToc
- M.I.X.A.
M.I.X.A.
- M.I.X.A. is short for Meta—Internal/eXternal command—Arguments
- this is the generalized command line structure
- flags, a.k.a. 'options', 'settings', Boolean or not:
- Boolean flags have no srgument and are
true
when given - flags of other types must be given a value as
-f value
,--flag value
, optionally with an equals sign between the flag name and the value
- Boolean flags have no srgument and are
- Internal commands must parse their specific flags and other arguments.
- External commands call a child process that is passed the remaing command line arguments, so those can be dealt with summarily.
M.I.X.A. for Command Line Argument Parsing
Command Line Structure
node cli.js │ --cd=some/other/place funge --verbose=true -gh 'foo'
│ │ │ │ │ │ │ │ │ └────────────┘ └─┘ └───┘
│ │ │ │ │ │ │ │ │ L S P
└──┘ └────┘ │ └───────────────────┘ └───┘ └──────────────────────┘
E S │ M I/X A
- E—executable
- S—script
- M—meta flags (flags that pertain to MIXA)
- I/X—internal or external command
- A—arguments (flags that pertain to the command)
- L—long flag
verbose
with value - S—short Boolean flags
g
,h
- P—positional flag
A valid command line must either call for printing a application-specific help (using one of ... -h
, ...
--help
, or ... help
), or application version (using one of ... -v
, ... --version
, or ... version
),
or else spell out a configured application-specific command (including additional arguments where required).
Metaflags
Metaflags are command line arguments preceded by one (short form) or two (long form) dashes that are placed before the command line like so:
node cli.js --help # show help and exit
node cli.js -h # dto.
node cli.js --version # show version and exit
node cli.js -v # dto.
node cli.js --cd some/other/place/somewhere foo 42 # change into directory given, then run `foo 42`
node cli.js -d some/other/place/somewhere foo 42 # dto.
The above are the preconfigured metaflags, but one can define additional ones in the Job Definition
(<jobdef>
).
Job Definition (<jobdef>
)
A Job definition (an object of type <jobdef>
) specifies declaratively what additional metaflags and
commands are available for the application in question. A Job definition may contain the following fields
(question mark indicates optional value, angle brackets specifiy types; the default is given behind the
equals sign):
?exit_on_error <boolean> = true
—When callingMIXA.run jobdef, input
, determines whether to exit with an exit code in case an error in the jobdef or the input was detected.exit_on_error
does not affect the behavior ofMIXA.parse jobdef, input
.?meta <mixa_flagdefs>
—An object that specifies (additional) metaflags to go before the command. See Flag Definitions (<flagdefs>
,<flagdef>
)?commands <mixa_cmddefs>
—See Command Definitions (<cmddefs>
,<cmddef>
).
Example:
jobdef =
exit_on_error: true
commands:
foo: { ... definition for command `foo` ... }
bar: { ... definition for command `bar` ... }
This job definition declares that there two commands foo
and bar
in the application.
Command Definitions (<cmddefs>
, <cmddef>
)
The keys of a cmddefs
object are interpreted as command names; its values are <cmddef>
objects:
?description <text>
—A helpful one-liner to explain what the command does.?allow_extra <boolean> = false
—Whether to allow unspecified arguments on the command line; these will be made available asverdict.extra
.?flags <mixa_flagdefs>
—An object detailing each command line argument to the command in question.?runner <_mixa_runnable>
—A synchronous or asynchronous function to be called byMIXA.run jobdef, input
provided no error occured during validation ofjobdef
and parsing theinput
?plus <any>
—Any additional value or values that should be made accessible to the runner asverdict.plus
.
Example (cont'd from above):
# file: cli.coffee
MIXA = require 'mixa'
jobdef =
exit_on_error: true
commands:
foo:
description: "Do something awesome"
allow_extra: true # allow unconfigured extra arguments
runner: run_foo # function to be called
listfiles:
runner: MIXA.runners.execSync # call convenience function for sync sub-process
plus: { executable: 'ls', } # `execSync` will use `plus.executable` as name of executable
allow_extra: true # `true` b/c we want to pass arguments to `ls`
console.log MIXA.run jobdef, process.argv
We can now call node cli.js --cd=somewhere listfiles -- -AlF
from the command line to execute ls -AlF
and get back whatever that outputs.
Flag Definitions (<flagdefs>
, <flagdef>
)
The keys of a flagdefs
object are interpreted as long flag names; its values are <flagdef>
objects:
multiple <_mixa_mutliple>
—false
,'greedy'
,'lazy'
; defaults tofalse
; if'greedy'
, multiple values may be set without repeating the flag name; if'lazy'
, flag name must be repeated for each value. Ensuing named values are honored in either case.fallback <any>
—Used when flag is missing; note that when flag is mentioned without a value, then valuenone
will be assignedpositional <boolean>
—true
orfalse
(translated todefaultOption
), indicates whether unnamed argument id allowed; interacts withallow_extra
; only at most one flag can be markedpositional
Command Line Parsing: Example
parse jobdef, process.argv
will return object withjobdef
—Thejobdef
that describes how to parse command line arguments into a command with flags (jobflags and metaflags)input
—Theargv
used as input, unchangedverdict
—The result of parsing the inputargv
against thejobdef
; this is either a 'happy' result or, if an error was detected, a 'sad' result with indicators what the problem was.- A happy verdict will have the following attributes:
cmd
—The matching command;argv
—Remaining arguments, if any;parameters
—Object with the named flags and their values;plus
—Theplus
attribute from the matching jobdef's command definition, if any;runner
—Therunner
attribute from the matching jobdef's command definition, if any.
- A sad verdict will have the following attributes:
cmd
—Invariably set tohelp
, indicating that a helpful message should be displayed;error
:tag
—A short upper case textual code that identifies the error classcode
—An integer error code in the range[ 1 .. 127 ]
message
—An error message
- A happy verdict will have the following attributes:
output
:—What the runner returnedok
:—The value of the computation, if any, depending on the runner callederror
:—Details in case a runner encountered problems computing anok
value; as above, will have fieldstag
,code
,message
where present
upcoming
Passing Options to Other Programs
- Sometimes one wants to pass options to another executable; e.g. say we want to use
node x.js search -iname 'whatever'
to call the Linuxfind
command in a subprocess and we wish to not analyze any options but just pass them through to the subprocess - Note that the
find
utility uses long options with a single hyphen; problem is that the parser currently used bei MIXA will misinterpretiname
as[ '-i', '-n', '-a', '-m', '-e', ]
- As a workaround, use
--
(a.k.a. 'the inhibitor') somewhere after the command (search
in this example) but before before the first single-dash flag to be left as-is (-iname
in this case), that is, call your program asnode x.js search -- -iname 'whatever'
. - A future version of MIXA might not require using
--
. - Observe that
allow_extra
should be set totrue
to allow extra arguments; otherwise, anEXTRA_FLAGS
error will be generated.
Passing Options to Run Methods
- set
runner
in the command definition to a synchronous or asynchronous function that will be called with the object that describes the parsing result; this will be called the 'run method' or the 'runner' - set
plus
in the command definition to any value to be attached to the result object - the value of
plus
is specific to the run method chosen
M.I.X.A. for TOML Configuration File Parsing
NOTE This functionality is pre-alpha; details are likely to change in upcoming releases
Configuration Sources
In the following list, later entries win over earlier ones; this is the same principle that CSS and
Object.assign()
use.
- a file named
.$app_name.toml
in the user's home directory; - a file named
.$app_name.toml
in the directory of the parent project (i.e. the containing directory of thepackage.json
file for the package that hasmixa
as direct dependency); - other file(s) identified by one or more paths passed to
read_cfg()
(from the command line); - other settings passed to
read_cfg()
(from the command line).
M.I.X.A. for Checking Pinned Versions of Dependencies
(forthcoming)
- Motivation
- Spec-ulation Keynote - Rich Hickey
- HN Discussion
- another HN Discussion
- devs like to forget they chose a particular version because of a particular feature that got removed or changed in a later version of a given dependency
- one example would be the now-popular move to ES Modules ('
import
') when your own package still used CJS Modules ('require
') - no way in
package.json
to indicate reason for a particular version choice: was it the latest available at the time? would the software work with a newer version or is it known not to? - tools such as
npm-check
always suggest to install the latest version which may not always be the right version for the project at hand
- How it works
- can configure SemVer matchers in file
pinned-package-versions.json
which is not touched by tools - add
require( 'mixa/lib/pinned-package-versions.json' )
close to your package entry point (index.js
,main.js
)
- can configure SemVer matchers in file
Also See
To Do
[–] consider whether to replace
positional
flag configuration with a single option in command configuration[–] implement mandatory flags
[–] consider moving from
command-line-args
tosade
[–] consider to offer sub-contexts as done by https://github.com/substack/subarg
[+/–] implement 'default_command'
- [+] must match existing named command
- [–] cannot have positional flags
[–] implement parsing of TOML configuration files
[–] make checks for pinned dependency versions part of public API
[+] in
check-package-versions.coffee
, handle missing dependency gracefully[–] rename
MIXA.configurator
->MIXA.cfg
[–] implement
defaults
,overrides
forMIXA.cfg
[–] update all dependencies
[–] update all dependency
intertype
, then integrate MIXA flag type declaration withintertype
[–] allow parameters use both hypen-minus
-
and underscores_
transparently[–] unify {
cmd
,command
,cmds
,commands
, } -> {cmd
,cmds
, }[–] do not use runners, restrict functionality to extract (and normalize) command and parameters (i.e. 'flags')
[–] rename parameters -> flags
[–] accept an optional
types
(anIntertype
instance); naming convention is to prefix flag names withcli_
(orflag_
?); can then set thetype
declaration inflags
section to any of these:- [–] nothing (or
null
): to be filled out using the conventional prefix, the flag name, and thetypes
argument - [–] any value that is legal for an InterType type declaration, including:
- [–] the name of a type that exists in
types
- [–] a test function
- [–] a valid InterType type declaration object
- [–] the name of a type that exists in
- [–] in any event, enforce at declaration time that
types.create[ type ].create()
can be meaningfully called; this may entail an update tointertype
to avoid non-sensical auto-generatedcreate
methods
- [–] nothing (or
Is Done
- [+] in case of error, should still deliver all flags with values in
verdict.parameters
an attributeverdict.extra_flags
will contain a list of extraneous flags