command-line-director 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG +10 -0
- package/LICENSE +17 -0
- package/README.md +158 -0
- package/lib/.DS_Store +0 -0
- package/lib/command-line-argument-data-type.js +7 -0
- package/lib/command-line-argument-factory.js +86 -0
- package/lib/command-line-argument-type.js +6 -0
- package/lib/command-line-argument.js +84 -0
- package/lib/command-line-director.js +83 -0
- package/lib/command-line.js +125 -0
- package/lib/command.js +9 -0
- package/package.json +44 -0
- package/test/.DS_Store +0 -0
- package/test/integration-test.js +198 -0
package/CHANGELOG
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2016 Marco Jonker
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
5
|
+
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
6
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
7
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
11
|
+
portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
14
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
15
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
16
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
17
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# COMMAND LINE DIRECTOR #
|
|
2
|
+
|
|
3
|
+
The command line director give direction to you command line arguments. With the command line director you can configure, validate and direct the command line argument supported by your nodejs command line application.
|
|
4
|
+
|
|
5
|
+
# QUICKSTART #
|
|
6
|
+
|
|
7
|
+
**STEP 1:** Install
|
|
8
|
+
```
|
|
9
|
+
npm install command-line-director
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**STEP 2:** Add the command line director to your application (see chapter 'sample')
|
|
13
|
+
|
|
14
|
+
**STEP 3:** Run your application
|
|
15
|
+
|
|
16
|
+
# CONFIGURATION #
|
|
17
|
+
|
|
18
|
+
The configuration the the command line director exists of 3 classes. The CommandLineDirector that contains a set of CommandLines. Each CommandLine exists of one or more CommandLineArguments.
|
|
19
|
+
|
|
20
|
+
## Configuring the CommandLineDirector
|
|
21
|
+
|
|
22
|
+
The command line direction the class that contains all the application's command line and maps the input argument to a specific Command. It also contain a title and a description of the application, which is printed a the top of the generated help. For an example see the chapter 'sample'. The CommandLineDirector has 2 public functions:
|
|
23
|
+
|
|
24
|
+
* parse(verbose) - To parse the current command line arguments to a specific command. If verbose is true information about why a command is not found is printen at stdout
|
|
25
|
+
* generateHelp - To Generate a help text
|
|
26
|
+
|
|
27
|
+
## Configuring a CommandLine
|
|
28
|
+
|
|
29
|
+
A command line the configuration of a set of arguments that defines a specific command. It also contains a title and a description of a CommandLine, which is printed in the generated Help. For an example see the chapter 'sample'.
|
|
30
|
+
|
|
31
|
+
## Configuring a CommandLineArgument ##
|
|
32
|
+
|
|
33
|
+
The most used types of argument can be defined using the CommandLineArgument factory. The factory can be used to create a key value argument, a flag argument and a value argument.
|
|
34
|
+
|
|
35
|
+
### Key value argument ###
|
|
36
|
+
|
|
37
|
+
A key value argument is a string argument with a key. The key value argument can be define with the following properties:
|
|
38
|
+
|
|
39
|
+
* propertyName - Name of the property that will contain the value when the command is parsed
|
|
40
|
+
* description - Description of the flag that is displayed in the command line help
|
|
41
|
+
* argumentName - Name of the argument in the command line (exmaple: '--test-flag')
|
|
42
|
+
* alias - Alias for the name of the argument (example: '-tf')
|
|
43
|
+
* required - Value is required or optional
|
|
44
|
+
* allowedValues - Array of allowed values
|
|
45
|
+
* regularExpression - regular expression for validating the value
|
|
46
|
+
|
|
47
|
+
Sample of creating a value argument:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
const argumentFactory = new CommandLineArgumentFactory()
|
|
51
|
+
argumentFactory.keyValueArgument('testValue', 'Used for testing', true, '--test-value', '-tv', ['test1', 'test2'], new RegEx('^A-Z*$'))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Flag argument ###
|
|
55
|
+
|
|
56
|
+
A flag argument a boolean argument. If a flag is present in the command line argument the boolean value will be set to true. The flag argument can be define with the following properties:
|
|
57
|
+
|
|
58
|
+
* propertyName - Name of the property that will contain the value when the command is parsed
|
|
59
|
+
* description - Description of the flag that is displayed in the command line help
|
|
60
|
+
* argumentName - Name of the argument in the command line (exmaple: '--test-flag')
|
|
61
|
+
* alias - Alias for the name of the argument (example: '-tf')
|
|
62
|
+
|
|
63
|
+
Sample of creating a flag argument:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
const argumentFactory = new CommandLineArgumentFactory()
|
|
67
|
+
argumentFactory.flagArgument('testFlag', 'Used for testing', '--test-flag', '-tf')
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Value argument ###
|
|
71
|
+
|
|
72
|
+
A value argument is a string argument without a key. A value argument can be detected by allowed values, a regex or just by value. A usecase can be passing a file path a last argument. The value argument can be define with the following properties:
|
|
73
|
+
|
|
74
|
+
* propertyName - Name of the property that will contain the value when the command is parsed
|
|
75
|
+
* description - Description of the flag that is displayed in the command line help
|
|
76
|
+
* required - Value is required or optional
|
|
77
|
+
* allowedValues - Array of allowed values
|
|
78
|
+
* regularExpression - regular expression for validating the value
|
|
79
|
+
|
|
80
|
+
Sample of creating a value argument:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
const argumentFactory = new CommandLineArgumentFactory()
|
|
84
|
+
argumentFactory.valueArgument('testValue', 'Used for testing', true, ['test1', 'test2'], new RegEx('^A-Z*$'))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
# Sample #
|
|
88
|
+
This is an example that can handle the following commands:
|
|
89
|
+
* node ./samples/app ?
|
|
90
|
+
* node ./samples/app.js open "from-path"
|
|
91
|
+
* node ./samples/app.js cf --from="from-path" -to="to-path"
|
|
92
|
+
* node ./samples/app.js cf --remove-source --from="from-path" -to="to-path"
|
|
93
|
+
* node ./samples/app.js cf -rs -f="from-path" -t="to-path"
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
const CommandLineDirector = require('../lib/command-line-director')
|
|
97
|
+
const CommandLine = require('../lib/command-line')
|
|
98
|
+
const CommandLineArgumentFactory = require('../lib/command-line-argument-factory')
|
|
99
|
+
|
|
100
|
+
class App {
|
|
101
|
+
constructor() {
|
|
102
|
+
const argumentFactory = new CommandLineArgumentFactory()
|
|
103
|
+
|
|
104
|
+
const commandLines = [
|
|
105
|
+
// node ./samples/app ?
|
|
106
|
+
new CommandLine('help-identifier', 'Help', 'Show help', [
|
|
107
|
+
argumentFactory.valueArgument('command', '?', true, ['?']),
|
|
108
|
+
]),
|
|
109
|
+
// node ./samples/app.js open "from-path"
|
|
110
|
+
new CommandLine('open-identifier', 'Open', 'Open a file', [
|
|
111
|
+
argumentFactory.valueArgument('command', 'Open command', true, ['open']),
|
|
112
|
+
argumentFactory.valueArgument('fileName', 'Name of the file to open', true),
|
|
113
|
+
]),
|
|
114
|
+
// node ./samples/app.js cf --from="from-path" -to="to-path"
|
|
115
|
+
// node ./samples/app.js cf --remove-source --from="from-path" -to="to-path"
|
|
116
|
+
// node ./samples/app.js cf -rs -f="from-path" -t="to-path"
|
|
117
|
+
new CommandLine('copy-file-identifier', 'Copy', 'Copy a file', [
|
|
118
|
+
argumentFactory.valueArgument('command', 'Copy file command', true, ['cf']),
|
|
119
|
+
argumentFactory.flagArgument('removeSource', 'Remove the source file', '--remove-source', '-rs'),
|
|
120
|
+
argumentFactory.keyValueArgument('from', 'From path', true, '--from', '-f'),
|
|
121
|
+
argumentFactory.keyValueArgument('to', 'To path', true, '--to', '-t'),
|
|
122
|
+
]),
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
this.commandLineDirector = new CommandLineDirector(
|
|
126
|
+
'File functions',
|
|
127
|
+
'A coomand line tool for special file operations',
|
|
128
|
+
commandLines)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
run() {
|
|
132
|
+
const command = this.commandLineDirector.parse()
|
|
133
|
+
|
|
134
|
+
if(command) {
|
|
135
|
+
switch(command.identifier) {
|
|
136
|
+
case 'help-identifier':
|
|
137
|
+
console.log(this.commandLineDirector.generateHelp())
|
|
138
|
+
break
|
|
139
|
+
case 'open-identifier':
|
|
140
|
+
console.log(`Open a file with values: ${JSON.stringify(command.values)}`)
|
|
141
|
+
break
|
|
142
|
+
case 'copy-file-identifier':
|
|
143
|
+
console.log(`Copy a file with values: ${JSON.stringify(command.values)}`)
|
|
144
|
+
break
|
|
145
|
+
default:
|
|
146
|
+
console.error('unknown command')
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.error('unknown command')
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const app = new App()
|
|
156
|
+
app.run()
|
|
157
|
+
```
|
|
158
|
+
|
package/lib/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const CommandLineArgumentDataType = require('./command-line-argument-data-type')
|
|
2
|
+
const CommandLineArgumentType = require('./command-line-argument-type')
|
|
3
|
+
const CommandLineArgument = require('./command-line-argument')
|
|
4
|
+
|
|
5
|
+
class CommandLineArgumentFactory {
|
|
6
|
+
/**
|
|
7
|
+
* Create string argument
|
|
8
|
+
* Examples:
|
|
9
|
+
* --dir="/var/www/test"
|
|
10
|
+
* -d="/var/www/test"
|
|
11
|
+
*
|
|
12
|
+
* @param propertyName - string
|
|
13
|
+
* @param description - string
|
|
14
|
+
* @param required - boolean
|
|
15
|
+
* @param argumentName - string
|
|
16
|
+
* @param alias - string
|
|
17
|
+
* @param defaultValue - string
|
|
18
|
+
* @param allowedValues
|
|
19
|
+
* @param regularExpression
|
|
20
|
+
*/
|
|
21
|
+
keyValueArgument(propertyName, description, required, argumentName, alias, defaultValue, allowedValues, regularExpression) {
|
|
22
|
+
return new CommandLineArgument(
|
|
23
|
+
propertyName,
|
|
24
|
+
required,
|
|
25
|
+
argumentName,
|
|
26
|
+
alias,
|
|
27
|
+
CommandLineArgumentDataType.String,
|
|
28
|
+
CommandLineArgumentType.KeyValue,
|
|
29
|
+
defaultValue,
|
|
30
|
+
allowedValues,
|
|
31
|
+
regularExpression,
|
|
32
|
+
description)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create boolean argument
|
|
37
|
+
* Examples:
|
|
38
|
+
* --force
|
|
39
|
+
* -f
|
|
40
|
+
*
|
|
41
|
+
* @param propertyName - string
|
|
42
|
+
* @param description - string
|
|
43
|
+
* @param argumentName - string
|
|
44
|
+
* @param alias - string
|
|
45
|
+
*/
|
|
46
|
+
flagArgument(propertyName, description, argumentName, alias) {
|
|
47
|
+
return new CommandLineArgument(
|
|
48
|
+
propertyName,
|
|
49
|
+
false,
|
|
50
|
+
argumentName,
|
|
51
|
+
alias,
|
|
52
|
+
CommandLineArgumentDataType.Boolean,
|
|
53
|
+
CommandLineArgumentType.KeyValue,
|
|
54
|
+
false,
|
|
55
|
+
null,
|
|
56
|
+
null,
|
|
57
|
+
description)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create command
|
|
62
|
+
* Examples:
|
|
63
|
+
* init
|
|
64
|
+
*
|
|
65
|
+
* @param propertyName - string
|
|
66
|
+
* @param description
|
|
67
|
+
* @param required - boolean
|
|
68
|
+
* @param allowedValues
|
|
69
|
+
* @param regularExpression
|
|
70
|
+
*/
|
|
71
|
+
valueArgument(propertyName, description, required, allowedValues, regularExpression) {
|
|
72
|
+
return new CommandLineArgument(
|
|
73
|
+
propertyName,
|
|
74
|
+
required,
|
|
75
|
+
null,
|
|
76
|
+
null,
|
|
77
|
+
CommandLineArgumentDataType.String,
|
|
78
|
+
CommandLineArgumentType.Value,
|
|
79
|
+
null,
|
|
80
|
+
allowedValues,
|
|
81
|
+
regularExpression,
|
|
82
|
+
description)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = CommandLineArgumentFactory
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const CommandLineArgumentDataType = require('./command-line-argument-data-type')
|
|
2
|
+
const CommandLineArgumentType = require('./command-line-argument-type')
|
|
3
|
+
|
|
4
|
+
class CommandLineArgument {
|
|
5
|
+
/**
|
|
6
|
+
* CommandLineArgument Constructor
|
|
7
|
+
* @param propertyName - string
|
|
8
|
+
* @param required - boolean
|
|
9
|
+
* @param argumentName - string
|
|
10
|
+
* @param alias - string
|
|
11
|
+
* @param dataType - CommandLineArgumentDataType
|
|
12
|
+
* @param type - CommandLineArgumentType
|
|
13
|
+
* @param defaultValue
|
|
14
|
+
* @param allowedValues
|
|
15
|
+
* @param regularExpression
|
|
16
|
+
* @param description
|
|
17
|
+
*/
|
|
18
|
+
constructor (propertyName, required, argumentName, alias, dataType, type, defaultValue, allowedValues, regularExpression, description) {
|
|
19
|
+
if (!propertyName) {
|
|
20
|
+
throw new Error("'propertyName' parameter not defined.")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (required === undefined || required === null) {
|
|
24
|
+
throw new Error("'required' parameter not defined.")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (type !== CommandLineArgumentType.KeyValue && type !== CommandLineArgumentType.Value) {
|
|
28
|
+
throw new Error("'type' parameter invalid.")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!argumentName && type === CommandLineArgumentType.KeyValue) {
|
|
32
|
+
throw new Error("'argumentName' parameter not defined.")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (allowedValues && Array.isArray(allowedValues) === false) {
|
|
36
|
+
throw new Error("'allowedValues' parameter should be an array.")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.propertyName = propertyName
|
|
40
|
+
this.required = Boolean(required)
|
|
41
|
+
this.argumentName = argumentName
|
|
42
|
+
this.alias = (alias !== undefined) ? alias : null
|
|
43
|
+
this.dataType = dataType || CommandLineArgumentDataType.String
|
|
44
|
+
this.type = type
|
|
45
|
+
this.defaultValue = defaultValue
|
|
46
|
+
this.allowedValues = allowedValues || null
|
|
47
|
+
this.regularExpression = regularExpression || null
|
|
48
|
+
this.description = description
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
toString () {
|
|
52
|
+
let help = ''
|
|
53
|
+
let title = ''
|
|
54
|
+
|
|
55
|
+
if (this.argumentName) {
|
|
56
|
+
title = ` ${this.argumentName},${this.alias}`
|
|
57
|
+
} else if (this.allowedValues) {
|
|
58
|
+
title = ` ${this.allowedValues}`
|
|
59
|
+
} else {
|
|
60
|
+
title = ' <string>'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
help += title
|
|
64
|
+
for (let i = 0; i < 24 - title.length; i++) {
|
|
65
|
+
help += ' '
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
help += ` ${this.required ? 'R' : 'O'} | ${this.dataType}`
|
|
69
|
+
|
|
70
|
+
if (this.argumentName && this.allowedValues) {
|
|
71
|
+
help += ` | VALUES = ${this.allowedValues}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.regularExpression) {
|
|
75
|
+
help += ` | PATTERN = ${this.regularExpression.toString()}`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
help += ` | ${this.description}`
|
|
79
|
+
|
|
80
|
+
return help
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = CommandLineArgument
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const Command = require('./command')
|
|
2
|
+
|
|
3
|
+
class CommandLineDirector {
|
|
4
|
+
constructor (title, description, commandLines) {
|
|
5
|
+
this.title = title
|
|
6
|
+
this.description = description
|
|
7
|
+
if (Array.isArray(commandLines)) {
|
|
8
|
+
this.commandLines = commandLines
|
|
9
|
+
} else {
|
|
10
|
+
throw new Error("Invalid argument 'commandLines', value should be of type 'array'")
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_removeQuotes (text) {
|
|
15
|
+
return text.replace(/['"]+/g, '')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_createArgumentLookupFromProcessArguments (cmdArguments) {
|
|
19
|
+
const lookup = {
|
|
20
|
+
values: [],
|
|
21
|
+
keyValues: {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
cmdArguments.forEach((cmdArgument) => {
|
|
25
|
+
const keyValue = cmdArgument.split('=')
|
|
26
|
+
// Flag of key value type
|
|
27
|
+
if (typeof keyValue[0] === 'string' && keyValue[0].charAt(0) === '-') {
|
|
28
|
+
const value = keyValue.length === 2 ? this._removeQuotes(keyValue[1]) : true
|
|
29
|
+
const keyName = keyValue[0]
|
|
30
|
+
lookup.keyValues[keyName] = value
|
|
31
|
+
// Value type
|
|
32
|
+
} else {
|
|
33
|
+
lookup.values.push(this._removeQuotes(cmdArgument))
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return lookup
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
parseArguments (cmdArguments, verbose) {
|
|
41
|
+
const lookup = this._createArgumentLookupFromProcessArguments(cmdArguments)
|
|
42
|
+
let command = null
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < this.commandLines.length; i++) {
|
|
45
|
+
const commandLine = this.commandLines[i]
|
|
46
|
+
try {
|
|
47
|
+
command = commandLine.commandFromLookup(lookup)
|
|
48
|
+
break
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (verbose) {
|
|
51
|
+
console.info(error.message)
|
|
52
|
+
}
|
|
53
|
+
command = null
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return command
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
parse (verbose) {
|
|
61
|
+
return this.parseArguments(process.argv.slice(2), verbose)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
generateHelp () {
|
|
65
|
+
let help = '\r\n=======================================================================\r\n'
|
|
66
|
+
help += `\r\n${this.title.toUpperCase()}\r\n${this.description}\r\n\r\n`
|
|
67
|
+
|
|
68
|
+
this.commandLines.forEach((commandLine) => {
|
|
69
|
+
help += '-----------------------------------------------------------------------\r\n'
|
|
70
|
+
help += `${commandLine.title.toUpperCase()} - ${commandLine.description}\r\n\r\n`
|
|
71
|
+
|
|
72
|
+
commandLine.commandLineArguments.forEach((argument) => {
|
|
73
|
+
help += `${argument.toString()}\r\n`
|
|
74
|
+
})
|
|
75
|
+
help += '\r\n'
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
help += '=======================================================================\r\n'
|
|
79
|
+
return help
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = CommandLineDirector
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const CommandLineArgumentDataType = require('./command-line-argument-data-type')
|
|
2
|
+
const CommandLineArgumentType = require('./command-line-argument-type')
|
|
3
|
+
const Command = require('./command')
|
|
4
|
+
|
|
5
|
+
class CommandLine {
|
|
6
|
+
/**
|
|
7
|
+
* CommandLine constructor
|
|
8
|
+
* @param identifier - string
|
|
9
|
+
* @param commandLineArguments - array
|
|
10
|
+
* @thows Error
|
|
11
|
+
*/
|
|
12
|
+
constructor (identifier, title, description, commandLineArguments) {
|
|
13
|
+
this.identifier = identifier
|
|
14
|
+
this.title = title
|
|
15
|
+
this.description = description
|
|
16
|
+
this.commandLineArguments = []
|
|
17
|
+
if (Array.isArray(commandLineArguments)) {
|
|
18
|
+
this.commandLineArguments = commandLineArguments
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error("Invalid argument 'commandLineArguments', value should be of type 'array'")
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create lookup table for process arguments
|
|
26
|
+
* @result object
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
_parseDataType (value, dataType) {
|
|
30
|
+
let parsedValue = null
|
|
31
|
+
|
|
32
|
+
if (value !== undefined && value !== null) {
|
|
33
|
+
switch (dataType) {
|
|
34
|
+
case CommandLineArgumentDataType.Boolean:
|
|
35
|
+
parsedValue = Boolean(value)
|
|
36
|
+
break
|
|
37
|
+
case CommandLineArgumentDataType.Number:
|
|
38
|
+
parsedValue = Number(value)
|
|
39
|
+
if (isNaN(parsedValue)) {
|
|
40
|
+
parsedValue = null
|
|
41
|
+
}
|
|
42
|
+
break
|
|
43
|
+
case CommandLineArgumentDataType.String:
|
|
44
|
+
parsedValue = String(value)
|
|
45
|
+
break
|
|
46
|
+
default: {
|
|
47
|
+
throw new Error(`Unknown data type '${dataType}.`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (parsedValue === null) {
|
|
52
|
+
throw new Error(`Could not parse value '${value}' to data type '${dataType}.`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return parsedValue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create command from argument lookup
|
|
61
|
+
* @param argument lookup
|
|
62
|
+
* @result command
|
|
63
|
+
* @throws Error
|
|
64
|
+
*/
|
|
65
|
+
commandFromLookup (argumentLookup) {
|
|
66
|
+
const result = {}
|
|
67
|
+
const values = argumentLookup.values
|
|
68
|
+
const keyValues = argumentLookup.keyValues
|
|
69
|
+
let valueIndex = 0
|
|
70
|
+
|
|
71
|
+
// Check all the arguments
|
|
72
|
+
this.commandLineArguments.forEach((arg) => {
|
|
73
|
+
if (arg.type === CommandLineArgumentType.KeyValue) {
|
|
74
|
+
if (keyValues[arg.argumentName] !== undefined) {
|
|
75
|
+
result[arg.propertyName] = keyValues[arg.argumentName]
|
|
76
|
+
} else if (keyValues[arg.alias] !== undefined) {
|
|
77
|
+
result[arg.propertyName] = keyValues[arg.alias]
|
|
78
|
+
}
|
|
79
|
+
} else if (arg.type === CommandLineArgumentType.Value) {
|
|
80
|
+
if (values.length > valueIndex) {
|
|
81
|
+
result[arg.propertyName] = values[valueIndex]
|
|
82
|
+
valueIndex++
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error('Invalid command line argument type')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Set the default value if avaiable
|
|
89
|
+
if (result[arg.propertyName] === undefined && arg.defaultValue !== undefined) {
|
|
90
|
+
result[arg.propertyName] = arg.defaultValue
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Parse data to the correct data type
|
|
94
|
+
if (result[arg.propertyName] !== undefined) {
|
|
95
|
+
result[arg.propertyName] = this._parseDataType(result[arg.propertyName], arg.dataType)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Check all the arguments
|
|
100
|
+
this.commandLineArguments.forEach(function (arg) {
|
|
101
|
+
// Check if required field exists
|
|
102
|
+
if (arg.required === true && (result[arg.propertyName] === undefined || result[arg.propertyName] === null)) {
|
|
103
|
+
throw new Error(`Required field '${arg.propertyName}' is missing.`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if value is allowed
|
|
107
|
+
if (arg.allowedValues && (arg.required === true || (result[arg.propertyName] !== undefined && result[arg.propertyName] !== null))) {
|
|
108
|
+
if (arg.allowedValues.indexOf(result[arg.propertyName]) === -1) {
|
|
109
|
+
throw new Error(`Value for field '${arg.propertyName}' is not allowed.`)
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Check if value meets regex
|
|
114
|
+
if (arg.regularExpression && (arg.required === true || (result[arg.propertyName] !== undefined && result[arg.propertyName] !== null))) {
|
|
115
|
+
if (!arg.regularExpression.test(result[arg.propertyName])) {
|
|
116
|
+
throw new Error(`Value for field field '${arg.propertyName}' is invalid.`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return new Command(this.identifier, this.commandLineArguments, result)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = CommandLine
|
package/lib/command.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "command-line-director",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Gives direction to you nodejs command line application",
|
|
5
|
+
"main": "./lib/command-line-director.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"command line",
|
|
8
|
+
"command line parser",
|
|
9
|
+
"command line mapper",
|
|
10
|
+
"command line arguments",
|
|
11
|
+
"arguments"
|
|
12
|
+
],
|
|
13
|
+
"bugs": "https://github.com/marcojonker/command-line-director/issues",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "./node_modules/mocha/bin/mocha"
|
|
17
|
+
},
|
|
18
|
+
"author": "Marco Jonker",
|
|
19
|
+
"files": [
|
|
20
|
+
"package.json",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"CHANGELOG",
|
|
24
|
+
"lib",
|
|
25
|
+
"test"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/marcojonker/command-line-director.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "http://www.cacadu.eu/opensource",
|
|
32
|
+
"dependencies": {},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"eslint": "^7.32.0",
|
|
35
|
+
"eslint-config-standard": "^16.0.3",
|
|
36
|
+
"eslint-plugin-import": "^2.24.2",
|
|
37
|
+
"eslint-plugin-node": "^11.1.0",
|
|
38
|
+
"eslint-plugin-promise": "^5.1.0",
|
|
39
|
+
"mocha": "^9.1.1"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=12.10.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/test/.DS_Store
ADDED
|
Binary file
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
var assert = require('assert');
|
|
2
|
+
const CommandLineDirector = require('../lib/command-line-director')
|
|
3
|
+
const CommandLine = require('../lib/command-line')
|
|
4
|
+
const CommandLineArgumentFactory = require('../lib/command-line-argument-factory')
|
|
5
|
+
|
|
6
|
+
describe('Integration test', function () {
|
|
7
|
+
const argumentFactory = new CommandLineArgumentFactory();
|
|
8
|
+
|
|
9
|
+
it('should handle string key value arguments correct', () => {
|
|
10
|
+
const commandLines = [
|
|
11
|
+
new CommandLine('test1', 'title1', 'title1 description', [
|
|
12
|
+
argumentFactory.keyValueArgument('param1', 'param1 description', true, '--param1', '-p1'),
|
|
13
|
+
argumentFactory.keyValueArgument('param2', 'param2 description', false, '--param2', '-p2', 'defaultParam2', ['defaultParam2', 'test']),
|
|
14
|
+
argumentFactory.keyValueArgument('param3', 'param3 description', false, '--param3', '-p3', '100', null, new RegExp('^[0-9]*$')),
|
|
15
|
+
argumentFactory.keyValueArgument('param4', 'param4 description', false, '--param4', '-p4')
|
|
16
|
+
]),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const commandLineDirector = new CommandLineDirector('title', 'description', commandLines)
|
|
20
|
+
|
|
21
|
+
let command = commandLineDirector.parseArguments([''], true)
|
|
22
|
+
assert.equal(command, null);
|
|
23
|
+
|
|
24
|
+
// required value set
|
|
25
|
+
command = commandLineDirector.parseArguments(['--param1=test'], true)
|
|
26
|
+
assert.equal(command.values['param1'], 'test');
|
|
27
|
+
assert.equal(command.values['param2'], 'defaultParam2');
|
|
28
|
+
assert.equal(command.values['param3'], '100');
|
|
29
|
+
assert.equal(command.values['param4'], undefined);
|
|
30
|
+
|
|
31
|
+
// allwed values invalid
|
|
32
|
+
command = commandLineDirector.parseArguments(['--param1=test', '--param2=invalid'], true)
|
|
33
|
+
assert.equal(command, null);
|
|
34
|
+
|
|
35
|
+
// allwed values valid
|
|
36
|
+
command = commandLineDirector.parseArguments(['--param1=test', '--param2=test'], true)
|
|
37
|
+
assert.equal(command.values['param1'], 'test');
|
|
38
|
+
assert.equal(command.values['param2'], 'test');
|
|
39
|
+
|
|
40
|
+
// regex invalid
|
|
41
|
+
command = commandLineDirector.parseArguments(['--param1=test', '--param3=test'], true)
|
|
42
|
+
assert.equal(command, null);
|
|
43
|
+
|
|
44
|
+
// regex valid
|
|
45
|
+
command = commandLineDirector.parseArguments(['--param1=test', '--param3=200'], true)
|
|
46
|
+
assert.equal(command.values['param1'], 'test');
|
|
47
|
+
assert.equal(command.values['param3'], '200');
|
|
48
|
+
|
|
49
|
+
// not required param
|
|
50
|
+
command = commandLineDirector.parseArguments(['--param1=test', '--param4=test'], true)
|
|
51
|
+
assert.equal(command.values['param1'], 'test');
|
|
52
|
+
assert.equal(command.values['param4'], 'test');
|
|
53
|
+
|
|
54
|
+
// alias
|
|
55
|
+
command = commandLineDirector.parseArguments(['-p1=test1', '-p2=test', '-p3=300', '-p4=test4'], true)
|
|
56
|
+
assert.equal(command.values['param1'], 'test1');
|
|
57
|
+
assert.equal(command.values['param2'], 'test');
|
|
58
|
+
assert.equal(command.values['param3'], '300');
|
|
59
|
+
assert.equal(command.values['param4'], 'test4');
|
|
60
|
+
|
|
61
|
+
// strip unknown param
|
|
62
|
+
command = commandLineDirector.parseArguments(['-p1=test1', '-p2=test', '-p3=300', '-p4=test4', '--param5=test5'], true)
|
|
63
|
+
assert.equal(command.values['param1'], 'test1');
|
|
64
|
+
assert.equal(command.values['param2'], 'test');
|
|
65
|
+
assert.equal(command.values['param3'], '300');
|
|
66
|
+
assert.equal(command.values['param4'], 'test4');
|
|
67
|
+
assert.equal(command.values['param5'], undefined);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle flag key value arguments correct', () => {
|
|
71
|
+
const commandLines = [
|
|
72
|
+
new CommandLine('test3', 'title3', 'title 3 description', [
|
|
73
|
+
argumentFactory.flagArgument('param1', 'param1 description', '--param1', '-p1')
|
|
74
|
+
]),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
const commandLineDirector = new CommandLineDirector('title', 'description', commandLines)
|
|
78
|
+
|
|
79
|
+
let command = commandLineDirector.parseArguments([''], true)
|
|
80
|
+
assert.equal(command.values['param1'], false);
|
|
81
|
+
|
|
82
|
+
command = commandLineDirector.parseArguments(['--param1'], true)
|
|
83
|
+
assert.equal(command.values['param1'], true);
|
|
84
|
+
|
|
85
|
+
command = commandLineDirector.parseArguments(['-p1'], true)
|
|
86
|
+
assert.equal(command.values['param1'], true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle string value arguments correct', () => {
|
|
90
|
+
const commandLines = [
|
|
91
|
+
new CommandLine('test4', 'title4', 'title 4 description', [
|
|
92
|
+
argumentFactory.valueArgument('param1', 'param1 description', true, ['value1', 'value2']),
|
|
93
|
+
argumentFactory.valueArgument('param2', 'param2 description', true, ['value3', 'value4']),
|
|
94
|
+
argumentFactory.valueArgument('param3', 'param3 description', false, ['value5', 'value6']),
|
|
95
|
+
argumentFactory.valueArgument('param4', 'param4 description', false),
|
|
96
|
+
]),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
const commandLineDirector = new CommandLineDirector('title', 'description', commandLines)
|
|
100
|
+
|
|
101
|
+
let command = commandLineDirector.parseArguments([''], true)
|
|
102
|
+
assert.equal(command, null);
|
|
103
|
+
|
|
104
|
+
command = commandLineDirector.parseArguments(['value1'], true)
|
|
105
|
+
assert.equal(command, null);
|
|
106
|
+
|
|
107
|
+
command = commandLineDirector.parseArguments(['value1', 'value4'], true)
|
|
108
|
+
assert.equal(command.values['param1'], 'value1');
|
|
109
|
+
assert.equal(command.values['param2'], 'value4');
|
|
110
|
+
|
|
111
|
+
command = commandLineDirector.parseArguments(['value1', 'value3', 'value6'], true)
|
|
112
|
+
assert.equal(command.values['param1'], 'value1');
|
|
113
|
+
assert.equal(command.values['param2'], 'value3');
|
|
114
|
+
assert.equal(command.values['param3'], 'value6');
|
|
115
|
+
|
|
116
|
+
command = commandLineDirector.parseArguments(['value1', 'value3', 'value6', '"http://url.test"'], true)
|
|
117
|
+
assert.equal(command.values['param1'], 'value1');
|
|
118
|
+
assert.equal(command.values['param2'], 'value3');
|
|
119
|
+
assert.equal(command.values['param3'], 'value6');
|
|
120
|
+
assert.equal(command.values['param4'], 'http://url.test');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should generate a help text', () => {
|
|
124
|
+
const commandLines = [
|
|
125
|
+
new CommandLine('test1', 'title1', 'title 1 description', [
|
|
126
|
+
argumentFactory.keyValueArgument('param1', 'param1 description', true, '--param1', '-p1'),
|
|
127
|
+
argumentFactory.keyValueArgument('param2', 'param2 description', false, '--param2', '-p2', 'defaultParam2', ['defaultParam2', 'test']),
|
|
128
|
+
argumentFactory.keyValueArgument('param3', 'param3 description', false, '--param3', '-p3', '100', null, new RegExp('^[0-9]*$')),
|
|
129
|
+
argumentFactory.keyValueArgument('param4', 'param4 description', false, '--param4', '-p4')
|
|
130
|
+
]),
|
|
131
|
+
new CommandLine('test2', 'title2', 'title 2 description', [
|
|
132
|
+
argumentFactory.keyValueArgument('param1', 'param1 description', true, '--param1', '-p1'),
|
|
133
|
+
argumentFactory.keyValueArgument('param2', 'param2 description', false, '--param2', '-p2', 200, [200, 201]),
|
|
134
|
+
argumentFactory.keyValueArgument('param3', 'param3 description', false, '--param3', '-p3')
|
|
135
|
+
]),
|
|
136
|
+
new CommandLine('test3', 'title3', 'title 3 description', [
|
|
137
|
+
argumentFactory.flagArgument('param1', 'param1 description', '--param1', '-p1')
|
|
138
|
+
]),
|
|
139
|
+
new CommandLine('test4', 'title4', 'title 4 description', [
|
|
140
|
+
argumentFactory.valueArgument('param1', 'param1 description', true, ['value1', 'value2']),
|
|
141
|
+
argumentFactory.valueArgument('param2', 'param2 description', true, ['value3', 'value4']),
|
|
142
|
+
argumentFactory.valueArgument('param3', 'param3 description', false, ['value5', 'value6']),
|
|
143
|
+
argumentFactory.valueArgument('param4', 'param4 description', false)
|
|
144
|
+
]),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
const commandLineDirector = new CommandLineDirector('title', 'description', commandLines)
|
|
148
|
+
const helpText = commandLineDirector.generateHelp();
|
|
149
|
+
|
|
150
|
+
assert.equal(helpText.includes('TITLE1'), true)
|
|
151
|
+
assert.equal(helpText.includes('TITLE2'), true)
|
|
152
|
+
assert.equal(helpText.includes('TITLE3'), true)
|
|
153
|
+
assert.equal(helpText.includes('TITLE4'), true)
|
|
154
|
+
assert.equal(helpText.includes('title 1 description'), true)
|
|
155
|
+
assert.equal(helpText.includes('param1 descriptio'), true)
|
|
156
|
+
assert.equal(helpText.includes(' R '), true)
|
|
157
|
+
assert.equal(helpText.includes('--param1,-p1'), true)
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should handle realworld example correct', () => {
|
|
161
|
+
const commandLines = [
|
|
162
|
+
new CommandLine('list-identifier', 'List', 'Create a list of items', [
|
|
163
|
+
argumentFactory.valueArgument('command', 'command', true, ['list']),
|
|
164
|
+
argumentFactory.flagArgument('delete', 'delete the list', '--delete', '-d'),
|
|
165
|
+
]),
|
|
166
|
+
new CommandLine('open-identifier', 'Open', 'Open an item', [
|
|
167
|
+
argumentFactory.valueArgument('command', 'command', true, ['open', 'display']),
|
|
168
|
+
argumentFactory.flagArgument('all', 'Open all items', '--all', '-a'),
|
|
169
|
+
argumentFactory.keyValueArgument('id', 'id to open', false, '--identifier', '-id'),
|
|
170
|
+
]),
|
|
171
|
+
new CommandLine('update-identifier', 'Update', 'Updat an item', [
|
|
172
|
+
argumentFactory.valueArgument('command', 'command', true, ['update']),
|
|
173
|
+
argumentFactory.flagArgument('all', 'Open all items', '--all', '-a'),
|
|
174
|
+
argumentFactory.keyValueArgument('id', 'id to open', false, '--identifier', '-id'),
|
|
175
|
+
]),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
const commandLineDirector = new CommandLineDirector('File cli', 'file cli description', commandLines)
|
|
179
|
+
const helpText = commandLineDirector.generateHelp();
|
|
180
|
+
|
|
181
|
+
let command = commandLineDirector.parseArguments(['list', '-d'], true)
|
|
182
|
+
assert.equal(command.identifier, 'list-identifier');
|
|
183
|
+
assert.equal(command.values['command'], 'list');
|
|
184
|
+
assert.equal(command.values['delete'], true);
|
|
185
|
+
|
|
186
|
+
command = commandLineDirector.parseArguments(['display', '--all', '--identifier=12'], true)
|
|
187
|
+
assert.equal(command.identifier, 'open-identifier');
|
|
188
|
+
assert.equal(command.values['command'], 'display');
|
|
189
|
+
assert.equal(command.values['all'], true);
|
|
190
|
+
assert.equal(command.values['id'], 12);
|
|
191
|
+
|
|
192
|
+
command = commandLineDirector.parseArguments(['update', '-a', '-id=100'], true)
|
|
193
|
+
assert.equal(command.identifier, 'update-identifier');
|
|
194
|
+
assert.equal(command.values['command'], 'update');
|
|
195
|
+
assert.equal(command.values['all'], true);
|
|
196
|
+
assert.equal(command.values['id'], 100);
|
|
197
|
+
});
|
|
198
|
+
});
|