Commands
In this guide, we will be creating a command-line interface for an example application that performs mathematical operations on its arguments.
Basic features
Our application is named calc
and performs the following operations:
add
- adds multiple numbers (defaults to0
if no number)sub
- subtracts two numbers (defaults toNaN
if less than two numbers)mult
- multiplies multiple numbers (defaults to1
if no number)div
- divides two numbers (defaults toNaN
if less than two numbers)
Advanced features
In addition to the basic operations, our application is capable of combining operations in a tail-recursive manner. For example, the following expression could be invoked:
calc add 1 sub 2 mult 3 div 4 2
And would be evaluated as 1 + (2 - (3 * (4 / 2)))
, which equals -3.
Option definitions
In this section we are going to define the command-line options for our application. The first thing we need is to import the necessary library types:
import type { Options, OptionValues } from 'tsargp';
Reusable definitions
Let's define a reusable option definition for the parameters of a multi-argument operation. It has a single numbers option that receives (unlimited) positional arguments:
const multiOpts = {
numbers: {
type: 'numbers',
preferredName: 'numbers',
desc: 'The numbers to operate on.',
default: [],
positional: true,
group: 'Arguments',
},
} as const satisfies Options;
Since this option accepts positional arguments, we do not want to give it a name. However, we give it a preferred name that will be displayed in error messages, as well as a separate group for it to be displayed in the help message. This option is optional and defaults to an empty array.
Now let's define a similar option for the parameters of a dual-argument operation. It has the same definition as above, with additional constraints: it is required and accepts at most two values.
const binaryOpts = {
numbers: {
...multiOpts.numbers,
limit: 2,
required: true,
default: undefined, // override this setting
},
} as const satisfies Options;
Operation definitions
With this in place, we can define each one of the basic operations, as follows:
const addOpts = {
add: {
type: 'command',
names: ['add'],
desc: 'A command that adds multiple numbers.',
options: (): Options => ({ ...multiOpts, ...mainOpts }),
exec({ param }): number {
const vals = param as OptionValues<typeof multiOpts & typeof mainOpts>;
const other = vals.add ?? vals.sub ?? vals.mult ?? vals.div ?? 0;
return vals.numbers.reduce((acc, val) => acc + val, other);
},
},
} as const satisfies Options;
Notice how we use an options callback to provide the option definitions for each command. This is
necessary because JavaScript does not allow us to reference the containing object from one of its
members, before its initialization. (The mainOpts
variable will be defined later, and the above
definitions will be embedded in it.)
Inside the command callback, we perform a typecast to access the values parsed for the command. This is necessary because the library does not know the concrete type of our option values when it calls the callback (the callback type cannot be defined in terms of a generic type parameter). So it must pass an opaque reference to them.
From those values, we select the value resulting from the next operation (if any), falling back to
a default if this was the last operation. We then return the accumulated result of the operations
performed so far in the stack (except if, in the case of sub
and mult
, two arguments are
provided before a recursive call, then the result of that call is ignored).
In TypeScript, it's necessary to specify the return type of both the options
and exec
callback
attributes. Otherwise, the compiler will not be able to resolve the type of the containing object.
Main definitions
Finally, we can define our main command-line options:
const mainOpts = {
help: {
type: 'help',
names: ['help'],
desc: 'Prints this help message.',
},
...addOpts,
...subOpts,
...multOpts,
...divOpts,
} as const satisfies Options;
Trying it out
List of commands to try:
calc help
- should print the main help messagecalc <op> help
- should print the help message of the<op>
commandcalc
- should printNaN
calc add
- should print0
calc mult
- should print1
calc sub
orcalc div
- should print an error messagecalc sub 1
orcalc div 1
- should printNaN
calc sub 1 2 3
orcalc div 1 2 3
- should print an error messagecalc add 1 sub 2 mult 3 div 4 2
- this is the aforementioned example that gives -3