electron 0.1.0 → 0.2.1

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.
@@ -0,0 +1,553 @@
1
+ /*!
2
+ * Electron - process.argv parsing
3
+ * Copyright (c) 2012 Jake Luer <jake@alogicalparadox.com>
4
+ * MIT Licensed
5
+ */
6
+
7
+ /*!
8
+ * External dependancies
9
+ */
10
+
11
+ var Drip = require('drip')
12
+ , tty = require('tty')
13
+ , util = require('util');
14
+
15
+ /*!
16
+ * Electron dependancies
17
+ */
18
+
19
+ var Args = require('./args')
20
+ , Command = require('./command')
21
+ , themes = require('./themes');
22
+
23
+ /*!
24
+ * isTTY (can support color)
25
+ */
26
+
27
+ var istty = tty.isatty(1) && tty.isatty(2);
28
+
29
+ /*!
30
+ * defaults (a, b)
31
+ *
32
+ * Helper function to merge one object to another
33
+ * using the first object as default values.
34
+ *
35
+ * @param {Object} subject
36
+ * @param {Object} defaults
37
+ * @name defaults
38
+ * @api private
39
+ */
40
+
41
+ function defaults (a, b) {
42
+ if (a && b) {
43
+ for (var key in b) {
44
+ if ('undefined' == typeof a[key]) a[key] = b[key];
45
+ }
46
+ }
47
+ return a;
48
+ };
49
+
50
+ /*!
51
+ * Main export
52
+ */
53
+
54
+ module.exports = Program;
55
+
56
+ /**
57
+ * ## Program Framework
58
+ *
59
+ * The primary export of the electron module is a function
60
+ * that composes a new program framework. The returned
61
+ * `program` is a chainable api that allow you to change
62
+ * settings, define commands, and start launch the program.
63
+ *
64
+ * The primary argument provided on construction is the base
65
+ * name used through the help documentation. In a majority of
66
+ * of cases, this would be the command executed from your terminal
67
+ * used launch the program.
68
+ *
69
+ * In the case of scripts with the header of `#!/usr/bin/env node`,
70
+ * you should use a variant of the following.
71
+ *
72
+ * var program = electron('microscope');
73
+ *
74
+ * If you are however launching your program from a `.js` file,
75
+ * the recommended construction pattern is the following.
76
+ *
77
+ * var program = electrong('node microscope.js');
78
+ *
79
+ * You can then chain any of the following commands to further
80
+ * define your application and commands.
81
+ *
82
+ * @header Program Framework
83
+ */
84
+
85
+ function Program (base, opts) {
86
+ /*!
87
+ * @param {String} base name
88
+ * @param {Object} options
89
+ */
90
+
91
+ Drip.call(this, { delimeter: ' ' });
92
+ this.commands = [];
93
+ this._colorized = false;
94
+ this.opts = defaults(opts || {}, {
95
+ useColors: istty
96
+ , version: null
97
+ , desc: null
98
+ , name: base || 'Electron'
99
+ , base: base || 'electron'
100
+ , cwd: process.cwd()
101
+ , theme: {
102
+ name: 'clean'
103
+ , spec: {
104
+ noColor: false
105
+ }
106
+ }
107
+ });
108
+ }
109
+
110
+ /*!
111
+ * Inherit from Drip event emitter.
112
+ */
113
+
114
+ util.inherits(Program, Drip);
115
+
116
+ /**
117
+ * ### .command (name)
118
+ *
119
+ * The most important aspect of an application is defining
120
+ * the different commands that can be executed for it. Some
121
+ * people prefer a CLI tool that does one thing based on a
122
+ * set of options. Others prefer a command line tool that
123
+ * can pivot based on a command string. Electron supports both.
124
+ *
125
+ * When using the `command` method, it will return a constructed
126
+ * command object with a different set of methods for chaining.
127
+ * Please read the "Constructing Commands" section for all available
128
+ * chainable methods and their respective purpose.
129
+ *
130
+ * ##### Single Command
131
+ *
132
+ * As you understand how Electron parses command-line options,
133
+ * you know that a command is any option that does
134
+ * not start with `-` or `--`. Therefor, a single-command electron
135
+ * application is one that does not require any `commands`, but
136
+ * will execute a single action. Best demonstrated...
137
+ *
138
+ * $ node app.js -p 8080
139
+ *
140
+ * In the case of single-command applications, we will define
141
+ * a `default` command for electron to run.
142
+ *
143
+ * program
144
+ * .command('default')
145
+ * .action(function (argv) {
146
+ * var port = argv.param('p', 'port');
147
+ * // something cool
148
+ * });
149
+ *
150
+ * The `default` command will run when no commands are passed in,
151
+ * but it will not run if a command is provided.
152
+ *
153
+ * ##### Multiple Commands
154
+ *
155
+ * You can also create many different commands for your application
156
+ * based on a simple string. These can be used in conjunction
157
+ * with `default` if you would like.
158
+ *
159
+ * $ node app.js hello --universe
160
+ *
161
+ * In this case we want to only run an action when the comamnd
162
+ * `hello` is present. This can easily be achieved.
163
+ *
164
+ * program
165
+ * .command('hello')
166
+ * .action(fn);
167
+ *
168
+ * There are also cases where you might want to have multipe layers
169
+ * of commands.
170
+ *
171
+ * $ node app.js hello universe
172
+ *
173
+ * Using the same mechanism, we can easily define this action.
174
+ *
175
+ * program
176
+ * .command('hello universe')
177
+ * .action(fn);
178
+ *
179
+ * One final option to explore with multiple commands is wildcards.
180
+ * Wildcards can exist at any level in a multi-word command, but they
181
+ * only work for entire words, not substrings.
182
+ *
183
+ * program
184
+ * .command('hello *')
185
+ * .action(function (argv) {
186
+ * var where = argv.commands[1];
187
+ * });
188
+ *
189
+ * Would respond for any command starting with `hello` and is two
190
+ * commands long, such as...
191
+ *
192
+ * $ node app.js hello world
193
+ * $ node app.js hello universe
194
+ *
195
+ * ##### Absent Commands
196
+ *
197
+ * Should you want to notify your users when they attempt to use
198
+ * a command is not support, you may use the `absent` command. This
199
+ * is also useful if you want to have a single command app but
200
+ * support a list of items as "options", such as a list of files or
201
+ * directories.
202
+ *
203
+ * $ node build.js file1.js files2.js
204
+ *
205
+ * program
206
+ * .command('absent')
207
+ * .action(function (argv) {
208
+ * var files = argv.commands.slice(0);
209
+ * // something cool
210
+ * });
211
+ *
212
+ * @param {String} command name
213
+ * @returns chainable constructed command
214
+ * @name command
215
+ * @api public
216
+ */
217
+
218
+ Program.prototype.command = function (name) {
219
+ var cmd = new Command(name);
220
+ this.commands.push(cmd);
221
+ return cmd;
222
+ };
223
+
224
+ /**
225
+ * ### .parse (process.argv)
226
+ *
227
+ * The `parse` method will initiate the program with
228
+ * selection of arguments and run a matching action. It
229
+ * should be used once all commands and settings have
230
+ * been propogated.
231
+ *
232
+ * program.parse();
233
+ * program.parse(process.argv);
234
+ * program.parse([ 'node', 'app.js', '--hello', '--universe' ]);
235
+ *
236
+ * If no parameter is provided, `parse` will default to using
237
+ * the current processes `process.argv` array. You may alse
238
+ * provide your own array of commands if you like. Note that
239
+ * argv parsing expectes the first item to be the executing program
240
+ * and the second argument to be the script (as is with all node argv).
241
+ * These will be discarded.
242
+ *
243
+ * @param {Array} process.argv or compable array
244
+ * @name parse
245
+ * @api public
246
+ */
247
+
248
+ Program.prototype.parse = function (args) {
249
+ args = args || process.argv;
250
+ var argv = new Args(args)
251
+ , cmds = mountCommands.call(this)
252
+ , command = argv.commands.slice(0);
253
+
254
+ argv.cwd = this.opts.cwd;
255
+ if (argv.mode('help', 'h')) {
256
+ displayHelp.call(this);
257
+ } else if (argv.mode('version', 'v')) {
258
+ displayVersion.call(this);
259
+ } else if (!command.length) {
260
+ if (cmds.def) cmds.def(argv);
261
+ } else if (!this.hasListener(command)) {
262
+ if (cmds.absent) cmds.absent(argv);
263
+ } else {
264
+ this.emit(command, argv);
265
+ }
266
+
267
+ return this;
268
+ };
269
+
270
+ /**
271
+ * ### .name (name)
272
+ *
273
+ * Provide a formal name to be used when displaying
274
+ * the help for the given program. Returns the program
275
+ * for chaining.
276
+ *
277
+ * program.name('Electron Framework');
278
+ *
279
+ * @param {String} formal name
280
+ * @returns `this` for chaining
281
+ * @name name
282
+ * @api public
283
+ */
284
+
285
+ Program.prototype.name = function (name) {
286
+ this.opts.name = name;
287
+ return this;
288
+ };
289
+
290
+ /**
291
+ * ### .version (version)
292
+ *
293
+ * Provide a program version to be used when displaying
294
+ * the version for the given program. Returns the program
295
+ * for chaining.
296
+ *
297
+ * program.version(electron.version);
298
+ *
299
+ * @param {String} application version
300
+ * @returns `this` for chaining
301
+ * @name version
302
+ * @api public
303
+ */
304
+
305
+ Program.prototype.version = function (v) {
306
+ this.opts.version = v;
307
+ return this;
308
+ };
309
+
310
+ /**
311
+ * ### .desc (description)
312
+ *
313
+ * Provide a program discription to be used when displaying
314
+ * the the help for the given program. Returns the program
315
+ * for chaining.
316
+ *
317
+ * program.desc('https://github.com/logicalparadox/electron');
318
+ *
319
+ * @param {String} application description
320
+ * @returns `this` for chaining
321
+ * @name desc
322
+ * @api public
323
+ */
324
+
325
+ Program.prototype.desc = function (desc) {
326
+ this.opts.desc = desc;
327
+ return this;
328
+ };
329
+
330
+ /**
331
+ * ### .cwd (fqp)
332
+ *
333
+ * Provide an alternative current working directory to be
334
+ * passed as part of the `argv` parameter to the action that has
335
+ * been executed. Returns the program for chaining.
336
+ *
337
+ * // set
338
+ * program.cwd(__dirname);
339
+ *
340
+ * // get
341
+ * program
342
+ * .command('universe')
343
+ * .action(function (argv) {
344
+ * var cwd = argv.cwd;
345
+ * // something cool
346
+ * });
347
+ *
348
+ * @param {String} fully quialified path
349
+ * @returns `this` for chaining
350
+ * @name cwd
351
+ * @api public
352
+ */
353
+
354
+ Program.prototype.cwd = function (p) {
355
+ this.opts.cwd = p;
356
+ return this;
357
+ };
358
+
359
+ /**
360
+ * ### .theme (theme, specifications)
361
+ *
362
+ * You may change the appearance of the `--help` using
363
+ * a simple theming mechanism. Electron comes bundled with
364
+ * two themes. Each theme also supports minor tweaks. Returns
365
+ * the program for chaining.
366
+ *
367
+ * ##### clean (default)
368
+ *
369
+ * A colorful and verbose theme useful for multi-command applications.
370
+ *
371
+ * program
372
+ * .theme('clean', {
373
+ * noColor: false // set to true to disable color coding
374
+ * , prefix: '' // prefix written before each line, such as 'help:'
375
+ * });
376
+ *
377
+ * <img alt="Electron Clean Theme" src="http://f.cl.ly/items/10283V3e2o1R0f2d2x32/electron-clean-theme.png" />
378
+ *
379
+ * ##### simple
380
+ *
381
+ * An "options only" theme useful for single command applications.
382
+ *
383
+ * program
384
+ * .theme('simple', {
385
+ * noColor: false // set to true to disable color coding
386
+ * , command: 'default' // which command to show options for
387
+ * , usage: '<options>' // special usage instructions
388
+ * });
389
+ *
390
+ * <img alt="Electron Simple Theme" src="http://f.cl.ly/items/3z3l162D101e0016320G/electron-simple-theme.png" />
391
+ *
392
+ * ##### Use Your Own Function
393
+ *
394
+ * You may also provide a function to `theme` to provide your own output.
395
+ * The following example will list all of the commands present in your
396
+ * program.
397
+ *
398
+ * program.theme(function () {
399
+ * this.colorize();
400
+ * console.log('Usage: %s <command>', this.opts.base);
401
+ * this.commands.forEach(functioni (cmd) {
402
+ * console.log(' %s - %s', cmd.cmd, cmd.desc);
403
+ * });
404
+ * });
405
+ *
406
+ * Those interesting in building custom themes should view Electron
407
+ * on GitHub and explore
408
+ * [lib/electron/themes](https://github.com/logicalparadox/electron/tree/master/lib/electron/themes).
409
+ *
410
+ * @param {String|Function} name of electron theme or custom function
411
+ * @param {Object} options for electron themes
412
+ * @returns `this` for chaining
413
+ * @name theme
414
+ * @api public
415
+ */
416
+
417
+ Program.prototype.theme = function (name, spec) {
418
+ if ('function' === typeof name) {
419
+ this.opts.theme = name;
420
+ } else {
421
+ var theme = {};
422
+ theme.name = name || 'clean';
423
+ theme.spec = defaults(spec || {}, { noColor: false });
424
+ this.opts.theme = theme;
425
+ }
426
+
427
+ return this;
428
+ };
429
+
430
+ /**
431
+ * ### .colorize ()
432
+ *
433
+ * The `colorize` helper is available if you wish you implement
434
+ * a colorized but lightweight logging mechanism in your actions,
435
+ * or if you are building a custom help theme. `colorize` works
436
+ * by extending `String.prototype` with a number of color options.
437
+ * Just in case, if the current program is not running as a TTY,
438
+ * no string changes will be made.
439
+ *
440
+ * program.colorize();
441
+ * console.log('hello universe'.green);
442
+ *
443
+ * ##### Colors
444
+ *
445
+ * - red
446
+ * - green
447
+ * - yellow
448
+ * - blue
449
+ * - magenta
450
+ * - cyan
451
+ * - gray
452
+ *
453
+ * @name colorize
454
+ * @api public
455
+ */
456
+
457
+ Program.prototype.colorize = function (noColors) {
458
+ if (this._colorized) return this;
459
+ var self = this
460
+ , colors = {
461
+ 'red': 31
462
+ , 'green': 32
463
+ , 'yellow': 33
464
+ , 'blue': 34
465
+ , 'magenta': 35
466
+ , 'cyan': 36
467
+ , 'gray': 90
468
+ , 'reset': 0
469
+ };
470
+
471
+ Object.keys(colors).forEach(function (color) {
472
+ Object.defineProperty(String.prototype, color,
473
+ { get: function () {
474
+ if (noColors || !self.opts.useColors) return this;
475
+ return '\033[' + colors[color] + 'm' + this + '\033[0m';
476
+ }
477
+ , configurable: true
478
+ });
479
+ });
480
+
481
+ this._colorized = true;
482
+ return this;
483
+ };
484
+
485
+ /*!
486
+ * mountCommands ()
487
+ *
488
+ * For each constructed command added to the the program
489
+ * mount as a listener. If the command is special, return
490
+ * it for futher inspection.
491
+ *
492
+ * @ctx program
493
+ * @api private
494
+ */
495
+
496
+ function mountCommands () {
497
+ var self = this
498
+ , res = {}
499
+ , arr = [];
500
+
501
+ this.commands.forEach(function (command) {
502
+ var fn = command.opts.action
503
+ , ev = Array.isArray(command.opts.cmd)
504
+ ? command.opts.cmd.join(' ')
505
+ : command.opts.cmd;
506
+
507
+ if (ev === 'default') res.def = fn;
508
+ else if (ev === 'absent') res.absent = fn;
509
+ else if (!~arr.indexOf(ev)) {
510
+ arr.push(ev);
511
+ self.on(ev, fn);
512
+ }
513
+ });
514
+
515
+ return res;
516
+ }
517
+
518
+ /*!
519
+ * displayVersion ()
520
+ *
521
+ * Display the current program version and exit
522
+ * process. Used when called with `-v` or `--version`
523
+ *
524
+ * @ctx program
525
+ * @api private
526
+ */
527
+
528
+ function displayVersion () {
529
+ process.stdout.write(this.opts.version + '\n');
530
+ process.exit();
531
+ }
532
+
533
+ /*!
534
+ * displayHelp ()
535
+ *
536
+ * Execute the custom help theme or find the respective
537
+ * electron theme and execute.
538
+ *
539
+ * @ctx program
540
+ * @api private
541
+ */
542
+
543
+ function displayHelp () {
544
+ if ('function' === typeof this.opts.theme)
545
+ return this.opts.theme.call(this);
546
+
547
+ var name = this.opts.theme.name
548
+ , theme = themes[name]
549
+ , spec = this.opts.theme.spec || {};
550
+
551
+ if (!theme) throw new Error('Electron: Invalid help theme defined.');
552
+ else theme.call(this, spec);
553
+ }
@@ -0,0 +1,63 @@
1
+ /*!
2
+ * Electron - clean theme
3
+ * Copyright (c) 2012 Jake Luer <jake@alogicalparadox.com>
4
+ * MIT Licensed
5
+ */
6
+
7
+ /*!
8
+ * main export - theme display
9
+ */
10
+
11
+ module.exports = function cleanHelp (spec) {
12
+ // parse specs
13
+ spec = spec || {};
14
+ spec.prefix = spec.prefix || '';
15
+
16
+ // colors
17
+ this.colorize(spec.noColor || false);
18
+
19
+ // helper function and variables
20
+ var l = function (s) { console.log(spec.prefix.cyan+ ' ' + (s || '')); }
21
+ , pad = function (s, w) { return Array(w - s.length - 1).join(' ') + s; }
22
+ , name = this.opts.name
23
+ , base = this.opts.base;
24
+
25
+ // header
26
+ l();
27
+ l(name.cyan + ' ' + this.opts.version);
28
+ if (this.opts.desc) l(this.opts.desc.gray);
29
+
30
+ // commmand display
31
+ this.commands.forEach(function (cmd) {
32
+ // we don't display absent command
33
+ if (cmd.opts.cmd === 'absent') return;
34
+
35
+ // hlper variables
36
+ var c = cmd.opts
37
+ , command = c.cmd !== 'default' ? c.cmd + ' ' : ''
38
+ , opts = c.options.length ? '<options>' : '';
39
+
40
+ // main lines
41
+ l();
42
+ l(base.gray + ' ' + command.green + opts);
43
+ if (c.desc.length) l(pad('', 4) + c.desc.blue);
44
+ if (!c.options.length) return;
45
+
46
+ // if there are options ...
47
+ c.options.forEach(function (opt) {
48
+ var n = c.desc.length ? 6 : 4
49
+ , opts = opt.opts.flags.map(function (flag) {
50
+ if (flag.length === 1) return '-' + flag;
51
+ else return '--' + flag;
52
+ });
53
+
54
+ l(pad('', n) + opts.join(', ')
55
+ + (opt.opts.def ? ' [' + opt.opts.def + ']' : '' )
56
+ + ' ' + opt.desc.gray);
57
+ });
58
+ });
59
+
60
+ // all done
61
+ l();
62
+ process.exit();
63
+ }
@@ -0,0 +1,17 @@
1
+ /*!
2
+ * Electron - theme loader
3
+ * Copyright (c) 2012 Jake Luer <jake@alogicalparadox.com>
4
+ * MIT Licensed
5
+ */
6
+
7
+ /*!
8
+ * expose clean theme
9
+ */
10
+
11
+ exports.clean = require('./clean');
12
+
13
+ /*!
14
+ * expose simple theme
15
+ */
16
+
17
+ exports.simple = require('./simple');
@@ -0,0 +1,71 @@
1
+ /*!
2
+ * Electron - simple theme
3
+ * Copyright (c) 2012 Jake Luer <jake@alogicalparadox.com>
4
+ * MIT Licensed
5
+ */
6
+
7
+ /*!
8
+ * main export - theme display
9
+ */
10
+
11
+ module.exports = function cleanHelp (spec) {
12
+ // parse specs
13
+ spec = spec || {};
14
+ spec.prefix = spec.prefix || '';
15
+ spec.command = spec.command || 'default';
16
+ spec.usage = spec.usage || '<options>';
17
+
18
+ // colors!
19
+ this.colorize(spec.noColor || false);
20
+
21
+ // helper function and variables
22
+ var l = function (s) { console.log(spec.prefix.cyan + ' ' + (s || '')); }
23
+ , pad = function (s, w) { return s + Array(w - s.length - 1).join(' '); }
24
+ , name = this.opts.name
25
+ , base = this.opts.base;
26
+
27
+ // header
28
+ l();
29
+ l(name.cyan + ' ' + this.opts.version);
30
+ if (this.opts.desc) l(this.opts.desc.gray);
31
+
32
+ // get the command
33
+ var command = this.commands.filter(function (cmd) {
34
+ return (cmd.opts.cmd === spec.command) ? true : false;
35
+ })[0];
36
+
37
+ // check for command
38
+ if (!command) {
39
+ l('Invalid command identified for help.'.red);
40
+ l();
41
+ process.exit();
42
+ }
43
+
44
+ // comamnd usage
45
+ l();
46
+ l('Usage: '.green + base.gray + ' ' + spec.usage);
47
+ l();
48
+ l('Options:'.magenta);
49
+ l();
50
+
51
+ // iterate through options
52
+ command.opts.options.forEach(function (opt) {
53
+ var opts = opt.opts.flags.map(function (flag) {
54
+ if (flag.length === 1) return '-' + flag;
55
+ else return '--' + flag;
56
+ });
57
+
58
+ l(pad('', 4)
59
+ + pad(
60
+ opts.join(', ')
61
+ + (opt.opts.def ? ' [' + opt.opts.def + ']' : '' )
62
+ , 26)
63
+ + ' ' + opt.desc.gray);
64
+ });
65
+
66
+ // all done
67
+ l();
68
+ l(command.opts.desc.blue);
69
+ l();
70
+ process.exit();
71
+ }