You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: libraries/ts-command-line/README.md
+41-33Lines changed: 41 additions & 33 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,16 +1,16 @@
1
1
# ts-command-line
2
2
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:
4
4
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.
6
6
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.
8
8
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]*
10
10
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.)
12
12
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!
In this example, we can identify the following components:
25
25
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.
31
33
32
34
33
35
## Scaffold Model
34
36
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.
36
38
37
39
Continuing our example from above, suppose we want to start with a couple simple flags like this:
38
40
39
41
```
40
42
widget --verbose push --force
41
43
```
42
44
43
-
We could define a subclass for the "`push`" action like this:
45
+
We could define our subclass for the "`push`" action like this:
44
46
45
47
```typescript
46
48
classPushActionextendsCommandLineAction {
@@ -50,7 +52,7 @@ class PushAction extends CommandLineAction {
50
52
super({
51
53
actionName: 'push',
52
54
summary: 'Pushes a widget to the service',
53
-
documentation: 'More detail about the "push" action'
55
+
documentation: 'Your long description goes here.'
54
56
});
55
57
}
56
58
@@ -61,6 +63,7 @@ class PushAction extends CommandLineAction {
61
63
protected onDefineParameters():void { // abstract
62
64
this._force=this.defineFlagParameter({
63
65
parameterLongName: '--force',
66
+
parameterShortName: '-f',
64
67
description: 'Push and overwrite any existing state'
65
68
});
66
69
}
@@ -76,7 +79,7 @@ class WidgetCommandLine extends CommandLineParser {
76
79
publicconstructor() {
77
80
super({
78
81
toolFilename: 'widget',
79
-
toolDescription: 'Documentation for the "widget" tool'
82
+
toolDescription: 'The widget tool is really great.'
80
83
});
81
84
82
85
this.addAction(newPushAction());
@@ -85,6 +88,7 @@ class WidgetCommandLine extends CommandLineParser {
85
88
protected onDefineParameters():void { // abstract
86
89
this._verbose=this.defineFlagParameter({
87
90
parameterLongName: '--verbose',
91
+
parameterShortName: '-v',
88
92
description: 'Show extra logging detail'
89
93
});
90
94
}
@@ -100,61 +104,62 @@ To invoke the parser, the application entry point will do something like this:
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.
107
111
108
112
109
113
#### Testing out the docs
110
114
111
115
If you invoke the tool as "`widget --help`", the docs are automatically generated:
112
116
113
117
```
114
-
usage: widget [-h] [--verbose] <command> ...
118
+
usage: widget [-h] [-v] <command> ...
115
119
116
-
Documentation for the "widget" tool
120
+
The widget tool is really great.
117
121
118
122
Positional arguments:
119
123
<command>
120
-
push Pushes a widget to the service
124
+
push Pushes a widget to the service
121
125
122
126
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
125
129
126
130
For detailed help about a specific command, use: widget <command> -h
127
131
```
128
132
129
133
For help about the `push` action, the user can type "`widget push --help`", which shows this output:
130
134
131
135
```
132
-
usage: widget push [-h] [--force]
136
+
usage: widget push [-h] [-f]
133
137
134
-
More detail about the "push" action
138
+
Your long description goes here.
135
139
136
140
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
139
143
```
140
144
141
145
## Dynamic Model
142
146
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:
144
148
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
147
151
148
152
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:
You can also mix the two models. For example, we could augment the `WidgetCommandLine` from the original model by adding `DynamicAction` objects to it.
182
188
183
189
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.
185
193
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**:
0 commit comments