Skip to content

Commit cfdb026

Browse files
committed
Editing for documentation content
1 parent f608d51 commit cfdb026

File tree

1 file changed

+41
-33
lines changed

1 file changed

+41
-33
lines changed

libraries/ts-command-line/README.md

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# ts-command-line
22

3-
This library makes it easy to create professional command-line tools for NodeJS. By "**professional**", we mean:
3+
This library helps you create professional command-line tools for Node.js. By "professional", we mean:
44

5-
- **no gotchas for users**: This requirement seems obvious, but try typing "`npm install --save-dex`" instead of "`npm install --save-dev`" sometime. The mistyped letter gets silently ignored, and the command appears to execute successfully! This can be extremely confusing and frustrating. It plagues many familiar NodeJS tools. For a great user experience, the command line should always use a strict parser that catches these mistakes.
5+
- **no gotchas for users**: Seems obvious, but try typing "`npm install --save-dex`" instead of "`npm install --save-dev`" sometime. The command seems to execute successfully, but it doesn't save anything, because the misspelled flag was silently ignored. This lack of rigor plagues many familiar NodeJS tools, and can sometimes be very confusing and frustrating! For a great user experience, a command line parser should always strictly validate all arguments.
66

7-
- **no gotchas for developers**: Most JavaScript command-line parsers store their output in a simple hash object. This is very convenient for small projects, but suppose many different source files participate in defining and reading command-line parameters: A misspelled variable name is indistinguishable from a real flag that was omitted. Even if you get the names right, the data type might be unpredictable (is that count `1` or `"1"`?). **ts-command-line** models each parameter type as a real TypeScript class.
7+
- **no gotchas for developers**: Many command-line libraries store their parsed data in a simple JavaScript hash object. This is convenient for small projects, but suppose a large project has many different source files that define and read parameters. If you try to read `data['output-dir']` when it wasn't defined, or if you misspell the key name, your tool will silently behave as if the parameter was omitted. And is `data['max-count']` a string or a number? Hard to tell! The **ts-command-line** library models each parameter type as a real TypeScript class.
88

9-
- **automatic documentation**: Some command-line libraries treat the `--help` docs as a separate exercise for the reader. **ts-command-line** requires documentation for every parameter, and automatically generates the `--help` for you. If you write long paragraphs, they will be word-wrapped correctly. (Yay!)
9+
- **automatic documentation**: Some command-line libraries treat the `--help` docs as a separate exercise for the reader. **ts-command-line** requires each every parameter to have a documentation string, and will automatically generates the `--help` for you. If you want to write long paragraphs, no problem -- they will be word-wrapped correctly. *[golf clap]*
1010

11-
- **structure and extensibility**: Instead of a simple function chain, **ts-command-line** provides a "scaffold" pattern that makes it easy to find and understand the command-line parser for tool project. The scaffold model is generally recommended, but there's also a "dynamic" model if you need it. (See below.)
11+
- **structure and extensibility**: Instead of a simple function chain, **ts-command-line** provides a "scaffold" pattern that makes it easy to find and understand the command-line implementation for any tool project. The scaffold model is generally recommended, but there's also a "dynamic" model if you need it. (See below.)
1212

