Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50665 views
1
/*
2
* index.js: Top-level plugin exposing CLI features in flatiron
3
*
4
* (C) 2011, Nodejitsu Inc.
5
* MIT LICENSE
6
*
7
*/
8
9
var fs = require('fs'),
10
path = require('path'),
11
flatiron = require('../../flatiron'),
12
common = flatiron.common,
13
director = require('director');
14
15
//
16
// ### Name this plugin
17
//
18
exports.name = 'cli';
19
20
//
21
// ### function attach (options, done)
22
// #### @options {Object} Options for this plugin
23
// Initializes `this` (the application) with the core `cli` plugins consisting of:
24
// `argv`, `prompt`, `routing`, `commands` in that order.
25
//
26
exports.attach = function (options) {
27
var app = this;
28
options = options || {};
29
30
//
31
// Define the `cli` namespace on the app for later use
32
//
33
app.cli = app.cli || {};
34
35
//
36
// Mixin some keys properly so that plugins can also set them
37
//
38
options.argv = common.mixin({}, app.cli.argv || {}, options.argv || {});
39
options.prompt = common.mixin({}, app.cli.prompt || {}, options.prompt || {});
40
41
app.cli = common.mixin({}, app.cli, options);
42
43
if (app.cli.notFoundUsage == undefined) {
44
app.cli.notFoundUsage = true;
45
}
46
47
//
48
// Setup `this.argv` to use `optimist`.
49
//
50
exports.argv.call(this, app.cli.argv);
51
app.use(flatiron.plugins.inspect);
52
53
//
54
// If `options.version` is truthy, `app.version` is defined and `-v` or
55
// `--version` command line parameters were passed, print out `app.version`
56
// and exit.
57
//
58
if (app.cli.version && app.version && (this.argv.v || this.argv.version)) {
59
console.log(app.version);
60
process.exit(0);
61
}
62
63
//
64
// Setup `this.prompt`.
65
//
66
exports.prompt.call(this, app.cli.prompt);
67
68
//
69
// Setup `app.router` and associated core routing method.
70
//
71
app.router = new director.cli.Router().configure({
72
async: app.async || app.cli.async
73
});
74
75
app.start = function (options, callback) {
76
if (!callback && typeof options === 'function') {
77
callback = options;
78
options = {};
79
}
80
81
callback = callback || function () {};
82
app.init(options, function (err) {
83
if (err) {
84
return callback(err);
85
}
86
87
app.router.dispatch('on', app.argv._.join(' '), app.log, callback);
88
});
89
};
90
91
app.cmd = function (path, handler) {
92
app.router.on(path, handler);
93
};
94
95
exports.commands.call(this);
96
};
97
98
//
99
// ### function init (done)
100
// #### @done {function} Continuation to respond to when complete
101
// Initializes this plugin by setting `winston.cli` (i.e. `app.log.cli`)
102
// to enable colors and padded levels.
103
//
104
exports.init = function (done) {
105
var app = this,
106
logger;
107
108
if (!app.log.help) {
109
logger = app.log.get('default');
110
logger.cli().extend(app.log);
111
}
112
113
if (app.config) {
114
//
115
// Create a literal store for argv to
116
// avoid re-parsing CLI options.
117
//
118
app.config.use('argv', {
119
type: 'literal',
120
store: app.argv
121
});
122
123
app.config.env();
124
}
125
126
done();
127
};
128
129
//
130
// ### function argv (options)
131
// #### @options {Object} Pass-thru options for optimist
132
// Sets up `app.argv` using `optimist` and the specified options.
133
//
134
exports.argv = function (options) {
135
var optimist;
136
137
if (options && Object.keys(options).length) {
138
optimist = require('optimist').options(options);
139
this.showOptions = optimist.help;
140
this.argv = optimist.argv;
141
}
142
else {
143
optimist = require('optimist');
144
this.showOptions = optimist.help;
145
this.argv = optimist.argv;
146
}
147
};
148
149
//
150
// ### function commands (options)
151
// #### @options {Object} Options for the application commands
152
// Configures the `app.commands` object which is lazy-loaded from disk
153
// along with some default logic for: `help` and `alias`.
154
//
155
exports.commands = function (options) {
156
var app = this;
157
158
function showUsage(target) {
159
target = Array.isArray(target) ? target : target.split('\n');
160
target.forEach(function (line) {
161
app.log.help(line);
162
});
163
164
var lines = app.showOptions().split('\n').filter(Boolean);
165
166
if (lines.length) {
167
app.log.help('');
168
lines.forEach(function (line) {
169
app.log.help(line);
170
});
171
}
172
}
173
174
//
175
// Setup any pass-thru options to the
176
// application instance but make them lazy
177
//
178
app.usage = app.cli.usage;
179
app.cli.source = app.cli.dir || app.cli.source;
180
app.commands = app.commands || {};
181
182
//
183
// Helper function which loads the file for the
184
// specified `name` into `app.commands`.
185
//
186
function loadCommand(name, command, silent) {
187
var resource = app.commands[name];
188
189
if (resource && (!command || resource[command])) {
190
return true;
191
}
192
193
if (app.cli.source) {
194
if (!app.cli.sourceDir) {
195
try {
196
var stats = fs.statSync(app.cli.source);
197
app.cli.sourceDir = stats.isDirectory();
198
}
199
catch (ex) {
200
if (app.cli.notFoundUsage) {
201
showUsage(app.usage || [
202
'Cannot find commands for ' + name.magenta
203
]);
204
}
205
206
return false;
207
}
208
}
209
210
try {
211
if (app.cli.sourceDir) {
212
app.commands[name] = require(path.join(app.cli.source, name));
213
}
214
else {
215
app.commands = common.mixin(app.commands, require(app.cli.source));
216
}
217
return true;
218
}
219
catch (err) {
220
// If that file could not be found, error message should start with
221
// "Cannot find module" and contain the name of the file we tried requiring.
222
if (!err.message.match(/^Cannot find module/) || (name && err.message.indexOf(name) === -1)) {
223
throw err;
224
}
225
226
if (!silent) {
227
if (app.cli.notFoundUsage) {
228
showUsage(app.usage || [
229
'Cannot find commands for ' + name.magenta
230
]);
231
}
232
}
233
234
return false;
235
}
236
}
237
}
238
239
//
240
// Helper function to ensure the user wishes to execute
241
// a destructive command.
242
//
243
function ensureDestroy(callback) {
244
app.prompt.get(['destroy'], function (err, result) {
245
if (result.destroy !== 'yes' && result.destroy !== 'y') {
246
app.log.warn('Destructive operation cancelled');
247
return callback(true);
248
}
249
250
callback();
251
});
252
}
253
254
//
255
// Helper function which executes the command
256
// represented by the Array of `parts` passing
257
// control to the `callback`.
258
//
259
function executeCommand(parts, callback) {
260
var name,
261
shouldLoad = true,
262
command,
263
usage;
264
265
if (typeof parts === 'undefined' || typeof parts === 'function') {
266
throw(new Error('parts is a required argument of type Array'));
267
}
268
269
name = parts.shift();
270
271
if (app.cli.source || app.commands[name]) {
272
if (app.commands[name]) {
273
shouldLoad = false;
274
if (typeof app.commands[name] != 'function' && !app.commands[name][parts[0]]) {
275
shouldLoad = true;
276
}
277
}
278
279
if (shouldLoad && !loadCommand(name, parts[0])) {
280
return callback();
281
}
282
283
command = app.commands[name];
284
while (command) {
285
usage = command.usage;
286
287
if (!app.argv.h && !app.argv.help && typeof command === 'function') {
288
while (parts.length + 1 < command.length) {
289
parts.push(null);
290
}
291
292
if (command.destructive) {
293
return ensureDestroy(function (err) {
294
return err ? callback() : command.apply(app, parts.concat(callback));
295
})
296
}
297
298
command.apply(app, parts.concat(callback));
299
return;
300
}
301
302
command = command[parts.shift()];
303
}
304
305
//
306
// Since we have not resolved a needle, try and print out a usage message
307
//
308
if (usage || app.cli.usage) {
309
showUsage(usage || app.cli.usage);
310
callback(false);
311
}
312
}
313
else if (app.usage) {
314
//
315
// If there's no directory we're supposed to search for modules, simply
316
// print out usage notice if it's provided.
317
//
318
showUsage(app.cli.usage);
319
callback(true);
320
}
321
}
322
323
//
324
// Expose the executeCommand method
325
//
326
exports.executeCommand = executeCommand;
327
328
//
329
// Allow commands to be aliased to subcomponents. e.g.
330
//
331
// app.alias('list', { resource: 'apps', command: 'list' });
332
// app.alias('new', { command: 'create' });
333
// app.alias('new', 'create');
334
//
335
app.alias = function (target, source) {
336
app.commands.__defineGetter__(target, function () {
337
338
var resource = source.resource || source.command || source,
339
command = source.resource ? source.command : null;
340
341
loadCommand(resource, command, true);
342
resource = app.commands[resource];
343
344
if (resource) {
345
return source.resource && source.command
346
? resource[source.command]
347
: resource;
348
}
349
});
350
};
351
352
//
353
// Set the `loadCommand` function to run
354
// whenever the router has not matched
355
// the CLI arguments, `process.argv`.
356
//
357
app.router.notfound = function (callback) {
358
executeCommand(app.argv._.slice(), callback);
359
};
360
361
//
362
// Setup default help command
363
//
364
app.cmd(/help ([^\s]+)?\s?([^\s]+)?/, app.showHelp = function showHelp() {
365
var args = Array.prototype.slice.call(arguments).filter(Boolean),
366
callback = typeof args[args.length - 1] === 'function' && args.pop(),
367
resource,
368
usage;
369
370
function displayAndRespond(found) {
371
showUsage(usage || app.usage);
372
if (!found) {
373
app.log.warn('Cannot find help for ' + args.join(' ').magenta);
374
}
375
376
if (callback) {
377
callback();
378
}
379
}
380
381
if (!loadCommand(args[0], args[1], true)) {
382
return displayAndRespond(false);
383
}
384
385
resource = app.commands[args[0]];
386
usage = resource.usage;
387
388
for (var i = 1; i < args.length; i++) {
389
if (!resource[args[i]]) {
390
return displayAndRespond(false);
391
}
392
else if (resource[args[i]].usage) {
393
resource = resource[args[i]];
394
usage = resource.usage;
395
}
396
}
397
398
displayAndRespond(true);
399
});
400
};
401
402
//
403
// ### function prompt (options)
404
// #### @options {Object} Options for the prompt.
405
// Sets up the application `prompt` property to be a lazy
406
// setting which loads the `prompt` module.
407
//
408
exports.prompt = function (options) {
409
options = options || {};
410
411
this.__defineGetter__('prompt', function () {
412
if (!this._prompt) {
413
//
414
// Pass-thru any prompt specific options that are supplied.
415
//
416
var prompt = require('prompt'),
417
self = this;
418
419
prompt.allowEmpty = options.allowEmpty || prompt.allowEmpty;
420
prompt.message = options.message || prompt.message;
421
prompt.delimiter = options.delimiter || prompt.delimiter;
422
prompt.properties = options.properties || prompt.properties;
423
424
//
425
// Setup `destroy` property for destructive commands
426
//
427
prompt.properties.destroy = {
428
name: 'destroy',
429
message: 'This operation cannot be undone, Would you like to proceed?',
430
default: 'yes'
431
};
432
433
//
434
// Hoist up any prompt specific events and re-emit them as
435
// `prompt::*` events.
436
//
437
['start', 'pause', 'resume', 'prompt', 'invalid'].forEach(function (ev) {
438
prompt.on(ev, function () {
439
var args = Array.prototype.slice.call(arguments);
440
self.emit.apply(self, [['prompt', ev]].concat(args));
441
});
442
});
443
444
//
445
// Extend `this` (the application) with prompt functionality
446
// and open `stdin`.
447
//
448
this._prompt = prompt;
449
this._prompt.start().pause();
450
}
451
452
return this._prompt;
453
});
454
};
455
456