UNPKG

6.89 kBJavaScriptView Raw
1/* jshint node: true */
2'use strict';
3
4var async = require('async');
5var util = require('util');
6var read = require('read');
7var _ = require('underscore');
8var Table = require('cli-table');
9var defaults = require('./defaults');
10
11var TableFormat = {
12 chars: { 'top': '' , 'top-mid': '' , 'top-left': '' , 'top-right': ''
13 , 'bottom': '' , 'bottom-mid': '' , 'bottom-left': '' , 'bottom-right': ''
14 , 'left': '' , 'left-mid': '' , 'mid': '' , 'mid-mid': ''
15 , 'right': '' , 'right-mid': '' , 'middle': ' ' },
16 style: { 'padding-left': 0, 'padding-right': 0 }
17};
18module.exports.TableFormat = TableFormat;
19
20/*
21 * Validate the options as if they came from a command line.
22 * This will not fill in missing options -- "validate" may be used
23 * to do that later.
24 */
25module.exports.getopts = function(argv, start, descriptor) {
26 var opts = {};
27 for (var i = start; i < argv.length; i++) {
28 var longArg = /^--(.+)/.exec(argv[i]);
29 if (longArg) {
30 var longArgName = longArg[1];
31 var ld = descriptor[longArgName];
32 if (ld) {
33 if (ld.toggle) {
34 opts[longArgName] = true;
35 } else if (i < (argv.length - 1)) {
36 i++;
37 opts[longArgName] = argv[i];
38
39 if (ld.type === 'int') {
40 opts[longArgName] = parseInt(opts[longArgName], 10);
41 }
42
43 // Set prompt
44 if(ld.prompt == false){
45 opts.prompt = true;
46 }
47 } else {
48 badArg(argv[i]);
49 }
50 } else {
51 badArg(argv[i]);
52 }
53 }
54 else {
55 var shortArg = /^-([A-Za-z])/.exec(argv[i]);
56 if (shortArg) {
57 var shortArgName = shortArg[1];
58 var longName = undefined;
59 var des;
60 for (var sd in descriptor) {
61 des = descriptor[sd];
62 if (des.shortOption === shortArgName) {
63 longName = sd;
64 break;
65 }
66 }
67
68 if (longName) {
69 if (des.toggle) {
70 opts[longName] = true;
71 } else if (i < (argv.length - 1)) {
72 i++;
73 if (des.type === 'int') {
74 argv[i] = parseInt(argv[i], 10);
75 }
76
77 // add ability to support multiple instances of an option, e.g. headers
78 if (des.multiple) {
79 if (!opts[longName]) {
80 opts[longName] = [];
81 }
82 opts[longName].push(argv[i]);
83 } else {
84 opts[longName] = argv[i];
85
86 // Set prompt - only makes sense for singletons (not multiples)
87 if(des.prompt == false){
88 opts.prompt = true;
89 }
90 }
91 } else {
92 badArg(argv[i]);
93 }
94 } else {
95 badArg(argv[i])
96 }
97
98 } else {
99 badArg(argv[i]);
100 }
101 }
102 }
103 return opts;
104};
105
106function badArg(arg) {
107 throw new Error(util.format('Unknown argument %s', arg));
108}
109
110/*
111 * Given an "options" object, validate it against "descriptor."
112 * Each property in "descriptor" is the name of an option, and has
113 * the following possible fields:
114 * required (boolean) error if the option is not present
115 * secure (boolean) error if we must prompt in a secure way
116 */
117module.exports.validate = function(opts, descriptor, cb) {
118 defaults.defaultOptions(opts);
119 async.eachSeries(Object.getOwnPropertyNames(descriptor), function(item, done) {
120 checkProperty(opts, descriptor, item, done);
121 }, function(err) {
122 cb(err, opts);
123 });
124};
125
126function checkProperty(opts, descriptor, propName, done) {
127 var desc = descriptor[propName];
128 // console.log( "DEBUG " + desc.required + " " + opts[propName] + " " + opts.prompt + " " + desc.prompt + " " + propName);
129 // console.log( "DEBUG OPTs" + JSON.stringify(opts) );
130 if (desc === null || desc === undefined) {
131 done(new Error(util.format('Invalid property %s', propName)));
132 return;
133 }
134 if (desc.required && !opts[propName]) {
135 if (desc.prompt && !opts.prompt ) {
136 if (opts.interactive) {
137 var pn = (desc.name ? desc.name : propName);
138 prompt(pn, desc.secure, function(err, val) {
139 if (err) {
140 done(err);
141 } else {
142 if (desc.secure === true) {
143 opts[propName] = new SecureValue(val);
144 } else {
145 opts[propName] = val;
146 }
147 done();
148 }
149 });
150 } else {
151 done(new Error(util.format('Missing required option "%s"', propName)));
152 }
153 } else {
154 done(new Error(util.format('Missing required option with no prompt "%s"', propName)));
155 }
156 } else {
157 if (opts[propName] && (desc.secure === true)) {
158 makeSecure(opts, propName);
159 }
160 done();
161 }
162}
163
164function prompt(name, secure, cb) {
165 var opts = {
166 prompt: name + ':'
167 };
168 if (secure) {
169 opts.silent = true;
170 opts.replace = '*';
171 }
172
173 read(opts, cb);
174}
175
176/*
177 * Do the same thing as "validate" but without a callback, so it can be used
178 * anywhere.
179 */
180module.exports.validateSync = function(opts, descriptor) {
181 _.each(Object.getOwnPropertyNames(descriptor), function(item) {
182 checkPropertySync(opts, descriptor, item);
183 });
184};
185
186function checkPropertySync(opts, descriptor, propName) {
187 var desc = descriptor[propName];
188 if (desc === null || desc === undefined) {
189 var err = new Error(util.format('Invalid property %s', propName));
190 console.error(err);
191 throw err;
192 }
193 if (desc.required && !opts[propName] && (!opts.prompt && desc.prompt)) {
194 var err = new Error(util.format('Missing required option "%s"', propName));
195 console.error(err);
196 throw err;
197 } else {
198 if (opts[propName] && (desc.secure === true)) {
199 makeSecure(opts, propName);
200 }
201 }
202}
203
204function makeSecure(opts, propName) {
205 if (opts[propName] && (!(opts[propName] instanceof SecureValue))) {
206 opts[propName] = new SecureValue(opts[propName]);
207 }
208}
209
210/*
211 * Produce some "help" text based on the descriptor.
212 */
213module.exports.getHelp = function(descriptor) {
214 var tab = new Table(TableFormat);
215
216 _.each(_.sortBy(_.pairs(descriptor)), function(d) {
217 tab.push([
218 d[0],
219 (d[1].shortOption ? '-' + d[1].shortOption : ''),
220 ((d[1].required && !d[1].prompt) ? '(required)': '(optional)'),
221 ((d[1].name ? d[1].name : 'undefined')),
222 ((d[1].scope == 'default') ? '' : '(command specific)')
223 ]);
224 });
225
226 return tab.toString();
227};
228
229/*
230 * This is a little wrapper for a secure value.
231 */
232function SecureValue(val) {
233 if (!(this instanceof SecureValue)) {
234 return new SecureValue(val);
235 }
236 Object.defineProperty(this, 'value', {
237 configurable: false,
238 enumerable: false,
239 writable: false,
240 value: val
241 });
242}
243
244module.exports.SecureValue = SecureValue;
245
246SecureValue.prototype.toString = function() {
247 return '********';
248};
249
250SecureValue.prototype.getValue = function () {
251 return this.value;
252};