Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/codeandcats/classy-commander
A TypeScript wrapper for Commander that lets you easily declare commands using classes & decorators and provides you strongly typed arguments.
https://github.com/codeandcats/classy-commander
Last synced: 3 months ago
JSON representation
A TypeScript wrapper for Commander that lets you easily declare commands using classes & decorators and provides you strongly typed arguments.
- Host: GitHub
- URL: https://github.com/codeandcats/classy-commander
- Owner: codeandcats
- License: isc
- Created: 2018-09-11T11:00:56.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2024-04-06T03:05:02.000Z (7 months ago)
- Last Synced: 2024-08-12T17:19:50.483Z (3 months ago)
- Language: TypeScript
- Size: 1.57 MB
- Stars: 10
- Watchers: 2
- Forks: 4
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Support: support/package.ts
Awesome Lists containing this project
README
A TypeScript wrapper for Commander that lets you easily declare commands using classes & decorators and provides you with strongly typed arguments.
[![npm version](https://badge.fury.io/js/classy-commander.svg)](https://badge.fury.io/js/classy-commander)
[![Build Status](https://travis-ci.org/codeandcats/classy-commander.svg?branch=master)](https://travis-ci.org/codeandcats/classy-commander)
[![Coverage Status](https://coveralls.io/repos/github/codeandcats/classy-commander/badge.svg?branch=master)](https://coveralls.io/github/codeandcats/classy-commander?branch=master)## Features
- Write commands as modular classes that can be easily tested
- Specify command usage via a class with decorators
- Command values
- Optional values
- Options
- Options with values
- Automatic coercion
- Version from package.json
- Support for Inversion of Control containers like [Inversify](http://inversify.io/)## Install
```sh
npm install classy-commander --save
```## Usage
First enable support for decorators in your `tsconfig.json` compiler options.```json
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
}
}
```Let's create a simple Calculator CLI app with a command that adds two numbers.
Our entry-point looks like this.
`./calc.ts`
```typescript
import * as cli from 'classy-commander';import './commands/add.ts';
cli.execute();
```Our add command looks like this.
`./commands/add.ts`
```typescript
import { Command, command, value } from 'classy-commander';export class AddCommandParams {
@value()
value1: number = 0;@value()
value2: number = 0;
}@command('add', AddCommandParams, 'Adds two numbers')
export class AddCommand implements Command {execute(params: AddCommandParams) {
const { value1, value2 } = params;const result = value1 + value2;
console.log(`${value1} + ${value2} = ${result}`);
}}
```For simplicity, we'll use [ts-node](https://github.com/TypeStrong/ts-node) to run our app.
Running `ts-node ./calc add 1 2` outputs:
```
1 + 2 = 3
```## Using optional values
But what if we want to add 3 numbers?
Lets allow adding an _optional_ third number.
```typescript
import { Command, command, value } from 'classy-commander';export class AddCommandParams {
@value()
value1: number = 0;@value()
value2: number = 0;@value({ optional: true })
value3: number = 0;
}@command('add', AddCommandParams, 'Adds two or three numbers')
export class AddCommand implements Command {execute(params: AddCommandParams) {
const { value1, value2, value3 } = params;const result = value1 + value2 + value3;
if (value3) {
console.log(`${value1} + ${value2} + ${value3} = ${result}`);
} else {
console.log(`${value1} + ${value2} = ${result}`);
}
}}
```Running `ts-node ./calc add 1 2 3` now outputs:
```
1 + 2 + 3 = 6
```Adding two numbers still works. `ts-node ./calc add 1 2` outputs:
```
1 + 2 = 3
```## Variadic Arguments
Okay, but what if we want to add 4 numbers, or 5? This could get messy.
It's time to turn our values into a variadic value.
```typescript
import { Command, command, value } from 'classy-commander';export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];
}@command('add', AddCommandParams, 'Adds two or more numbers')
export class AddCommand implements Command {execute(params: AddCommandParams) {
const { values } = params;const result = values.reduce((total, val) => total + val, 0);
console.log(`${values.join(' + ')} = ${result}`);
}}
```Running `ts-node ./calc add 1 2 3 4 5` now outputs:
```
1 + 2 + 3 + 4 + 5 = 15
```## Using options
Let's add an option to show thousand separators.
```typescript
import { Command, command, option, value } from 'classy-commander';export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];@option({ shortName: 't' })
thousandSeparators: boolean = false;
}@command('add', AddCommandParams, 'Adds two or more numbers')
export class AddCommand implements Command {execute(params: AddCommandParams) {
const { values, thousandSeparators } = params;const result = values.reduce((total, val) => total + val, 0);
const format = (val: number) => val.toLocaleString(undefined, {
useGrouping: thousandSeparators
});console.log(`${values.map((val) => format(val)).join(' + ')} = ${format(result)}`);
}}
```Running `ts-node ./calc add 500 1000 --thousandSeparators` or `ts-node ./calc add 500 1000 -t` will output:
```
500 + 1,000 = 1,500
```## Using option values
Lets add an option with a value that lets us specify the number of decimal places to show.
```typescript
import { Command, command, option, value } from 'classy-commander';export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];@option({ shortName: 't' })
thousandSeparators: boolean = false;@option({ shortName: 'd', valueName: 'count' })
decimalPlaces: number = 0;
}@command('add', AddCommandParams, 'Adds two or more numbers')
export class AddCommand implements Command {execute(params: AddCommandParams) {
const { values, thousandSeparators, decimalPlaces } = params;const result = values.reduce((total, val) => total + val, 0);
const format = (val: number) => val.toLocaleString(undefined, {
useGrouping: thousandSeparators,
maximumFractionDigits: decimalPlaces
});console.log(`${values.map((val) => format(val)).join(' + ')} = ${format(result)}`);
}}
```Running `ts-node ./calc add 1 2.2345 --decimalPlaces 2` will output:
```
1 + 2.23 = 3.23
```## Getting usage
Running `ts-node ./calc.ts --help` outputs:
```
Usage: calc [options] [command]Options:
-h, --help output usage information
Commands:
add [options]
```Running `ts-node ./calc.ts add --help` shows the usage for our `add` command:
```
Usage: add [options]Options:
-t, --thousandSeparators
-d, --decimalPlaces (default: 0)
-h, --help output usage information
```## Dependency Injection
To keep our add command easy to test, lets move that heavy math into a calculator service, and have that service automatically injected into the command when it gets created. Let's use the awesome [Inversify](http://inversify.io/) library which has excellent support for TypeScript (though in principal we could use any JavaScript Dependency Injection library).Let's start by adding the calculator service.
`./services/calculator.ts`
```typescript
import { injectable } from 'inversify';@injectable()
export class Calculator {
add(...amounts: number[]) {
return amounts.reduce((total, amount) => total + amount, 0);
}
}
```Now lets update our add command to use the service.
`./commands/add.ts`
```typescript
import { injectable } from 'inversify';
import { Command, command, option, value } from 'classy-commander';
import { Calculator } from '../services/calculator';export class AddCommandParams {
@value({ variadic: { type: Number } })
values: number[] = [];@option({ shortName: 't' })
thousandSeparators: boolean = false;@option({ shortName: 'd', valueName: 'count' })
decimalPlaces: number = 0;
}@command('add', AddCommandParams, 'Adds two or more numbers')
@injectable()
export class AddCommand implements Command {
constructor(private calculator: Calculator) {
}execute(params: AddCommandParams) {
const { values, thousandSeparators, decimalPlaces } = params;const result = this.calculator.add(...values);
const format = (val: number) => val.toLocaleString(undefined, {
useGrouping: thousandSeparators,
maximumFractionDigits: decimalPlaces
});console.log(`${values.map((val) => format(val)).join(' + ')} = ${format(result)}`);
}}
```Finally, in our entrypoint, lets create our inversify container and pass it to classy-commander.
`./calc.ts`
```typescript
import { Container } from 'inversify';
import * as cli from 'classy-commander';import './commands/add.ts';
import './services/calculator';const container = new Container({ autoBindInjectable: true });
cli
.ioc(container)
.execute();
```## Specifying the version
There are two ways to specify the version of your CLI:Using the version in your `package.json`.
```typescript
import * as cli from 'classy-commander';...
cli
.versionFromPackage(__dirname)
.execute();
```Or manually.
```typescript
import * as cli from 'classy-commander';...
cli
.version('1.2.3')
.execute();
```## Loading commands from a directory
Maybe we end up adding a bunch of commands to our CLI app and we don't want to manually import each command in our entry point like below:```typescript
import * as cli from 'classy-commander';import './commands/add.ts';
import './commands/subtract.ts';
import './commands/multiply.ts';
import './commands/divide.ts';
import './commands/square.ts';
import './commands/squareRoot.ts';
import './commands/cube.ts';
import './commands/cubeRoot.ts';cli.execute();
```We can tell classy-commander to dynamically load all commands from a directory thus reducing our imports.
```typescript
import * as cli from 'classy-commander';
import * as path from 'path';async function run() {
await cli.commandsFromDirectory(path.join(__dirname, '/commands'));
cli.execute();
}run().catch(console.error);
```## Contributing
Got an issue or a feature request? [Log it](https://github.com/codeandcats/classy-commander/issues).[Pull-requests](https://github.com/codeandcats/classy-commander/pulls) are also welcome. 😸