13-
Internally, **ts-command-line** is based on [argparse](https://www.npmjs.com/package/argparse) and the Python approach to command-lines. Compared to other libraries, it doesn't provide zillions of alternative syntaxes and bells and whistles. But if you're looking for a simple, professional, railed experience for your command-line tool, give it a try!
13+
Internally, **ts-command-line** is based on [argparse](https://www.npmjs.com/package/argparse) and the Python approach to command-lines. It doesn't provide zillions of alternative syntaxes and bells and whistles. But if you're looking for a simple, robust solution for your command-line, give it a try!
1414

1515

1616
### Some Terminology
@@ -23,24 +23,26 @@ widget --verbose push --force --max-count 123
2323

2424
In this example, we can identify the following components:
2525

26-
- **"parameter"**: The `--verbose`, `--force`, and `--max-count` are called *parameters*. The currently supported parameter types include: **flag** (i.e. boolean), **integer**, **string**, **choice** (i.e. enums), and **string list**.
27-
28-
- **"argument"**: The value "123" is the *argument* for the `--max-count` integer parameter. (Flags don't have arguments, because their value is determined by whether the flag was provided or not.)
29-
30-
- **"action"**: Similar to Git's command-line, the `push` token acts as sub-command with its own unique set of parameters. This means that **global parameters** come before the action and affect all actions, whereas **action parameters** come after the action and only affect that action.
26+
- The **tool name** in this example is `widget`. This is the name of your Node.js bin script.
27+
- The **parameters** are `--verbose`, `--force`, and `--max-count`.
28+
- The currently supported **parameter kinds** include: **flag** (i.e. boolean), **integer**, **string**, **choice** (i.e. enums), and **string list**.
29+
- The value "123" is the **argument** for the `--max-count` integer parameter. (Flags don't have arguments, because their value is determined by whether the flag was provided or not.)
30+
- Similar to Git's command-line, the `push` token is called an **action**. It acts as sub-command with its own unique set of parameters.
31+
- The `--verbose` flag is a **global parameter** because it precedes the action name. It affects all actions.
32+
- The `--force` flag is an **action parameter** because it comes after the action name. It only applies to that action.
3133

3234

3335
## Scaffold Model
3436

35-
The scaffold model works by extending the abstract base classes `CommandLineParser` (for the overall command-line) and `CommandLineAction` for a specific subcommand.
37+
If your tool uses the scaffold model, you will create subclasses of two abstract base classes: `CommandLineParser` for the overall command-line, and `CommandLineAction` for each action.
3638

3739
Continuing our example from above, suppose we want to start with a couple simple flags like this:
3840

3941
```
4042
widget --verbose push --force
4143
```
4244

43-
We could define a subclass for the "`push`" action like this:
45+
We could define our subclass for the "`push`" action like this:
4446

4547
```typescript
4648
class PushAction extends CommandLineAction {
@@ -50,7 +52,7 @@ class PushAction extends CommandLineAction {
5052
super({
5153
actionName: 'push',
5254
summary: 'Pushes a widget to the service',
53-
documentation: 'More detail about the "push" action'
55+
documentation: 'Your long description goes here.'
5456
});
5557
}
5658

@@ -61,6 +63,7 @@ class PushAction extends CommandLineAction {
6163
protected onDefineParameters(): void { // abstract
6264
this._force = this.defineFlagParameter({
6365
parameterLongName: '--force',
66+
parameterShortName: '-f',
6467
description: 'Push and overwrite any existing state'
6568
});
6669
}
@@ -76,7 +79,7 @@ class WidgetCommandLine extends CommandLineParser {
7679
public constructor() {
7780
super({
7881
toolFilename: 'widget',
79-
toolDescription: 'Documentation for the "widget" tool'
82+
toolDescription: 'The widget tool is really great.'
8083
});
8184

8285
this.addAction(new PushAction());
@@ -85,6 +88,7 @@ class WidgetCommandLine extends CommandLineParser {
8588
protected onDefineParameters(): void { // abstract
8689
this._verbose = this.defineFlagParameter({
8790
parameterLongName: '--verbose',
91+
parameterShortName: '-v',
8892
description: 'Show extra logging detail'
8993
});
9094
}
@@ -100,61 +104,62 @@ To invoke the parser, the application entry point will do something like this:
100104

101105
```typescript
102106
const commandLine: WidgetCommandLine = new WidgetCommandLine();
103-
commandLine.execute();
107+
commandLine.execute(process.argv);
104108
```
105109

106-
When we run `widget --verbose push --force`, the `PushAction.onExecute()` method will get invoked and your business logic takes over.
110+
When we run `widget --verbose push --force`, the `PushAction.onExecute()` method will get invoked and then your business logic takes over.
107111

108112

109113
#### Testing out the docs
110114

111115
If you invoke the tool as "`widget --help`", the docs are automatically generated:
112116

113117
```
114-
usage: widget [-h] [--verbose] <command> ...
118+
usage: widget [-h] [-v] <command> ...
115119
116-
Documentation for the "widget" tool
120+
The widget tool is really great.
117121
118122
Positional arguments:
119123
<command>
120-
push Pushes a widget to the service
124+
push Pushes a widget to the service
121125
122126
Optional arguments:
123-
-h, --help Show this help message and exit.
124-
--verbose Show extra logging detail
127+
-h, --help Show this help message and exit.
128+
-v, --verbose Show extra logging detail
125129
126130
For detailed help about a specific command, use: widget <command> -h
127131
```
128132

129133
For help about the `push` action, the user can type "`widget push --help`", which shows this output:
130134

131135
```
132-
usage: widget push [-h] [--force]
136+
usage: widget push [-h] [-f]
133137
134-
More detail about the "push" action
138+
Your long description goes here.
135139
136140
Optional arguments:
137-
-h, --help Show this help message and exit.
138-
--force Push and overwrite any existing state
141+
-h, --help Show this help message and exit.
142+
-f, --force Push and overwrite any existing state
139143
```
140144

141145
## Dynamic Model
142146

143-
Creating subclasses provides a simple, recognizable pattern that you can use across all your tooling projects. It's the generally recommended approach. However, there are some cases where we need to break out of the scaffold. For example:
147+
The action subclasses provide a simple, recognizable pattern that you can use across all your tooling projects. It's the generally recommended approach. However, there are some cases where we need to break out of the scaffold. For example:
144148

145-
- Actions or parameters are discovered at runtime, e.g. from a config file
146-
- The actions and their implementations aren't closely coupled
149+
- Actions or parameters may be discovered at runtime, e.g. from a config file
150+
- The actions and their implementations may sometimes have very different structures
147151

148152
In this case, you can use the `DynamicCommandLineAction` and `DynamicCommandLineParser` classes which are not abstract (and not intended to be subclassed). Here's our above example rewritten for this model:
149153

150154
```typescript
151155
// Define the parser
152156
const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({
153157
toolFilename: 'widget',
154-
toolDescription: 'Documentation for the "widget" tool'
158+
toolDescription: 'The widget tool is really great.'
155159
});
156160
commandLineParser.defineFlagParameter({
157161
parameterLongName: '--verbose',
162+
parameterShortName: '-v',
158163
description: 'Show extra logging detail'
159164
});
160165

@@ -168,11 +173,12 @@ commandLineParser.addAction(action);
168173

169174
action.defineFlagParameter({
170175
parameterLongName: '--force',
176+
parameterShortName: '-f',
171177
description: 'Push and overwrite any existing state'
172178
});
173179

174180
// Parse the command line
175-
commandLineParser.execute().then(() => {
181+
commandLineParser.execute(process.argv).then(() => {
176182
console.log('The action is: ' + commandLineParser.selectedAction!.actionName);
177183
console.log('The force flag is: ' + action.getFlagParameter('--force').value);
178184
});
@@ -181,9 +187,11 @@ commandLineParser.execute().then(() => {
181187
You can also mix the two models. For example, we could augment the `WidgetCommandLine` from the original model by adding `DynamicAction` objects to it.
182188

183189

184-
### Real world examples
190+
### Further reading
191+
192+
The [API reference](http://rushstack.io/api/ts-command-line.html) has complete documentation for the library.
185193

186-
Here are some GitHub projects that illustrate different use cases for **ts-command-line**:
194+
Here are some real world GitHub projects that illustrate different use cases for **ts-command-line**:
187195

188196
- [@microsoft/rush](https://www.npmjs.com/package/@microsoft/rush)
189197
- [@microsoft/api-extractor](https://www.npmjs.com/package/@microsoft/api-extractor)

0 commit comments

Comments
 (0)