-
Notifications
You must be signed in to change notification settings - Fork 0
/
options.js
executable file
·393 lines (349 loc) · 11.5 KB
/
options.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/**
* @license Copyright 2010 Mikol Graves.
* Available under a Creative Commons Attribution 4.0 International License.
* See http://creativecommons.org/licenses/by/4.0/ for details.
*/
//------------------------------------------------------------------------------
/**
* Minimalistic utility for parsing typical Unix command line options following
* POSIX conventions, including support for GNU-style long options. Each command
* line argument in `process.argv` will be parsed in order and classified as a
* key or a value (the first 2 elements in `process.argv` will be skipped
* because they will always be "node" and the name of the executed JavaScript
* file; thus they are not user-specified options).
* <ul>
* <li>Command line arguments are keys if they begin with a single or double
* hyphen delimiter ("-" or "--").</li>
*
* <li>Keys that follow a single-hyphen delimiter must themselves be a single
* character each. Keys following a double hyphen must be at least two
* characters each and are typically one to three words long, with each word
* separated from the preceeding one by a hyphen.</li>
*
* <li>Multiple single-character keys may follow a single-hyphen delimiter.
* For example, "-key" is equivalent to "-k -e -y".</li>
*
* <li>Command line arguments that do not begin with a hyphen are values.</li>
*
* <li>Keys may accept, require, or ignore a subsequent value argument.</li>
*
* <li>A single character key and its corresponding value may or may not
* be separated by whitespace. For example, "-k value" and "-kvalue" are
* equivalent.</li>
*
* <li>A single character key may also be separated from its corresponding
* value with an equal sign ("=") so that "-k value", "-kvalue", and
* "-k=value" are all equivalent.</li>
*
* <li>A multi-character key must be separated from its corresponding value
* by either whitepsace or an equal sign ("="). For example, "--foo bar" and
* "--foo=bar" are equivalent, but "--foobar" is a discrete key named
* "foobar".</li>
*
* <li>An argument whose value is exactly "-" indicates that options
* processing should stop. By convention, it is used to declare input from
* STDIN or output to STDOUT.</li>
*
* <li>An argument whose value is exactly "--" indicates that any subsequent
* arguments should ne treated as values, even if they begin with a
* hyphen.</li>
*
* <li>Keys and values may be supplied in any order and may appear multiple
* times. Their interpretation is left up to the implementor.</li>
* </ul>
* <p>
* To use `options` call `options.next` in a loop. For
* example:
* </p>
* <pre>
* var sys = require('sys');
* var options = require('options');
*
* function processOptions(token, type) {
* if (type === options.KEY) {
* switch (token) {
* case 'e': // -e
* case 'encrypt': // --encrypt
* // Accept, but do not require, a specific cipher.
* var v = options.getOptional();
*
* if (v != null) {
* if (/^AES|Blowfish|DSA|RSA$/.test(v)) {
* sys.debug('Output will be encrypted using the "' + v +
* '" cipher.');
* } else {
* sys.debug('Unknown cipher "' + v + '".');
* }
* } else {
* sys.debug('Output will be encrypted using the default cipher.');
* }
*
* break;
* case 'h': // -h
* case 'help': // --help
* sys.debug('This command will print a help message.');
* break;
* case 'v': // -v
* case 'verbose': // --verbose
* sys.debug('This command will execute verbosely.');
* break;
* default:
* sys.debug('Unknown key "' + token + '".');
* }
* } else if (type === options.VALUE) {
* sys.debug('This command will process "' + token + '".');
* } else if (type === options.IO) {
* sys.debug('This command will process input from STDIN or output to ' +
* 'STDOUT.');
* }
* }
*
* while (options.next(processOptions));
* </pre>
*
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
*/
var Options = exports;
//--------------------------------------------------------------------
// Constants
/**
* @const
* @type {String} A sentinel indicating that the next command line argument
* is a key.
*/
Options.KEY = '{\b\0KEY}\b';
/**
* @const
* @type {String} A sentinel indicating that the next command line argument is
* a value.
*/
Options.VALUE = '{\b\0VALUE}\b';
/**
* @const
* @type {String} A sentinel indicating that the next command line argument will
* be input from STDIN or output to STDOUT.
*/
Options.IO = '{\b\0IO}\b';
//--------------------------------------------------------------------
// Variables
/**
* @private
* @type {Array.<String>} A copy of the command line options specified by
* the caller.
*/
var _args = process.argv.slice(2);
/**
* @private
* @type {Boolean} Flag indicating that the command expects some arguments to be
* keys, which is `true` by default; and `false` following a double-hyphen
* token ("--").
*/
var _expectingKeys = true;
/**
* @private
* @type {Boolean} Flag indicating that the command may expect I/O from STDIN or
* STDOUT following a single-hyphen token ("-").
*/
var _expectingIo = false;
/**
* @private
* @type {Boolean} Flag indicating that multiple characters follow a single-
* hyphen delimiter. Depending on the caller's expectations, a bundle may be a
* string of undelimited single-character keys (for example, `-key` as opposed
* to `-k -e -y`), a single-character key plus a corresponding value (for
* example, `-kvalue` as opposed to `-k value`), or both (for
* example, `-eykvalue`).
*/
var _inBundle = false;
/**
* @private
* @type {Boolean} Flag indicating that the previous token parsed was a key and
* that the next token is its corresponding value, which was declared
* unambiguously by using an equal sign ("=") to separate the key and value
* (for example, `--foo=bar` as opposed to `--foo bar`).
*
* @see #_inBundle
* @see #getOptional
*/
var _beforeOptional = false;
//-------------------------------------------------------------------
// Methods
/**
* Parse the next token from the command line and execute the specified
* callback function.
*
* @param {Function} callback The method to execute for each token parsed from
* the command line. `callback` should expect arguments representing
* the token parsed and its type, which can be one of `Options.KEY`,
* `Options.VALUE`, or `Options.IO`.
*
* @return {Boolean} `true` if there are more command line arguments
* to process; `false` otherwise.
*
* @throws {Error} If there are arguments on the command line following a
* single-hyphen token ("-").
*
* @throws {Error} If there are single-character keys on the command line that
* follow a double-hyphen delimiter ("--").
*
* @see #getValue
*/
Options.next = function (callback) {
if (_args.length < 1) {
return false;
}
var n = _next();
var t = null;
if (n === Options.VALUE) {
t = n;
n = Options.getValue();
} else if (_expectingKeys) {
t = Options.KEY;
} else if (_expectingIo) {
t = Options.IO;
} else {
t = Options.VALUE;
}
callback(n, t);
return (_args.length > 0);
};
/**
* Implements core processing logic for `next()`.
*
* @see #next
* @private
*/
function _next() {
var o;
if (_args.length === 0) {
return null;
}
if (_args[0] === '-') {
_expectingIo = true;
_expectingKeys = false;
_args.shift();
if (_args.length > 0) {
throw Error('Expected input or output, but found command line ' +
'arguments after hyphen ("-") token.');
}
return null;
}
if (!_expectingKeys) {
return _args.shift();
}
if (_args[0] === '--') {
_expectingKeys = false;
_args.shift();
return _next();
}
if (_args[0].indexOf('-') === 0) {
o = _args.shift();
var e = o.indexOf('=');
if (e > -1) {
var r = o.substr(e + 1);
o = o.substring(0, e);
if (r !== '') {
_beforeOptional = true;
_args.unshift(r);
}
}
if (o.indexOf('--') === 0) {
if (o.length < 4) {
throw Error('Expected a multi-character key to follow a double ' +
'hyphen ("--"), but found a single character key "' + o + '".');
}
return o.substr(2);
} else if (o.length === 2) {
_inBundle = false;
return o.substr(1);
} else {
_inBundle = true;
_args.unshift('-' + o.substr(2));
return o.substr(1,1);
}
}
return Options.VALUE;
}
/**
* Parse the next value from the command line. `getValue` is useful if the
* previous token is a key that requires a value.
*
* @return {String|null} The next command line argument—if one is
* available and it is not a key; `null` otherwise.
*/
Options.getValue = function () {
_beforeOptional = false;
if (_args.length === 0 || (!_inBundle && _args[0].indexOf('-') === 0)) {
return null;
}
var o = _args.shift();
if (_inBundle) {
_inBundle = false;
return o.substr(1);
}
return o;
};
/**
* Parse the next value from the command line if, and only if, it is part of a
* series of characters following a single-hyphen delimiter (for example,
* `-kvalue`) or it is separated from the preceding token by an equal sign (for
* example, `--foo=bar`). `getOptional` is useful if the previous token is a key
* that accepts, but doesn't require, a value.
*
* @return {String|null} The next command line argument—if one is
* available and it is unambiguously attached to the preceding key;
* `null` otherwise.
*/
Options.getOptional = function () {
if (_beforeOptional || _inBundle) {
return Options.getValue();
}
return null;
};
//------------------------------------------------------------------------------
if (process.argv[1] === __filename) {
(function test () {
var sys = require('sys');
var options = Options;
sys.debug(process.argv);
sys.debug(_args);
function processOptions(token, type) {
sys.debug(token + ' : ' + type + ' : ' + _args);
if (type === options.KEY) {
switch (token) {
case 'e': // -e
case 'encrypt': // --encrypt
// Accept, but do not require, a specific cipher.
var v = options.getOptional();
if (v != null) {
if (/^AES|Blowfish|DSA|RSA$/.test(v)) {
sys.debug('Output will be encrypted using the "' + v +
'" cipher.');
} else {
sys.debug('Unknown cipher "' + v + '".');
}
} else {
sys.debug('Output will be encrypted using the default cipher.');
}
break;
case 'h': // -h
case 'help': // --help
sys.debug('This command will print a help message.');
break;
case 'v': // -v
case 'verbose': // --verbose
sys.debug('This command will execute verbosely.');
break;
default:
sys.debug('Unknown key "' + token + '".');
}
} else if (type === options.VALUE) {
sys.debug('This command will process "' + token + '".');
} else if (type === options.IO) {
sys.debug('This command will process input from STDIN or output ' +
'to STDOUT.');
}
}
while (options.next(processOptions)) {}
})();
}