npm package: @ratwizard/cli
Description: Easy Node CLI framework
Author: Tony McCoy
Latest version: 0.17.0

# @ratwizard/cli

Work-in-progress framework for quickly setting up CLI tools.

# Quick Start

- Install with `npm i @ratwizard/cli` or `yarn add @ratwizard/cli`.
- See [How does this thing work?](#getting-started) to see how this thing works.

# How does this thing work?<a name="getting-started"></a>

## Contents

- [Initialize](#initialize)
- [Define a Command](#define-a-command)
  - [Single root command](#define-a-command)
  - [Multiple commands](#multiple-commands)
  - [Commands from other files](#commands-from-other-files)
- [Naming the CLI](#naming-the-cli)

## Initialize<a name="initialize"></a>

The module exports a function to which you pass some optional metadata. You will receive a set of functions you can use to configure your CLI.

```js
// in cli.js

const { program } = require("@ratwizard/cli");
```

We are destructuring the `program` object, used to define a root command. The `program` object has all the functions we'll need to do everything here in the main file.

## Define A Command<a name="define-a-command"></a>

Let's start with the most basic CLI definition; a single function with arguments and flags. The root command can have as many arguments and options as needed. All you need is the single command with a bunch of options if you want to structure your CLI like [youtube-dl](https://youtube-dl.org/).

```js
// in cli.js

program
  .description("extends a greeting")
  .arg("name", {
    description: "name of the person to greet",
    default: "world"
  })
  .option("-l, --loud", {
    description: "SHOUT",
    boolean: true // a true/false flag that takes no arguments
  })
  .option("--extra <text>", {
    // Whereas this does take a value
    description: "add more to your greeting"
  })
  .action({ args, options } => {
    let message = `Hello, ${args.name}!`;

    if (options.loud) {
      message = message.toUpperCase() + "!!";
    }

    if (options.extra) {
      message += options.extra + "!";

      if (options.loud) {
        message = message.toUpperCase() + "!!";
      }
    }

    console.log(message);
  })
  .run(process.argv);
```

Notice the call to `.run()` at the end of the chain. This will parse command line arguments and execute the command we just defined.

Now you can call your CLI like this:

```
$ node cli.js Tom --extra gaaaaa --loud
HELLO, TOM!!! GAAAAA!!!
```

You also get a nicely formatted `--help` option out of the box. The help text includes any `description` fields you add to your command, arguments and flags.

```
$ node cli.js --help

USAGE
    cli.js [--flags] [<name>]

DESCRIPTION
    extends a greeting

ARGUMENTS
    name             (string)   name of the person to greet

FLAGS
    -l, --loud       SHOUT
    --extra <text>   add more to your greeting

```

### Multiple Commands<a name="multiple-commands"></a>

Or, you can define several non-root commands. A user would invoke these commands by passing their name as an argument. If you are familiar with `git`, think of commands as you would `git commit` or `git log`. They are sub-functions under the larger CLI umbrella.

Let's turn the single command above into a subcommand called "greet". We'll move its properties to an object and pass them to a .command() function on the program object. Let's also add another command for if we encounter any droids.

```js
// in cli.js

program
  .command("greet", {
    description: "extends a greeting",
    args: {
      name: {
        description: "name of the person to greet",
        default: "world",
      },
    },
    options: {
      "-l, --loud": {
        description: "SHOUT",
        boolean: true,
      },
      "--extra <text>": {
        description: "add more to your greeting",
      },
    },
    action: ({ args, options }) => {
      let message = `Hello, ${args.name}!`;

      if (options.loud) {
        message = message.toUpperCase() + "!!";
      }

      if (options.extra) {
        message += options.extra + "!";

        if (options.loud) {
          message = message.toUpperCase() + "!!";
        }
      }

      console.log(message);
    },
  })
  .command("beep", {
    description: "extends a greeting for droids",
    action: () => {
      console.log("boop");
    },
  })
  .run(process.env);
```

Now both commands can be called by name through the CLI:

```
$ node cli.js greet Bob
Hello, Bob!

$ node cli.js beep
boop
```

If you enter a command name that doesn't exist you will see a listing of all valid commands. This is the same output you would see if you used the `--help` flag.

```
$ node cli.js notreal

USAGE
    cli.js <command> [<args>]

COMMANDS
    run `cli.js <command> --help` to read about args and options for a specific command

    greet .. extends a greeting
    beep ... extends a greeting for droids
```

As it says, if you need help with a specific command, you can get it by using `--help` with that command.

```
$ node cli.js greet --help

USAGE
    cli.js greet [--flags] [<name>]

DESCRIPTION
    extends a greeting

ARGUMENTS
    name             (string)   name of the person to greet

FLAGS
    -l, --loud       SHOUT
    --extra <text>   add more to your greeting
```

### Commands from other files<a name="commands-from-other-files"></a>

Of course, chaining multiple commands requires a change of syntax from chained functions to an object. If you prefer the function chaining syntax, if your commands have many args and options, or if you have many commands, it might be nicer to split each one off into its own separate file. You can do that!

You'll still initialize your command with a name, but we are moving the other calls into another file. The other file will export a top level command.

```js
// in cli.js

program
  .command("greet", {
    description: "extends a greeting",
    path: "./commands/greet",
  })
  .command("beep", {
    description: "extends a greeting for droids",
    path: "./commands/beep",
  })
  .run(process.env);
```

That's it! Everything other than the command name is configured in our new `./commands/greet.js` file. Running `node cli.js greet` should have exactly the same effect it did before.

The only difference is that you'll define the command with `new Command()` and not include a `.run()` call at the end.

```js
// in ./commands/greet.js

const { Command } = require("@ratwizard/cli");

module.exports = new Command()
  .arg("name", {
    description: "name of the person to greet",
    default: "world"
  })
  .option("-l, --loud", {
    description: "SHOUT",
    boolean: true // a true/false flag that takes no arguments
  })
  .option("--extra <text>", {
    // Whereas this does take a value
    description: "add more to your greeting"
  })
  .action({ args, options } => {
    let message = `Hello, ${args.name}!`;

    if (options.loud) {
      message = message.toUpperCase() + "!!";
    }

    if (options.extra) {
      message += options.extra + "!";

      if (options.loud) {
        message = message.toUpperCase() + "!!";
      }
    }

    console.log(message);
  })
```

> **TIP**<br>
> Commands loaded from a path are only executed when invoked or when generating help. If the CLI arguments don't specify the command then the file is never touched. This can be a good way to keep even a complex CLI lean and fast.

## Naming the CLI<a name="naming-the-cli"></a>

You can give your CLI a name in a couple simple steps. This is an `npm` feature that has nothing to do with this library, but chances are you're going to want to do this if you plan on publishing your CLI.

In your `package.json`, add a `bin` object and add your CLI's name as a key with the path to your entry file as a value. In this example, running `greeter` will invoke our CLI.

```json
{
  ...

  "bin": {
    "greeter": "./cli.js"
  }

  ...
}
```

In `cli.js`, add the Node hashbang at the top of the file. This tells your system which program is supposed to interpret the file. Our whole example file looks like this:

```js
#!/usr/bin/env node

const { program } = require("@ratwizard/cli");

program
  .command("greet", {
    description: "extends a greeting",
    args: {
      name: {
        description: "name of the person to greet",
        default: "world",
      },
    },
    options: {
      "-l, --loud": {
        description: "SHOUT",
        boolean: true,
      },
      "--extra <text>": {
        description: "add more to your greeting",
      },
    },
    action: ({ args, options }) => {
      let message = `Hello, ${args.name}!`;

      if (options.loud) {
        message = message.toUpperCase() + "!!";
      }

      if (options.extra) {
        message += options.extra + "!";

        if (options.loud) {
          message = message.toUpperCase() + "!!";
        }
      }

      console.log(message);
    },
  })
  .command("beep", {
    description: "extends a greeting for droids",
    action: () => {
      console.log("boop");
    },
  })
  .run(process.env);
```

Now install the module globally through `npm`:

```
$ npm i -g .
```

Now you should be able to run it with the name you've chosen:

```bash
$ greeter greet
Hello, world!

$ greeter greet Stranger
Hello, Stranger!

$ greeter beep
boop
```

The `--help` command will automatically take the name the program is run with into account when generating help text. You'll notice the example usage now uses the name `greeter` instead of `cli.js`.

```
$ greeter --help

usage: greeter <command> [<args>]

COMMANDS
    run `greeter <command> --help` to read about args and options for a specific command

    greet .. extends a greeting
    beep ... extends a greeting for droids
```

# Utilities<a name="utilities"></a>

There are some other utilities exported from the top level of the module:

## `print` and `println`



[See print README](./src/lib/print/README.md)

```js
const { print, println } = require("@ratwizard/cli");

// Prints without line breaks
print("ba");
print("na");
print("na");

// Adds a line break at the end
println("one");
println("two");
println("three");
```

And in the terminal you see:

```
bananaone
two
three
```

You can also use `tml` tags to style text for the terminal along with `sprintf` style interpolation:

```js
println(
  "Oh, <italic>yeah</italic>, you can also <bg-lightgreen>%s</bg-lightgreen> with <red>%s</red>",
  "format stuff",
  "tml tags"
);
```

## `prompt`

Ask the user for freeform text input.

[See prompt README](./src/lib/ask/README.md)

```js
const { ask } = require("@ratwizard/cli");

const answer = await ask("What color do you want?", {
  validate: (input) => {
    if (input.toLowerCase() !== "black") {
      return "You can have any color as long as it's black".
    }
  }
});

console.log("Very good choice.");
```

And in the terminal:

```
What color do you want?
> red
You can have any color as long as it's black
> orange
You can have any color as long as it's black
> pink
You can have any color as long as it's black
> black
Very good choice.
```

## `confirm`

Ask the user a yes or no question and get `true` or `false` back.

[See confirm README](./src/lib/confirm/README.md)

```js
const { confirm } = require("@ratwizard/cli");

const confirmed = await confirm("Are you sure?");

if (confirmed) {
  console.log("Thank you. Confirmed.");
} else {
  console.log("Not confirmed.");
}
```

User enters their response and presses enter:

```
Are you sure? [Y/N] y
Thank you. Confirmed.

$|
```

```
Are you sure? [Y/N] n
Not confirmed.

$|
```

## `tabulate`

Transforms a 2D array into a table.

[See tabulate README](./src/lib/tabulate/README.md)

```js
const { tabulate } = require("@ratwizard/cli");

const table = tabulate(
  [
    [6, "two", "three", 1.44],
    [1235, "two", null, 662.141],
    [45, null, "hello\nworld", 89.00000000000001],
    [331, "last", "last", 5],
  ],
  {
    showIndex: true,
    headers: ["One", "Two", "Three", "Numbers"],
    style: "box",
  }
);

console.log(table);

```

And ye shall see:

```
┌───┬──────┬──────┬───────┬────────────────────┐
│ # │  One │ Two  │ Three │ Numbers            │
├───┼──────┼──────┼───────┼────────────────────┤
│ 0 │    6 │ two  │ three │   1.44             │
│ 1 │ 1235 │ two  │       │ 662.141            │
│ 2 │   45 │      │ hello │  89.00000000000001 │
│   │      │      │ world │                    │
│ 3 │  331 │ last │ last  │   5                │
└───┴──────┴──────┴───────┴────────────────────┘
```


## `tml`

Transforms HTML-style tags into terminal styles and colors.

[See tml README](./src/lib/tml/README.md)

```js
const { tml } = require("@ratwizard/cli");

const output = tml("This text is <italic>formatted</italic> with <red><bold>TML</bold> tags</red>!");

console.log(output);
```

# API Reference<a name="api-reference"></a>

To be written as the library approaches v1.0.