Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/vtex/findhelp
A simple and hackable lib to create modular CLI's
https://github.com/vtex/findhelp
argv cli nodejs xp-developer
Last synced: about 2 months ago
JSON representation
A simple and hackable lib to create modular CLI's
- Host: GitHub
- URL: https://github.com/vtex/findhelp
- Owner: vtex
- License: mit
- Created: 2016-05-29T20:34:06.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2024-06-28T01:57:58.000Z (7 months ago)
- Last Synced: 2024-11-20T12:35:18.515Z (about 2 months ago)
- Topics: argv, cli, nodejs, xp-developer
- Language: JavaScript
- Homepage:
- Size: 102 KB
- Stars: 9
- Watchers: 146
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
- my-awesome-list - findhelp
README
# findhelp
> A simple and hackable lib to help create modular command line programs.
For those times when you just need to find some help to structure your CLI. đ âšī¸
## What
Given a `tree` of commands and an arguments vector, `findhelp` can:
- Create a pretty "help" menu.
- Traverse the tree and find the correct command.
- *(Optionally)* Run the command `handler` with given `args` and `options`.For example, [this tree](./src/fixtures.js) generates this help content:
```
Usage: findhelp [options]Commands:
login [email] Login with your account
logout Logout from current account
list [query] List your packages
install Install the given app
uninstall Remove the given app
publish Publish this appworkspace new Create a new workspace
workspace delete Delete this workspace
workspace promote Promote this workspace to master
workspace list List available workspacesOptions:
--verbose show all logs
-h, --help show help information
-v, --version show version number
```What's interesting is that you can assemble that tree any way you want, so your commands might be handled by completely different modules - no problem.
For a real-life usage example, take a look at [VTEX Toolbelt](https://github.com/vtex/toolbelt/).
## Why
Node has some pretty good, full-feature CLI libs, like [commander](https://github.com/tj/commander.js), [yargs](https://github.com/yargs/yargs) and [neodoc](https://github.com/felixschl/neodoc). Why write another one?
First, those projects are *very* opinionated. This is excellent for small and quick projects - they got the 95% of the cases covered. You won't go wrong with any of them!
However, the structure comes at the price of control. They tend to _own_ the entire lifecycle of your CLI, which might be bad if you want fine-grained control over how your program behaves.
Second, I had a free weekend. đ
## How
Unlike other CLI solutions available, `findhelp` won't *actually do* anything for you. It finds the command based on arguments, and gets out of your way.
### `find(tree, argv)` and `run(command, root)`
Here's a minimal example of the `find` usage:
```js
#!/usr/bin/env node
import {find, run, MissingRequiredArgsError, CommandNotFoundError} from 'findhelp'
import {tree} from './fixtures' // Your tree defining the commandstry {
const found = find(tree, process.argv.slice(2))
run(found) // This will run the command called by the user
} catch (e) {
switch (e.constructor) {
case MissingRequiredArgsError:
console.error('Missing required arguments:', e.message)
break
case CommandNotFoundError:
console.error('Command not found:', process.argv.slice(2))
break
default:
console.error('Something exploded :(')
console.error(e, e.stack)
}
}
```That's it. You pass to `find` your command `tree` and your `argv`, and it will return an object like:
```js
{
command: ,
args: ['any', 'required', 'or', 'optional', 'args', argv]
}
```The last argument is always `argv`, as parsed by `minimist`. It will contain any flag `options` defined by your command.
You can optionally use `run`, which calls `command.handler` with the provided `args` for you.
### `help(tree, {name})`
You can use that same `tree` to output a pretty help menu. The second parameter is an object with the name of the command line application. Here's the handler for the root command in that example:
```js
import {help} from 'findhelp'handler: (options) => {
if (options.h || options.help) {
console.log(help(tree, {name: 'findhelp'}))
} else if (options.v || options.version) {
console.log(pkg.version)
} else {
console.log('Hi, there! :)')
}
}
```No automatic anything. You're in control. (Use your power wisely).
### The command tree
A command tree is composed of one or many command objects with:
- **`requiredArgs`**: Required arguments to run the command
- **`optinalArgs`**: Optional arguments
- **`description`**: Description to be displayed in the `help()` function
- **`handler`**: Function that will be called with the `run()` function passing the required and optional arguments as parameters
- **`alias`**: An alias for the command
- **`options`**: An object of [`options`](#options)The `handler` can be either a function or a string that locates the module where the handling function is the default export. The `root` parameter in `run()` will be used to resolve the full path of the module in the case a string is passed. If `handler` is not specified, findhelp will try to locate the module following the folders maching the command tree structure from the specified `root` (see the examples below).
#### Examples
```js
login: {
requiredArgs: 'store',
optionalArgs: 'email',
description: 'Login with your account',
handler: (store, email, options) => { /* do awesome stuff! */ },
logout: {
description: 'Logout from current account',
handler: './logout'
},
workspace: {
new: {
requiredArgs: 'name',
description: 'Create a new workspace',
// will look at './workspace/new' (from root) for handling function
},
delete: {
requiredArgs: 'name',
description: 'Delete this workspace',
options: [
{
short: 'a',
long: 'account',
type: 'string',
},
],
// will look at './workspace/delete' (from root) for handling function
},
}
```Here is how './workspace/delete' could look like:
```js
export default async (name, {account}) => {
// ...
}
```These will define the following commands:
- `yourapp login [email]`
- `yourapp crazy [thisisfine]`#### Namespaces
Namespaces enable commands with 2 or more levels. Example:
```js
workspace: {
new: {
requiredArgs: 'name',
description: 'Create a new workspace',
handler: console.log.bind(console),
},
delete: {
requiredArgs: 'name',
description: 'Delete this workspace',
options: [
{
short: 'a',
long: 'account',
type: 'string',
},
],
handler: console.log.bind(console),
},
}
```These will define the following commands:
- `yourapp workspace new `
- `yourapp workspace delete `### Options
An array containing options:
```js
options: [
{
long: 'verbose',
description: 'show all logs',
type: 'boolean',
},
{
short: 'h',
long: 'help',
description: 'show help information',
type: 'boolean',
},
{
long: 'version',
short: 'v',
description: 'show version number',
type: 'boolean',
},
]
```These will enable the following options:
- `yourapp --verbose`- `yourapp --help` or `yourapp -h`
- `yourapp --version` or `yourapp -v`## That's it
Now you know everything. Go play! Then, submit a sweet pull request to make this shinier. Thanks. đ¤