UNPKG

6.39 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 }
51
52 }
53 else {
54 var shortArg = /^-([A-Za-z])/.exec(argv[i]);
55 if (shortArg) {
56 var shortArgName = shortArg[1];
57 var longName = undefined;
58 var des;
59 for (var sd in descriptor) {
60 des = descriptor[sd];
61 if (des.shortOption === shortArgName) {
62 longName = sd;
63 break;
64 }
65 }
66
67 if (longName) {
68 if (des.toggle) {
69 opts[longName] = true;
70 } else if (i < (argv.length - 1)) {
71 i++;
72 if (des.type === 'int') {
73 argv[i] = parseInt(argv[i], 10);
74 }
75
76 // add ability to support multiple instances of an option, e.g. headers
77 if (des.multiple) {
78 if (!opts[longName]) {
79 opts[longName] = [];
80 }
81 opts[longName].push(argv[i]);
82 } else {
83 opts[longName] = argv[i];
84
85 // Set prompt - only makes sense for singletons (not multiples)
86 if(des.prompt == false){
87 opts.prompt = true;
88 }
89 }
90 } else {
91 badArg(argv[i]);
92 }
93 } else {
94 badArg(argv[i])
95 }
96
97 } else {
98 badArg(argv[i]);
99 }
100 }
101 }
102 return opts;
103};
104
105function badArg(arg) {
106 throw new Error(util.format('Unknown argument %s', arg));
107}
108
109/*
110 * Given an "options" object, validate it against "descriptor."
111 * Each property in "descriptor" is the name of an option, and has
112 * the following possible fields:
113 * required (boolean) error if the option is not present
114 * secure (boolean) error if we must prompt in a secure way
115 */
116module.exports.validate = function(opts, descriptor, cb) {
117 defaults.defaultOptions(opts);
118 async.eachSeries(Object.getOwnPropertyNames(descriptor), function(item, done) {
119 checkProperty(opts, descriptor, item, done);
120 }, function(err) {
121 cb(err, opts);
122 });
123};
124
125function checkProperty(opts, descriptor, propName, done) {
126 var desc = descriptor[propName];
127 if (desc === null || desc === undefined) {
128 done(new Error(util.format('Invalid property %s', propName)));
129 return;
130 }
131 if (desc.required && !opts[propName] && (!opts.prompt && desc.prompt)) {
132 if (opts.interactive) {
133 var pn = (desc.name ? desc.name : propName);
134 prompt(pn, desc.secure, function(err, val) {
135 if (err) {
136 done(err);
137 } else {
138 if (desc.secure === true) {
139 opts[propName] = new SecureValue(val);
140 } else {
141 opts[propName] = val;
142 }
143 done();
144 }
145 });
146 } else {
147 done(new Error(util.format('Missing required option "%s"', propName)));
148 }
149 } else {
150 if (opts[propName] && (desc.secure === true)) {
151 makeSecure(opts, propName);
152 }
153 done();
154 }
155}
156
157function prompt(name, secure, cb) {
158 var opts = {
159 prompt: name + ':'
160 };
161 if (secure) {
162 opts.silent = true;
163 opts.replace = '*';
164 }
165
166 read(opts, cb);
167}
168
169/*
170 * Do the same thing as "validate" but without a callback, so it can be used
171 * anywhere.
172 */
173module.exports.validateSync = function(opts, descriptor) {
174 _.each(Object.getOwnPropertyNames(descriptor), function(item) {
175 checkPropertySync(opts, descriptor, item);
176 });
177};
178
179function checkPropertySync(opts, descriptor, propName) {
180 var desc = descriptor[propName];
181 if (desc === null || desc === undefined) {
182 var err = new Error(util.format('Invalid property %s', propName));
183 console.error(err);
184 throw err;
185 }
186 if (desc.required && !opts[propName] && (!opts.prompt && desc.prompt)) {
187 var err = new Error(util.format('Missing required option "%s"', propName));
188 console.error(err);
189 throw err;
190 } else {
191 if (opts[propName] && (desc.secure === true)) {
192 makeSecure(opts, propName);
193 }
194 }
195}
196
197function makeSecure(opts, propName) {
198 if (opts[propName] && (!(opts[propName] instanceof SecureValue))) {
199 opts[propName] = new SecureValue(opts[propName]);
200 }
201}
202
203/*
204 * Produce some "help" text based on the descriptor.
205 */
206module.exports.getHelp = function(descriptor) {
207 var tab = new Table(TableFormat);
208
209 _.each(_.sortBy(_.pairs(descriptor)), function(d) {
210 tab.push([d[0],
211 (d[1].shortOption ? '-' + d[1].shortOption : ''),
212 ((d[1].required && !d[1].prompt) ? '(required)': '(optional)')]);
213 });
214
215 return tab.toString();
216};
217
218/*
219 * This is a little wrapper for a secure value.
220 */
221function SecureValue(val) {
222 if (!(this instanceof SecureValue)) {
223 return new SecureValue(val);
224 }
225 Object.defineProperty(this, 'value', {
226 configurable: false,
227 enumerable: false,
228 writable: false,
229 value: val
230 });
231}
232
233module.exports.SecureValue = SecureValue;
234
235SecureValue.prototype.toString = function() {
236 return '********';
237};
238
239SecureValue.prototype.getValue = function () {
240 return this.value;
241};