Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/DiegoZoracKy/cliss
CLI Simple, Stupid. Automatic discovery of parameters names. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand. Support to sync / async. Support to subcommands down to N levels.
https://github.com/DiegoZoracKy/cliss
cli command-line command-line-interface nodejs
Last synced: 3 months ago
JSON representation
CLI Simple, Stupid. Automatic discovery of parameters names. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand. Support to sync / async. Support to subcommands down to N levels.
- Host: GitHub
- URL: https://github.com/DiegoZoracKy/cliss
- Owner: DiegoZoracKy
- Created: 2017-11-04T16:13:27.000Z (about 7 years ago)
- Default Branch: master
- Last Pushed: 2021-06-12T15:53:07.000Z (over 3 years ago)
- Last Synced: 2024-07-08T14:48:12.030Z (4 months ago)
- Topics: cli, command-line, command-line-interface, nodejs
- Language: JavaScript
- Size: 57.6 KB
- Stars: 10
- Watchers: 3
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# CLIss
[![Build Status](https://api.travis-ci.org/DiegoZoracKy/cliss.svg)](https://travis-ci.org/DiegoZoracKy/cliss) [![npm](https://img.shields.io/npm/v/cliss.svg)]() [![npm](https://img.shields.io/npm/l/cliss.svg)]()
CLI Simple, Stupid. Automatic discovery of parameters names and support to subcommands down to N levels. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand.
Side note: It is worth taking a look at [MagiCLI](https://github.com/DiegoZoracKy/magicli), which is a module capable to create a CLI interface automatically for a module, instead of creating one by hand.
## Goals
* Simple and easy API
* Easy minimal setup, extracting options names from functions parameters
* Out of the box support to sync or async (Promise) functions
* Subcommands down to N levels
* Automatic Help section generation, that can be improved only when needed## Installation
```bash
$ npm install cliss
```## Usage
Through this section we'll be going from the most minimal usage of the module, where options names are extracted from functions parameters:
```javascript
const func = (param1, param2) => `${param1}_${param2}`;
cliss(func);
```to a version using all the possible options it provides:
```javascript
const cliSpec = {
name,
description,
version,
options: [{
name,
description,
required,
type
}],
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {},
before: (args, positionalArgs, argsAfterEndOfOptions) => {},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {}
},
action: () => {},
commands: [{}]
};const clissOptions = {
command: {
subcommandsDelimiter
},
options: {
validateRequiredParameters
},
version: {
option
},
help: {
option,
stripAnsi
},
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {},
before: (args, positionalArgs, argsAfterEndOfOptions) => {},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {}
}
};cliss(cliSpec, clissOptions);
```### A CLI for a function (the most simple and minimal use case)
`cliss(functionReference)`Creating a CLI for a function by doing nothing more than passing it as a parameter to cliss. The options names will be the same as the parameters expected by the function.
```javascript
'use strict';
const cliss = require('cliss');const aFunctionWithWeirdParametersDefinition = (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;return result;Run the program passing with the following options:
};
cliss(aFunctionWithWeirdParametersDefinition);
```
Calling it via CLI with `--help` will give you:```bash
Options:--param1
--param2
--param3
--args
```Passing in the options:
`node cli.js --param2=PARAM2 --param1=PARAM1 --param3=PARAM3 --args=a --args=r --args=g --args=s`Or passing options + arguments (arguments for the "...args" parameter in this case):
`node cli.js --param2=PARAM2 --param1=PARAM1 --param3=PARAM3 a r g s`Will result in:
```bash
param1: PARAM1
param2: PARAM2
param3: PARAM3
args: a,r,g,s
```
Note that the order of the options doesn't need to match the order of the parameters.### Improving the help section
`cliss(cliSpec)`Great, but probably one would like to improve a bit the `--help` section of the module, by providing to the end user the **name** (the command's name for calling it via CLI), **description** and **version** of the module. In this case a *Object Literal* will be used instead of just a function reference.
```javascript
'use strict';
const cliss = require('../');cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;return result;
}
});
```Now, when calling it with `--help`, a better help section will be shown:
```bash
Description:Just an example that will do nothing but concat all the parameters.
Usage:
$ some-command [options] [args...]
Options:
--param1
--param2
--param3
--args
```### Providing more information about the expected options
`cliss(cliSpec)`The options were effortlessly extracted from the parameters names, but **cliss** provides a way for one to provide more information about each of them. The *Object Literal* passed in the *cliSpec* parameter can have a property named **options**, which expects an *Array* of objects, containing the **name** of the option plus some of the following properties:
* **required**
To tell if the parameter is required.* **description**
To give hints or explain what the option is about.* **type**
To define how the parser should treat the option (Array, Object, String, Number, etc.). Check [yargs-parser](https://github.com/yargs/yargs-parser) for instructions about *type*, as it is the engine being used to parse the options.* **alias**
To define an alias for the option.Following the last example, let's improve it to:
* give more information about **param1**
* check **args** as required```javascript
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
options: [{
name: 'param1',
description: 'This param is the base value to compute everything else.',
required: true,
type: 'String'
}, {
name: 'args',
required: true
}],
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;return result;
}
});
```Call `--help`, and note that the *Usage* section will also be affected. Now *[options] [args...]* will be shown as * *, because both of them are required.
```bash
Description:Just an example that will do nothing but logging all the parameters.
Usage:
$ some-command
Options:
--param1 String Required - This param is the base value to compute
everything else.
--param2
--param3
--args Required
```Run the program with the following options:
`node cli.js --param1=001 --param2=002 --param3=PARAM3 a r g s`And check the result to see how *param1* was indeed treated as a string, while *param2* was parsed as a number:
```bash
param1: 001
param2: 2
param3: PARAM3
args: a,r,g,s
```### Pipe: STDIN, Before and After
`cliss(cliSpec)`A property named **pipe** can also be defined on *cliSpec* in order to handle **stdin** and also, some steps of the execution flow (**before** and **after**). To define a single handle for all the commands, the **pipe** option can be defined on [Cliss options](#cliss-options) as will be shown later on the documentation. The pipeline execution of a command is:
**stdin** *(command.pipe.stdin || clissOptions.pipe.stdin)* =>
**clissOptions.pipe.before** =>
**command.pipe.before** =>
**command.action** =>
**command.pipe.after** =>
**clissOptions.pipe.after** =>
**stdout**
Where each of these steps can be handled if needed.
The properties expected by **pipe** are:
* **stdin**
`(stdinValue, args, positionalArgs, argsAfterEndOfOptions)`* **before**
`(args, positionalArgs, argsAfterEndOfOptions)`
To transform the data being input, before it is passed in to the main command action.* **after**
`(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)`
To transform the *output* (for example, to JSON.stringify an *Object Literal*)Note: **stdin** and **before** must always return *args*, and **after** must always return *result*, as these values will be passed in for the next function in the pipeline.
To better explain with an example, let's modify the previous one to:
* get *param3* from **stdin**
* use **before** to reverse *...args* array
* use **after** to decorate the outputCheck the *pipe* property on the following code:
```javascript
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
options: [{
name: 'param1',
description: 'This param is needed to compute everything else.',
required: true,
type: 'String'
}, {
name: 'args',
required: true
}],
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {
args.param3 = stdinValue;
return args;
},
before: (args, positionalArgs, argsAfterEndOfOptions) => {
positionalArgs.reverse();
return args;
},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {
return `======\n${result}\n======`;
}
},
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;return result;
}
});
```Calling it as:
`echo "fromSTDIN" | node cli.js --param1=001 --param2=002 a r g s`Will result in:
```bash
=======
param1: 001
param2: 2
param3: fromSTDIN
args: s,g,r,a
=======
```### Subcommands
Subcommands can be defined in a very simple way. Thinking naturally, a subcommand should be just a command that comes nested into another one, and it is exactly how it's done.
Here one more property of the *cliSpec* is introduced: **commands**. It is an *Array* that can contains N commands, including the **commands** property (commands can be nested down to N levels).
As each subcommand is a command itself, they also counts with its own `--help` section, and possibly its own `--version` (if it is not defined for a subcommand, the one defined for the root will be shown).
The following example will introduce:
* 1 subcommand, thas has no action, and contains more 2 subcommands
* 1 subcommand that contains an action```javascript
cliss({
name: 'some-command',
description: 'Just an example that will do nothing but concat all the parameters.',
version: '1.0.0',
options: [{
name: 'param1',
description: 'This param is the base value to compute everything else.',
required: true,
type: 'String'
}, {
name: 'args',
required: true
}],
pipe: {
stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {
args.param3 = stdinValue;
return args;
},
before: (args, positionalArgs, argsAfterEndOfOptions) => {
positionalArgs.reverse();
return args;
},
after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {
return `======\n${result}\n======`;
}
},
action: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) => {
let result = `param1: ${param1} \n`;
result += `param2: ${param2} \n`;
result += `param3: ${param3} \n`;
result += `args: ${args.join(',')}`;return result;
},
commands: [{
name: 'subcommand1',
commands: [{
name: 'action1',
options: [{
name: 'param',
required: true
}],
action: param => `subcommand1 action1 param: ${param}`
}, {
name: 'action2',
action: () => 'subcommand1 action2'
}]
}, {
name: 'subcommand2',
action: () => console.log('subcommand2')
}]
});
```Call `--help` to see that a new section *Commands:* is presented:
```bash
Description:Just an example that will do nothing but concat all the parameters.
Usage:
$ some-command
$ some-command [command]Options:
--param1 String Required - This param is needed to compute
everything else.
--param2
--param3
--args RequiredCommands:
subcommand1
subcommand2
```Each subcomand has its own help section, check:
`node cli.js subcommand1 --help````bash
Usage:$ some-command subcommand1
````node cli.js subcommand2 --help`:
```bash
Usage:$ some-command subcommand2
Commands:
action1
action2
```Just call the commands names separated by space:
`node cli.js subcommand2 action1 --param=VALUE`Result:
```bash
subcommand2 action1 param: VALUE
```### Cliss options
`cliss(cliSpec, clissOptions)`An `Object Literal` with the following options can be passed in as the second parameter to cliss:
* **command**
* **subcommandsDelimiter**
To define a delimiter for a subcommand to be used instead of a white space. For example, if `'-'` is passed in, the subcommands should be called as `subcommand1-action1` instead of `subcommand1 action1`.* **options**
* **validateRequiredParameters**
If set to `true`, the required parameters will be checked before the command action is called, and the help section will be shown in case a required parameter is missing.* **help**
* **option**
To define a different option name to show the help section. For example, if `'helpsection'` is passed in, `--helpsection` must be used instead of `--help`.* **stripAnsi**
Set to `true` to strip all ansi escape codes (colors, underline, etc.) and output just a raw text.* **version**
* **option**
To define a different option name to show the version. For example, if `'moduleversion'` is passed in, `--moduleversion` must be used instead of `--version`.* **pipe**
As it is defined on *cliSpec* for each command, **pipe** can also be defined in *clissOptions* to implement for all commands a unique way to handle **stdin** and also, some steps of the execution flow (**before** and **after**) in case it is needed. The pipeline execution of a command is:
**stdin** *(command.pipe.stdin || clissOptions.pipe.stdin)* =>
=> **clissOptions.pipe.before** =>
=> **command.pipe.before** =>
=> **command.action** =>
=> **command.pipe.after** =>
=> **clissOptions.pipe.after**
=> **stdout**
The properties expected by **pipe** are:* **stdin**
`(stdinValue, args, positionalArgs, argsAfterEndOfOptions)`* **before**
`(args, positionalArgs, argsAfterEndOfOptions)`
To transform the data being input, before it is passed in to the main command action.* **after**
`(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)`
To transform the *output* (for example, to JSON.stringify an *Object Literal*)Note: **stdin** and **before** must always return *args*, and **after** must always return *result*, as these values will be passed in for the next function in the pipeline.