Minicle is a small helper library for CLI Node programs. Its main purpose is to parse commandline arguments -- including Git-style subcommands -- with a minimum of fuss and a maximum of flexibility. There are certainly a few edge cases it does not handle, but it should be adequate for the vast majority of programs.
Other features are mainly cosmetic conveniences:
- Runtime header output
- Convenient BBS-style markup for ANSI colors
- A generic but colorful error message template
There are scads of CLI argument processors out there, so why another one? Mostly
because the others aim to do too much and can be a pain to use when you just
need something quick and simple, or worse, when you need to do something
complicated that the original programmer didn't anticipate. All parseCliArgs
does is parse CLI options. It doesn't handle exotic edge cases, validate
arguments, or anything else.
What it does:
- Git-style subcommands
- Short (
-h
) and long (--help
) options - Optional limited number of arguments per option
- Flags/switches implemented by limiting an option's argument count to zero
- Clustering of short options, e.g.
-abc
instead of-a -b -c
- GNU-style double-dash (
--
) to indicate the end of options
Let's take a simple case, a program that takes three options: a flag that takes no arguments, an option that takes up to two arguments, and a number of general arguments not attached to an option.
const minicle = require("minicle");
var options = {
switches: {
able: { short: "a", maxArgs: 0 }, // flag, takes no args
baker: { short: "b", maxArgs: 2 }, // has up to 2 args
}
};
var result = minicle.parseCliArgs(process.argv.slice(2), options);
And that's it. The switches
element contains the long options as keys, and
the values contain the equivalent short options and, optionally, the maximum
number of arguments they accept. One important thing to note here is that,
unlike the 1.x.x versions, you have to explicitly pass the argument array to
parseCliArgs
, which is usually process.argv.slice(2)
.
Then if you run the program
./myprog.js -a --baker foo bar baz quux
you get this back:
{
command: null,
switches: {
able: { cnt: 1, args: [] },
baker: { cnt: 1, args: [ 'foo', 'bar' ] }
},
errcode: null,
errmsg: null,
general: [ 'baz', 'quux' ]
}
Since there were no errors and we're not using subcommands yet, all we care
about here are the switches
and general
elements. Each switch is identified
by its long name and the associated value contains a cnt
element indicating
how many times it appeared, and an args
element which is an array containing
any arguments that were passed along with it.
Because we configured baker
to accept two arguments at most, foo
and bar
in this case, the remaining arguments all get dumped into the general
array.
Now if we want to get a little more complicated and use git-style subcommands,
we just add the command names into the options object as an array named commands
,
and put the command names and their associated switches into the switches
object:
{
commands: [ "first", "second" ],
switches: {
first: {
able: { short: "a", maxArgs: 0 },
baker: { short: "b", maxArgs: 2 },
},
second: {
charlie: { short: "c", maxArgs: 1 },
delta: { short: "d", maxArgs: 3 },
}
}
}
Now we can type something like
./myprog.js second --charlie foo -d bar baz quux
and end up with this:
{
command: 'second',
switches: {
charlie: { cnt: 1, args: [ 'foo' ] },
delta: { cnt: 1, args: [ 'bar', 'baz', 'quux' ] }
},
errcode: null,
errmsg: null,
general: []
}
The rest Minicle are functions to handle terminal text, mostly with gratuitous ANSI colors. Some quick examples:
The errmsg
function produces colored error messages. It has its own default
set of error levels -- fatal
, warn
, info
, and debug
-- and associated
colors, but you can substitute your own.
The ansiMarkup
function simulates the ancient DOS-era BBS color codes as a
more convenient way of apply ANSI colors without a bunch of separate, nested
function calls. There are also convenience functions like ansiSubstr
for
manipulating strings with embedded color codes.
There are also a number of functions for manipulating fixed-pitch type. This has relatively little use on the web, but can be helpful for console output and code generation. All of them can handle embedded color codes as well.
Patterned loosely after the ANSI markup codes from the venerable Wildcat BBS
from the DOS era, ansiMarkup()
takes some text and applies ANSI color codes to
it, returning the results. Markup consists of a two-digit hexadecimal number
bracketed by at-signs, e.g., @0C@
is bright red text on a black background.
The first digit encodes the background color and the second the foreground. The
values for both are as follows:
Code | Color |
---|---|
0 | Black |
1 | Blue |
2 | Green |
3 | Cyan |
4 | Red |
5 | Magenta |
6 | Yellow/brown |
7 | White |
8 | Bright black (grey) |
9 | Bright blue |
A | Bright green |
B | Bright cyan |
C | Bright red |
D | Bright magenta |
E | Bright yellow |
F | Bright white |
X | no change from the previous code. |
For example
console.log(
minicle.ansiMarkup("This text features both @0B@foreground@07@ and @2E@background@07@ colors.")
);
will produce
Essentially the same as String.substr
, except that it knows about ansiMarkup
codes and handles them gracefully. Takes text
and returns the substring
starting at offset
and running for length
characters with all color codes
intact.
Outputs a colorful error message if level
is equal to or less than options.verbosity
which
takes the form
[level] message (location)
If location
is null
, it will be omitted from output. By default, output is
via console.log
, but another function may be substituted by setting
options.output
.
Also by default, there are four error levels named "fatal"
, "warn"
,
"info"
, and "debug"
, and if "fatal"
is given, the program will terminate
by calling process.exit(1)
. To alter or replace the defaults, pass your own
definitions in options.levels
. The defaults look like this:
const errorLevels = [
{ name: "fatal", levelColor: "@CE@", messageColor: "@CF@", locationColor: "@0E@", quit: true },
{ name: "warn", levelColor: "@E0@", messageColor: "@0E@", locationColor: "@06@", quit: false },
{ name: "info", levelColor: "@2F@", messageColor: "@0A@", locationColor: "@02@", quit: false },
{ name: "debug", levelColor: "@8F@", messageColor: "@07@", locationColor: "@0F@", quit: false },
];
The various colors are specified using the color codes from ansiMarkup
.
This is a simplified high-level wrapper around textBox
for the special case of
generating a colorful one-line text box to serve as a header for the output of a
command line program.
argument | description |
---|---|
text | The text to be displayed, which may include ANSI markup codes. This must be a single line, and will be truncated if necessary to fit within the box width . |
style | One of "basic" , "ascii" , "pcdos1" , or "pcdos2" |
boxColor | The ANSI markup code to apply to the box outline, or null if none. |
width | The width of the box, including the box outline. Defaults to 76 . |
The result is automatically sent to console.log()
. For example
minicle.outputHeader("@0E@MyProg v1.2.0@07@ -- @0B@Processing for something made easy@07@", "pcdos2", "@0C@");
will produce
Takes a string and wraps and aligns it, returning the result as an array of lines.
option | description |
---|---|
width | The width of the paragraph. Defaults is '76'. |
indent | Number of characters to indent the first line. Default is '0'. |
align | May be "left" , "right" , "center" , or "full" , and defaults to "left" . |
ignoreAnsiMarkup | If true , ignore ANSI markup so that line lengths are unaffected. Default is false . |
join | If true , return a string with embedded newlines instead of an array of strings. Default is false . |
The four align
settings appear thus with a width
of 32
:
LEFT: This is a short sample
paragraph, intended for use in
the documentation of Minicle.
It's just long enough to
demonstrate the paragraphize
method.
RIGHT: This is a short sample
paragraph, intended for use in
the documentation of Minicle.
It's just long enough to
demonstrate the paragraphize
method.
CENTER: This is a short sample
paragraph, intended for use in
the documentation of Minicle.
It's just long enough to
demonstrate the paragraphize
method.
FULL: This is a short sample
paragraph, intended for use in
the documentation of Minicle.
It's just long enough to
demonstrate the paragraphize
method.
Takes an input array, args
, which is usually process.argv.slice(2)
, and an
options
object specifying the command line switches and optional subcommands
to be parsed. Returns either an object containing the parsed results or an
object containing errcode
and errmsg
elements.
The options
argument takes one of two forms, depending on whether subcommands
are being used. Without subcommands:
{
switches: {
able: { short: "a", maxArgs: 0 }, // flag, takes no args
baker: { short: "b", maxArgs: 2 }, // has up to 2 args
},
ddash: true
}
The optional ddash
element, if true
, will cause a GNU-style double dash --
to end processing of switches, resulting in all subsequent arguments being
treated as literals.
If subcommands are desired, they are specified in a commands
element, and the
switches
element will contain objects with switches for each subcommand:
{
commands: [ "first", "second" ],
switches: {
first: {
able: { short: "a", maxArgs: 0 },
baker: { short: "b", maxArgs: 2 },
},
second: {
charlie: { short: "c", maxArgs: 1 },
delta: { short: "d", maxArgs: 3 },
}
}
}
On error, an object will be returned containing an error code and an error message:
{ errcode: "BADLONG", errmsg: "Invalid long option '--foo'." }
The errcode
is guaranteed to remain unchanged across future versions of
Minicle so user code can rely on it. The errmsg
is mainly intended to help
with debugging, though it is suitable for display to end users.
On success, a structure like this is returned for simple switches:
{
command: null,
switches: {
alpha: { cnt: 1, args: [ 'foo', 'bar' ] },
beta: { cnt: 1, args: [] },
charlie: { cnt: 0, args: [] }
},
errcode: null,
errmsg: null,
general: []
}
The only difference for git-style commands is that the command
element
will not be null
.
A convenience function used internally, takes text
and returns its
length minus any of the codes used by ansiMarkup
.
Generates a box around a block of text
. The style
argument is an array of
eight strings specifying the appearance of corners and sides in clockwise order
starting with the top left corner. Alternatively, a few named styles are built
in: "basic"
, "ascii"
, "pcdos1"
, "pcdos2"
. (Minicle also exports an
object named extraStyles
which contains additional named styles, but you must
pass the value instead of the key when using them.) The options
argument may
contain one or more of the following:
option | description |
---|---|
width | Total width of box, sides inclusive. Defaults to 76 . |
reflow | If true , all contiguous blocks of text are rewrapped to fit within the box. Lines beginning with whitespace are not modified and may cause overflow on the right. Blank lines are unaltered. Defaults to false . |
noRight | If true , omit the right side of the box. Defaults to false . |
ignoreAnsiMarkup | If true, expect the style to contain ansiMarkup codes and exclude them from character length calculations. Defaults to false . |
padding | An array of character cell counts, [ top, right, bottom, left ] to use for padding. Defaults to [0, 1, 0, 1] . |
The result is returned as an array of lines.
For example
console.log(minicle.textBox("basic style", "basic", { width: 20 }).join("\n") + "\n");
console.log(minicle.textBox("ascii style", "ascii", { width: 20 }).join("\n") + "\n");
console.log(minicle.textBox("pcdos1 style", "pcdos1", { width: 20 }).join("\n") + "\n");
console.log(minicle.textBox("pcdos2 style", "pcdos2", { width: 20 }).join("\n") + "\n");
will yield
As a guide to designing your own styles, here is how the built-in styles are defined:
const styles = {
// 0 1 2 3 4 5 6 7 0 1 2 TL TC TR
// TL TC TR R BR BC BL L 7 3 ML MR
basic: [ "=", "=", "=", "=", "=", "=", "=", "=" ], // 6 5 4 BL BC BR
ascii: [ "+", "-", "+", "|", "+", "-", "+", "|" ],
pcdos1: [ "┌", "─", "┐", "│", "┘", "─", "└", "│" ],
pcdos2: [ "╔", "═", "╗", "║", "╝", "═", "╚", "║" ],
};
Minicle also exports an object named extraStyles
which contains additional
named styles, but you must pass the value instead of the key when using them:
var lines = minicle.textBox("extra example", minicle.extraStyles.lcAsciiGap, { width: 20 })
At present (version 2.1.0), these are all for generating comment blocks in C-like languages:
Emits a single line of the specified style. The style
argument follows the
same format as in textBox
, but only the first three elements are used. If
ignoreAnsiMarkup
is true
, embedded ansiMarkup
codes will be ignored when
performing character length calculations.
Copyright 2019-2023 Eric O'Dell and subsequent contributors
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2.1.2: Bumped version number.
2.1.1: Added graphical representations of ASCII art that the NPM Markdown renderer was messing up.
2.1.0:
- Added
outputHeader
as a convenience function for usage headers. - Added more examples in the documentation.
- Fixed missing graphics in documentation.
2.0.0:
The format of the option map has been greatly simplified along with the
underlying code with no loss of functionality. Exceptions have been replaced
with simple error codes. The usage()
function has been eliminated and replaced
with a generic ANSI markup language interpreter.
- Removed exceptions triggered by end-user error and instead return error objects.
- Merged
minicle-usage
into the main Minicle module. - Eliminated '@' in map receptacle entries so they can be used without brackets in the code.
- Filled in missing documentation on the
customColors
option to theheaders
method.
1.0.6: Updated the docs to announce minicle-usage.
1.0.5: Added doubleDash
option, improved docs, tested heavily, fixed a bunch of edge cases.
1.0.4: Added @subcommand
to optionMap
results, documented same.
1.0.3: We don't talk about this anymore.
1.0.2: Fixed bug that threw an uncaught exception when no CLI arguments were given.
1.0.1: Updated docs to include the necessary options for subcommands.