├── .travis.yml ├── .gitignore ├── lib ├── utils │ ├── bind-all-fns.js │ ├── flag-runner.js │ ├── parse-command.js │ └── extend-flags.js ├── name.js ├── wrappers.js ├── commands.js ├── flags.js ├── flag.js ├── command.js └── index.js ├── .eslintrc ├── package.json ├── LICENSE ├── test ├── name.js ├── flag.js ├── flags.js ├── wrappers.js ├── commands.js ├── command.js └── index.js ├── CHANGELOG.md └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 0.12 5 | - iojs 6 | - stable 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | spike 17 | -------------------------------------------------------------------------------- /lib/utils/bind-all-fns.js: -------------------------------------------------------------------------------- 1 | var map = require('lodash').map; 2 | 3 | module.exports = function bindAllFunctionsTo (ctx, fns) { 4 | 5 | return map(fns, function (fn) { 6 | 7 | return fn.bind(ctx); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/utils/flag-runner.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var async = require('async'); 3 | 4 | module.exports = function (flagMap, allFlags, runnerDone) { 5 | 6 | // NOTE: can these be parallel or only series? 7 | 8 | async.eachSeries(Object.keys(flagMap), function (flagName, keyDone) { 9 | 10 | var flags = _.filter(allFlags, function (flag) { 11 | 12 | return flag.matchesName(flagName); 13 | }); 14 | 15 | async.eachSeries(flags, function (flag, flagDone) { 16 | 17 | flag.runOnce(flagMap[flagName], flagDone); 18 | }, keyDone); 19 | }, runnerDone); 20 | }; 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "brace-style": [2, "stroustrup", {"allowSingleLine": false}], 7 | "camelcase": [2, {"properties": "never"}], 8 | "comma-style": [2, "last"], 9 | "consistent-this": [2, "self"], 10 | "indent": [2, 2], 11 | "no-console": 1, 12 | "no-use-before-define": 0, 13 | "consistent-return": 0, 14 | "new-cap": 0, 15 | "no-alert": 0, 16 | "no-underscore-dangle": 0, 17 | "quotes": [2, "single", "avoid-escape"], 18 | "semi": [2, "always"], 19 | "space-before-blocks": [2, "always"], 20 | "space-before-function-paren": [2, "always"], 21 | "space-in-brackets": [2, "never"], 22 | "space-in-parens": [2, "never"], 23 | "strict": 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/utils/parse-command.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | function commandName (args) { 4 | 5 | // This could happen when trying to run a default command 6 | // with or without a flag 7 | if (!args) { 8 | return; 9 | } 10 | 11 | return args.split(':')[0]; 12 | } 13 | 14 | function taskName (args) { 15 | 16 | if (!args) { 17 | return; 18 | } 19 | 20 | return args.split(':')[1]; 21 | } 22 | 23 | function commandData (data) { 24 | 25 | return _.tail(data); 26 | } 27 | 28 | function commandFlags (args) { 29 | 30 | return _.omit(args, '_'); 31 | } 32 | 33 | module.exports = function parseCommand (args) { 34 | 35 | var data = args._[0]; 36 | 37 | return { 38 | name: commandName(data), 39 | task: taskName(data), 40 | data: commandData(args._), 41 | flags: commandFlags(args) 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /lib/utils/extend-flags.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | module.exports = function extendFlags (defaultFlags, flagOverrides) { 4 | 5 | if (!flagOverrides) { 6 | return defaultFlags; 7 | } 8 | 9 | var overrideFlagsWith = _.filter(flagOverrides, function (f) { 10 | 11 | return f.shouldOverride(); 12 | }); 13 | 14 | // Override default flags with command-specific flags 15 | _.each(overrideFlagsWith, function (flag) { 16 | 17 | var flagIndex = -1; 18 | _.find(defaultFlags, function (f, idx) { 19 | 20 | var matches = f.matchesName(flag.name()); 21 | 22 | if (matches) { 23 | flagIndex = idx; 24 | } 25 | 26 | return matches; 27 | }); 28 | 29 | // Found a matching flag to override 30 | if (flagIndex > -1) { 31 | defaultFlags[flagIndex] = flag; 32 | } 33 | }); 34 | 35 | return defaultFlags; 36 | }; 37 | -------------------------------------------------------------------------------- /lib/name.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var flatten = require('flat-arguments'); 3 | 4 | module.exports = function () { 5 | 6 | var aliases = flatten(arguments); 7 | 8 | var methods = { 9 | all: function () { 10 | 11 | return aliases; 12 | }, 13 | 14 | add: function () { 15 | 16 | aliases = _.uniq(aliases.concat(flatten(arguments))); 17 | 18 | return methods; 19 | }, 20 | 21 | remove: function () { 22 | 23 | aliases = _.xor(aliases, flatten(arguments)); 24 | 25 | return methods; 26 | }, 27 | 28 | matches: function () { 29 | 30 | return _.intersection(aliases, flatten(arguments)).length > 0; 31 | }, 32 | 33 | // TODO: test this 34 | matchesAll: function () { 35 | 36 | var names = flatten(arguments); 37 | 38 | return _.intersection(aliases, names).length >= names.length; 39 | }, 40 | 41 | toString: function () { 42 | 43 | return aliases.join(', '); 44 | } 45 | }; 46 | 47 | return Object.freeze(methods); 48 | }; 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nash", 3 | "version": "3.0.0", 4 | "description": "Craft command-line Masterpieces", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "npm run lint && tape test/** | tap-format-spec", 8 | "jshint": "jshint ./lib/** ./test/**", 9 | "lint": "eslint lib/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/scottcorgan/nash.git" 14 | }, 15 | "keywords": [ 16 | "command", 17 | "line", 18 | "cli", 19 | "bash", 20 | "terminal", 21 | "commander", 22 | "prompt" 23 | ], 24 | "author": "Scott Corgan", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/scottcorgan/nash/issues" 28 | }, 29 | "homepage": "https://github.com/scottcorgan/nash", 30 | "dependencies": { 31 | "async": "^1.3.0", 32 | "flat-arguments": "^1.0.0", 33 | "lodash": "^4.17.5", 34 | "minimist": "^1.1.0" 35 | }, 36 | "devDependencies": { 37 | "@tap-format/spec": "0.2.0", 38 | "eslint": "^0.24.0", 39 | "tape": "^4.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Scott Corgan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/wrappers.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var flatten = require('flat-arguments'); 3 | var async = require('async'); 4 | 5 | function lastIsCallback (values) { 6 | 7 | return _.isFunction(_.last(values)); 8 | } 9 | 10 | var exports = module.exports = function (options) { 11 | 12 | return new Wrappers(options); 13 | }; 14 | 15 | var Wrappers = exports.Instance = function (options) { 16 | 17 | this.internals = { 18 | options: options || {}, 19 | all: [] 20 | }; 21 | }; 22 | 23 | Wrappers.prototype.all = function () { 24 | 25 | return this.internals.all; 26 | }; 27 | 28 | Wrappers.prototype.add = function () { 29 | 30 | var fns = flatten(arguments); 31 | 32 | this.internals.all.push(fns); 33 | 34 | return this; 35 | }; 36 | 37 | Wrappers.prototype.run = function () { 38 | 39 | var self = this; 40 | var values = _.toArray(arguments); 41 | 42 | // Async mode 43 | var done = lastIsCallback(values) ? values.pop() : function () {}; 44 | 45 | // this._runAsync(values, done); 46 | async.eachSeries(this.all(), function (wrapperFns, wrapperFnsDone) { 47 | 48 | async.each(wrapperFns, function (fn, fnDone) { 49 | 50 | fn.apply(self, values.concat(fnDone)); 51 | }, wrapperFnsDone); 52 | 53 | }, done); 54 | 55 | return this; 56 | }; 57 | -------------------------------------------------------------------------------- /test/name.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var name = require('../lib/name'); 4 | 5 | test('name: adds names', function (t) { 6 | 7 | t.deepEqual(name().add('test').all(), ['test'], 'add single name'); 8 | t.deepEqual(name().add(['test']).all(), ['test'], 'add single name form array'); 9 | t.deepEqual(name().add(['test', 't']).all(), ['test', 't'], 'add multiples names from array'); 10 | t.deepEqual(name().add('test', 't').all(), ['test', 't'], 'add multiple names as arguments'); 11 | t.deepEqual(name().add('test', 't').add('test').all(), ['test', 't'], 'add multiple names but ensures unique'); 12 | t.end(); 13 | }); 14 | 15 | test('name: removes names', function (t) { 16 | 17 | t.deepEqual(name('test', 't').remove('test').all(), ['t'], 'remove single name'); 18 | t.deepEqual(name('test', 't').remove(['test']).all(), ['t'], 'remove single name with array'); 19 | t.deepEqual(name('test', 't', 'another').remove(['test', 't']).all(), ['another'], 'remove multiple names with array'); 20 | t.deepEqual(name('test', 't', 'another').remove('test', 't').all(), ['another'], 'remove multiple names as arguments'); 21 | t.end(); 22 | }); 23 | 24 | test('name: lists name', function (t) { 25 | 26 | var nm = name('test', 't'); 27 | 28 | t.deepEqual(nm.all(), ['test', 't'], 'name listed'); 29 | t.end(); 30 | }); 31 | 32 | test('name: matching to given name', function (t) { 33 | 34 | var nm = name('test', 't', 'another'); 35 | 36 | t.ok(nm.matches('test'), 'matches single name'); 37 | t.ok(nm.matches(['t']), 'matches single name in array'); 38 | t.ok(nm.matches(['test', 't']), 'matches multiple names in array'); 39 | t.ok(nm.matches('test', 't'), 'matches multiple names as arguments'); 40 | t.end(); 41 | }); 42 | 43 | test('name: toString()', function (t) { 44 | 45 | t.equal(name('test', 't').toString(), 'test, t', 'output'); 46 | t.end(); 47 | }); -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var flatten = require('flat-arguments'); 3 | 4 | var exports = module.exports = function () { 5 | 6 | return new Commands(arguments); 7 | }; 8 | 9 | var Commands = exports.Instance = function () { 10 | 11 | this.internals = { 12 | all: flatten(arguments) 13 | }; 14 | }; 15 | 16 | Commands.prototype.all = function () { 17 | 18 | return this.internals.all; 19 | }; 20 | 21 | Commands.prototype.add = function () { 22 | 23 | var names = _(arguments) 24 | .flatten() 25 | .map(function (cmd) { 26 | return cmd.name(); 27 | }) 28 | .flatten() 29 | .value(); 30 | 31 | // Test for unique command 32 | var cmd = this.findByName(names); 33 | 34 | // Command exists in collection 35 | // Add any names that aren't part of the current command 36 | if (cmd) { 37 | cmd.internals.name.add(names); 38 | } 39 | 40 | // Command doesn't exist in collection 41 | if (!cmd) { 42 | this.internals.all = this.internals.all.concat(flatten(arguments)); 43 | } 44 | 45 | return this; 46 | }; 47 | 48 | Commands.prototype.findByName = function () { 49 | 50 | var names = flatten(arguments); 51 | 52 | return _.find(this.all(), function (cmd) { 53 | 54 | return cmd.matchesName(names); 55 | }); 56 | }; 57 | 58 | Commands.prototype.run = function (command, commandRunDone) { 59 | 60 | if (!command.name) { 61 | return commandRunDone(new Error('Command not Found')); 62 | } 63 | 64 | var commandName = command.name; 65 | var taskName = command.task; 66 | 67 | var cmd = this.findByName(commandName); 68 | 69 | 70 | if (!cmd) { 71 | return commandRunDone(new Error('Command not found')); 72 | } 73 | 74 | // Execute command or task 75 | cmd = cmd.findTask(taskName) || cmd; 76 | 77 | // Run command with data and flags 78 | cmd.run(command.data, command.flags, commandRunDone); 79 | }; 80 | -------------------------------------------------------------------------------- /lib/flags.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var flatten = require('flat-arguments'); 3 | var async = require('async'); 4 | 5 | var exports = module.exports = function () { 6 | 7 | return new Flags(arguments); 8 | }; 9 | 10 | var Flags = exports.Instance = function () { 11 | 12 | this.internals = { 13 | all: flatten(arguments) 14 | }; 15 | }; 16 | 17 | Flags.prototype.all = function () { 18 | 19 | return this.internals.all; 20 | }; 21 | 22 | Flags.prototype.add = function () { 23 | 24 | // Parse names of given flags 25 | var names = _(arguments) 26 | .flatten() 27 | .map(function (flg) { 28 | return flg.name(); 29 | }) 30 | .flatten() 31 | .value(); 32 | 33 | // Test for unique flag 34 | var flg = this.findByName(names); 35 | 36 | // Flag exists in collection 37 | // Add any names that aren't part of the current flag 38 | if (flg) { 39 | flg.internals.name.add(names); 40 | } 41 | 42 | // Flag doesn't exist in collection 43 | if (!flg) { 44 | this.internals.all = this.internals.all.concat(flatten(arguments)); 45 | } 46 | 47 | return this; 48 | }; 49 | 50 | Flags.prototype.findByName = function () { 51 | 52 | var names = flatten(arguments); 53 | 54 | return _.find(this.all(), function (flag) { 55 | 56 | return flag.matchesName(names); 57 | }); 58 | }; 59 | 60 | Flags.prototype.run = function (flagMap, doneRunningFlags) { 61 | var findByName = this.findByName.bind(this); 62 | var flagsToRun = _(flagMap) 63 | .keys() 64 | .map(function (flagName) { 65 | 66 | var flag = findByName(flagName); 67 | 68 | if (flag) { 69 | return { 70 | instance: flag, 71 | args: flagMap[flagName] 72 | }; 73 | } 74 | }, this) 75 | .filter(_.identity) 76 | .value(); 77 | 78 | async.each(flagsToRun, function (flag, flagDone) { 79 | 80 | flag.instance.runOnce(flag.args, flagDone); 81 | }, doneRunningFlags); 82 | }; 83 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.0](https://github.com/scottcorgan/nash/issues?q=milestone%3A2.0+is%3Aclosed) 4 | 5 | **Released on 3-2-2015** 6 | 7 | * **[#30](https://github.com/scottcorgan/nash/issues/30)** - FIXED: Review command before and after async relation to the command 8 | * **[#37](https://github.com/scottcorgan/nash/issues/37)** - BREAKING: handler arguments should be an object instead of separate arguments 9 | * **[#39](https://github.com/scottcorgan/nash/issues/39)** - BREAKING: Remove Sync mode. Async mode only for commands, flags, and all the things. 10 | * **[#42](https://github.com/scottcorgan/nash/issues/42)** - BREAKING: Flag assumed async 11 | * **[#44](https://github.com/scottcorgan/nash/issues/44)** - BREAKING: Deprecate `exit()` method 12 | * **[#45](https://github.com/scottcorgan/nash/issues/45)** - BREAKING: Remove deprecate methods 13 | * **[#46](https://github.com/scottcorgan/nash/issues/46)** - BREAKING: deprecate `onInvalidCommand` in favor of `default()` 14 | * **[#47](https://github.com/scottcorgan/nash/issues/47)** - Document running a command by name 15 | * **[#49](https://github.com/scottcorgan/nash/issues/49)** - Remove remnants of built in usage screen 16 | * **[#52](https://github.com/scottcorgan/nash/issues/52)** - NEW: Create cli-level process object with argv data (command, task, data, flags, etc.) 17 | * **[#53](https://github.com/scottcorgan/nash/issues/53)** - Run tests on Travis instead of Codeship 18 | * **[#54](https://github.com/scottcorgan/nash/issues/54)** - NEW: set values with an object 19 | * **[#55](https://github.com/scottcorgan/nash/issues/55)** - BREAKING: plugins should just export a function 20 | 21 | ### Breaking Changes 22 | 23 | There are quite a few breaking changes in this release. They are noted above. See the documentation for how upgrade your code. 24 | 25 | * All commands, flags, befores, and afters are assumed async. All features that could be run in a synchronous way have been updated to use only the async mode. This creates consistency across the module and gives us one less thing to think about. 26 | * Handlers on commands now receive 3 arguments always: `data`, `flags`, `done`. The `data` argument is an array of all the non-command and non-flag data from `process.argv`. The `flags` object is a key/value map of flags defined on the cli and used from `process.argv`. The `done` argument is the callback that must be called to pass execution back to the cli level. 27 | * Several command and flag level methods have been removed. They are listed above. 28 | * How plugins are registered and changed slightly. Instead of exporting a register function in the plugin, a plugin should now export a function. 29 | 30 | 31 | * * * 32 | 33 | (For releases previous to **2.0.0**, see [releases](https://github.com/scottcorgan/nash/releases)) 34 | 35 | -------------------------------------------------------------------------------- /lib/flag.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | 3 | var _ = require('lodash'); 4 | var flatten = require('flat-arguments'); 5 | 6 | var name = require('./name'); 7 | 8 | var flag = module.exports = function () { 9 | 10 | // TODO: test command decorators individually 11 | _.each(flag.decorators, function (decorator) { 12 | 13 | Flag.prototype[decorator.name] = decorator.handler; 14 | }); 15 | 16 | // Also ensure that the name is always an array 17 | // This gives us the ability to have multiple names/aliases 18 | return new Flag(arguments); 19 | }; 20 | 21 | flag.decorators = []; 22 | 23 | flag.prefix = function (flagName) { 24 | 25 | // i.e. --flag or --test 26 | if (flagName.slice(0, 2) === '--' && flagName.length > 2) { 27 | return flagName; 28 | } 29 | 30 | // i.e: -t or -f 31 | if (flagName.slice(0, 1) === '-' && flagName.length < 3) { 32 | return flagName; 33 | } 34 | 35 | return (flagName.length === 1) 36 | ? '-' + flagName 37 | : '--' + flagName; 38 | }; 39 | 40 | flag.unprefix = function (flagName) { 41 | 42 | // --flag 43 | if (flagName.slice(0, 2) === '--') { 44 | return flagName.slice(2); 45 | } 46 | 47 | // -f 48 | if (flagName.slice(0, 1) === '-') { 49 | return flagName.slice(1); 50 | } 51 | 52 | return flagName; 53 | }; 54 | 55 | function Flag () { 56 | 57 | this.internals = { 58 | name: name(arguments), 59 | ran: false, 60 | handler: function (val, done) { 61 | 62 | done(); 63 | } 64 | }; 65 | 66 | _.extend(this, EventEmitter.prototype); 67 | } 68 | 69 | Flag.prototype.run = function (data, done) { 70 | 71 | done = done || function () {}; 72 | 73 | var self = this; 74 | 75 | function runDone () { 76 | 77 | self.internals.ran = true; 78 | done(); 79 | } 80 | 81 | this.internals.handler(data, runDone); 82 | 83 | return this; 84 | }; 85 | 86 | Flag.prototype.runOnce = function () { 87 | 88 | if (!this.internals.ran) { 89 | this.run.apply(this, arguments); 90 | } 91 | else { 92 | // Already ran. Just finish. 93 | _(arguments).toArray().last()(); 94 | } 95 | 96 | return this; 97 | }; 98 | 99 | Flag.prototype.matchesName = function () { 100 | 101 | var names = _.map(flatten(arguments), flag.prefix); 102 | 103 | return this.internals.name.matches(names); 104 | }; 105 | 106 | Flag.prototype.handler = function (fn) { 107 | 108 | if (arguments.length === 0) { 109 | return this.internals.handler; 110 | } 111 | 112 | this.internals.handler = fn.bind(this); 113 | 114 | return this; 115 | }; 116 | 117 | Flag.prototype.shouldOverride = function () { 118 | 119 | return this.internals.override; 120 | }; 121 | 122 | Flag.prototype.override = function (val) { 123 | 124 | this.internals.override = (val !== false) ? true : false; // Is true if no value 125 | 126 | return this; 127 | }; 128 | 129 | Flag.prototype.name = function (val) { 130 | 131 | if (arguments.length === 0) { 132 | return this.internals.name.all(); 133 | } 134 | 135 | this.internals.name.add(val); 136 | 137 | return this; 138 | }; 139 | -------------------------------------------------------------------------------- /test/flag.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var flag = require('../lib/flag'); 4 | 5 | test('flag: extends EventEmitter', function (t) { 6 | 7 | var EventEmitter = require('events').EventEmitter; 8 | var flg = flag('-t'); 9 | 10 | t.ok(flg.emit, 'emit'); 11 | t.ok(flg.addListener, 'addListener'); 12 | t.ok(flg.on, 'on'); 13 | t.ok(flg.once, 'once'); 14 | t.ok(flg.removeListener, 'removeListener'); 15 | t.ok(flg.removeAllListeners, 'removeAllListeners'); 16 | t.ok(flg.listeners, 'listeners'); 17 | 18 | t.end(); 19 | }); 20 | 21 | test('flag: handler', function (t) { 22 | 23 | var handlerCalled = false; 24 | var flg = flag('test') 25 | .handler(function (done) { 26 | 27 | handlerCalled = true; 28 | 29 | done(); 30 | }); 31 | 32 | flg.handler()(function () { 33 | 34 | t.equal(typeof flg.handler(), 'function', 'gets function'); 35 | t.ok(handlerCalled, 'sets the function'); 36 | t.end(); 37 | }); 38 | }); 39 | 40 | test('flag: getters and setters', function (t) { 41 | 42 | var flg = flag('-f') 43 | .override(true); 44 | 45 | t.deepEqual(flg.name(), ['-f'], 'name'); 46 | t.equal(flg.shouldOverride(), true, 'override'); 47 | t.end(); 48 | }); 49 | 50 | test('flag: matches flag name', function (t) { 51 | 52 | var flg = flag('--test', '-t'); 53 | 54 | t.ok(flg.matchesName('--test'), 'matches single name'); 55 | t.ok(flg.matchesName('-t'), 'matches other names'); 56 | t.ok(flg.matchesName(['-t']), 'accepts and array of names'); 57 | t.end(); 58 | }); 59 | 60 | test('flag: running the flag', function (t) { 61 | 62 | var flagCalled = false; 63 | var flagCallCount = 0; 64 | var flg = flag('-t') 65 | .handler(function (data, done) { 66 | 67 | flagCalled = true; 68 | flagCallCount += 1; 69 | 70 | t.equal(data, 'data', 'flag value'); 71 | 72 | done(); 73 | }); 74 | 75 | flg.run('data', function () { 76 | 77 | flg.runOnce('data', function () { 78 | 79 | t.ok(flagCalled, 'ran flag'); 80 | t.equal(flagCallCount, 1, 'called only once when using runOnce()'); 81 | t.ok(flg.internals.ran, 'flag ran set to true'); 82 | t.end(); 83 | }); 84 | }); 85 | }); 86 | 87 | test('flag: prefixes', function (t) { 88 | 89 | t.equal(flag.prefix('test'), '--test', '2 dashes to a word'); 90 | t.equal(flag.prefix('t'), '-t', '1 dash for a letter'); 91 | t.equal(flag.prefix('--test'), '--test', 'add no dashes for a name with dashes already'); 92 | t.equal(flag.prefix('-t'), '-t', 'add no dashes for a letter with a dash already'); 93 | t.end(); 94 | }); 95 | 96 | test('flag: unprefixes', function (t) { 97 | 98 | t.equal(flag.unprefix('--test'), 'test', '2 dashes to a word'); 99 | t.equal(flag.unprefix('-t'), 't', '1 dash for a letter'); 100 | t.equal(flag.unprefix('test'), 'test', 'add no dashes for a name with no dashes already'); 101 | t.equal(flag.unprefix('t'), 't', 'add no dashes for a letter with no dash already'); 102 | t.end(); 103 | }); 104 | 105 | test.skip('flag: default value', function (t) { 106 | 107 | var f = flag(); 108 | 109 | f.default('my value'); 110 | 111 | t.equal(f.default(), 'my value', 'set default'); 112 | t.deepEqual(f.default('another value'), f, 'chainable'); 113 | t.end(); 114 | }); 115 | -------------------------------------------------------------------------------- /test/flags.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var flags = require('../lib/flags'); 4 | var flag = require('../lib/flag'); 5 | 6 | test('flags: instance', function (t) { 7 | 8 | t.ok(flags() instanceof flags.Instance, 'instance of Flags'); 9 | t.end(); 10 | }); 11 | 12 | test('flags: instantiates with a list of flags', function (t) { 13 | 14 | var flg = flag('-t'); 15 | var flg2 = flag('-t2'); 16 | 17 | t.deepEqual(flags(flg).all(), [flg], 'with single flag'); 18 | t.deepEqual(flags([flg]).all(), [flg], 'with single flag in array'); 19 | t.deepEqual(flags([flg, flg2]).all(), [flg, flg2], 'with multiple flags in array'); 20 | t.deepEqual(flags(flg, flg2).all(), [flg, flg2], 'with multiple flags as arguments'); 21 | t.end(); 22 | }); 23 | 24 | test('flags: raw array of flags', function (t) { 25 | 26 | var flg = flag('-t'); 27 | var flgs = flags(flg); 28 | 29 | t.deepEqual(flgs.all(), [flg], 'list of flags'); 30 | t.end(); 31 | }); 32 | 33 | test('flags: add flag to collection', function (t) { 34 | 35 | var flg = flag('-t'); 36 | var flg2 = flag('-t2'); 37 | 38 | t.deepEqual(flags().add(flg).all(), [flg], 'added single flag'); 39 | t.deepEqual(flags().add([flg]).all(), [flg], 'added single flag in array'); 40 | t.deepEqual(flags().add([flg, flg2]).all(), [flg, flg2], 'added multiple flags in array'); 41 | t.deepEqual(flags().add(flg, flg2).all(), [flg, flg2], 'added multiple flags as arguments'); 42 | t.end(); 43 | }); 44 | 45 | test('flags: does not overwrite flag when adding to collection', function (t) { 46 | 47 | var flgs = flags(); 48 | var handler = function () {/* handler */}; 49 | 50 | var flg = flag('--test') 51 | .handler(handler); 52 | 53 | var flg2 = flag('--test', '-t'); 54 | 55 | flgs.add(flg); 56 | flgs.add(flg2); 57 | 58 | t.equal(flgs.all().length, 1, 'one uqique'); 59 | t.equal(flgs.findByName('test').handler().toString(), handler.bind(flg).toString(), 'same flg handler'); 60 | t.ok(flgs.findByName('-t'), 'adds missing names when flags match'); 61 | t.end(); 62 | }); 63 | 64 | test('flags: find flag by name', function (t) { 65 | 66 | var flg1 = flag('-t1', '--test1', '--another1'); 67 | var flg2 = flag('--test2', '-t2'); 68 | var flgs = flags(flg1, flg2); 69 | 70 | t.deepEqual(flgs.findByName('--test1'), flg1, 'by single name'); 71 | t.deepEqual(flgs.findByName(['--test1']), flg1, 'by single name in array'); 72 | t.deepEqual(flgs.findByName(['--test1', '-t1']), flg1, 'by multiple names in array'); 73 | t.deepEqual(flgs.findByName('--test1', '-t1'), flg1, 'by multiple names as arguments'); 74 | 75 | t.deepEqual(flgs.findByName('test1'), flg1, 'by single name with no dashes'); 76 | t.deepEqual(flgs.findByName(['test1', '-t1']), flg1, 'by multiple names with no dashes'); 77 | 78 | t.end(); 79 | }); 80 | 81 | test('flags: runs flags', function (t) { 82 | 83 | t.plan(5); 84 | 85 | var flg1Ran = false; 86 | var flg2Ran = false; 87 | 88 | var flg1 = flag('-t') 89 | .handler(function (value, done) { 90 | 91 | t.equal(value, 't value', 'passed in value'); 92 | 93 | flg1Ran = true; 94 | done(); 95 | }); 96 | 97 | var flg2 = flag('--test2') 98 | .handler(function (value, done) { 99 | 100 | t.equal(value, 'test2 value', 'passed in value'); 101 | t.ok(typeof done === 'function', 'passed in callback'); 102 | 103 | flg2Ran = true; 104 | done(); 105 | }); 106 | 107 | var flgs = flags(flg1, flg2); 108 | 109 | flgs.run({ 110 | t: 't value', 111 | test2: 'test2 value' 112 | }, function (err) { 113 | 114 | t.ok(flg1Ran, 'ran flag 1'); 115 | t.ok(flg2Ran, 'ran flag 2'); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/wrappers.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var wrappers = require('../lib/wrappers'); 4 | 5 | test('wrappers: instance', function (t) { 6 | 7 | t.ok(wrappers() instanceof wrappers.Instance, 'instance of wrappers'); 8 | t.ok(wrappers().internals.options, 'initiates options'); 9 | t.end(); 10 | }); 11 | 12 | test('wrappers: raw array of wrappers', function (t) { 13 | 14 | var wrap = wrappers(); 15 | 16 | t.ok(wrap.internals.all, 'all internal'); 17 | t.ok(wrap.all, 'all getter'); 18 | t.end(); 19 | }); 20 | 21 | test('wrappers: adds single wrappers', function (t) { 22 | 23 | t.deepEqual(wrappers().add(wrapFn).all(), [[wrapFn]], 'wrapper fn as argument'); 24 | t.deepEqual(wrappers().add([wrapFn]).all(), [[wrapFn]], 'wrapper fn from array'); 25 | t.end(); 26 | 27 | function wrapFn () { 28 | wrapFn(); 29 | } 30 | }); 31 | 32 | test('wrappers: adds multiple wrappers at a time', function (t) { 33 | 34 | var wrap1 = wrappers(); 35 | var wrap2 = wrappers(); 36 | 37 | wrap1.add(fn1); 38 | wrap1.add([fn1, fn2]); 39 | wrap2.add(fn1, fn2); 40 | 41 | t.deepEqual(wrap1.all(), [[fn1], [fn1, fn2]], 'adds multiple functions from array'); 42 | t.deepEqual(wrap2.all(), [[fn1, fn2]], 'adds multiple functions as arguments'); 43 | t.end(); 44 | 45 | function fn1 () {} 46 | function fn2() {} 47 | }); 48 | 49 | test('wrappers: runs all the wrapper functions', function (t) { 50 | 51 | var wrap = wrappers(); 52 | var wrapFn1Called = false; 53 | var wrapFn2Called = false; 54 | var wrapFn3Called = false; 55 | 56 | wrap.add(function (done) { 57 | 58 | wrapFn1Called = true; 59 | done(); 60 | }); 61 | 62 | wrap.add(function (done) { 63 | 64 | wrapFn2Called = true; 65 | done(); 66 | }, function (done) { 67 | 68 | wrapFn3Called = true; 69 | done(); 70 | }); 71 | 72 | wrap.run(function () { 73 | 74 | t.ok(wrapFn1Called, 'called first fn'); 75 | t.ok(wrapFn2Called, 'called second fn'); 76 | t.ok(wrapFn3Called, 'called third fn'); 77 | t.end(); 78 | }); 79 | }); 80 | 81 | test('wrappers: runs wrapper functions with given values passed into functions', function (t) { 82 | 83 | t.plan(4); 84 | 85 | var wrap = wrappers(); 86 | 87 | wrap.add(function (val1, val2, done) { 88 | 89 | t.equal(val1, 'val1', 'passed in value 1'); 90 | t.equal(val2, 'val2', 'passed in value 2'); 91 | t.ok(typeof done, 'function', 'passed in callback'); 92 | 93 | done(); 94 | }); 95 | 96 | wrap.run('val1', 'val2', function () { 97 | 98 | t.ok(true, 'done'); 99 | }); 100 | }); 101 | 102 | test('wrappers: runs commands added seperately in series', function (t) { 103 | 104 | t.plan(1); 105 | 106 | var callstack = []; 107 | var wrap = wrappers(); 108 | 109 | wrap 110 | .add(function (done) { 111 | 112 | callstack.push('wrap1'); 113 | done(); 114 | }) 115 | .add(function (done) { 116 | 117 | callstack.push('wrap2'); 118 | done(); 119 | }); 120 | 121 | wrap.run(function () { 122 | 123 | t.deepEqual(callstack, ['wrap1', 'wrap2']); 124 | }); 125 | }); 126 | 127 | test('wrappers: runs commands added together in parallel', function (t) { 128 | 129 | var callstack = []; 130 | var wrap = wrappers(); 131 | 132 | wrap 133 | .add(function (done) { 134 | 135 | callstack.push('wrap1'); 136 | done(); 137 | }) 138 | .add( 139 | function (done) { 140 | 141 | process.nextTick(function () { 142 | 143 | callstack.push('wrap2'); 144 | done(); 145 | }); 146 | }, 147 | function (done) { 148 | 149 | callstack.push('wrap3'); 150 | done(); 151 | } 152 | ) 153 | .add(function (done) { 154 | 155 | callstack.push('wrap4'); 156 | done(); 157 | }); 158 | 159 | wrap.run(function () { 160 | 161 | var expectedCallstack = ['wrap1', 'wrap3', 'wrap2', 'wrap4']; 162 | 163 | t.deepEqual(callstack, expectedCallstack, 'called in series or parallel'); 164 | t.end(); 165 | }); 166 | }); -------------------------------------------------------------------------------- /test/commands.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var commands = require('../lib/commands'); 4 | var defineCommand = require('../lib/command'); 5 | 6 | test('commands: instance', function (t) { 7 | 8 | var cmds = commands(); 9 | 10 | t.ok(cmds instanceof commands.Instance, 'instance of Commands'); 11 | t.end(); 12 | }); 13 | 14 | test('commands: instantiates with list of commands', function (t) { 15 | 16 | var cmd = defineCommand('test'); 17 | var cmd2 = defineCommand('test2'); 18 | 19 | t.deepEqual(commands(cmd).all(), [cmd], 'with single command'); 20 | t.deepEqual(commands([cmd]).all(), [cmd], 'with single command in array'); 21 | t.deepEqual(commands([cmd, cmd2]).all(), [cmd, cmd2], 'with multiple commands in array'); 22 | t.deepEqual(commands(cmd, cmd2).all(), [cmd, cmd2], 'with multiple commands as arguments'); 23 | t.end(); 24 | }); 25 | 26 | test('commands: raw array of commands', function (t) { 27 | 28 | var cmd = defineCommand('test'); 29 | var cmds = commands().add(cmd); 30 | 31 | t.deepEqual(cmds.all(), [cmd], 'list of commands'); 32 | t.end(); 33 | }); 34 | 35 | test('commands: add command to collection', function (t) { 36 | 37 | var cmd = defineCommand('test'); 38 | var cmd2 = defineCommand('test2'); 39 | 40 | t.deepEqual(commands().add(cmd).all(), [cmd], 'added single command'); 41 | t.deepEqual(commands().add([cmd]).all(), [cmd], 'added single command in array'); 42 | t.deepEqual(commands().add([cmd, cmd2]).all(), [cmd, cmd2], 'added multiple commands in array'); 43 | t.deepEqual(commands().add(cmd, cmd2).all(), [cmd, cmd2], 'added multiple commands as arguments'); 44 | t.end(); 45 | }); 46 | 47 | test('commands: does not overwrite command when adding to collection', function (t) { 48 | 49 | var cmds = commands(); 50 | var handler = function () {/* handler */}; 51 | 52 | var cmd = defineCommand('test') 53 | .handler(handler); 54 | // .description('test description'); 55 | 56 | var cmd2 = defineCommand('test', 't'); 57 | 58 | cmds.add(cmd); 59 | cmds.add(cmd2); 60 | 61 | t.equal(cmds.all().length, 1, 'one uqique'); 62 | t.equal(cmds.findByName('test').handler().toString(), handler.bind(cmd).toString(), 'same command handler'); 63 | t.ok(cmds.findByName('t'), 'adds missing names when commands match'); 64 | t.end(); 65 | }); 66 | 67 | test('commands: find command by name', function (t) { 68 | 69 | var cmd1 = defineCommand('test1', 't1', 'another'); 70 | var cmd2 = defineCommand('test2', 't2'); 71 | var cmds = commands().add(cmd1, cmd2); 72 | 73 | t.deepEqual(cmds.findByName('test1'), cmd1, 'by single name'); 74 | t.deepEqual(cmds.findByName(['test1']), cmd1, 'by single name in array'); 75 | 76 | // TODO: fix this test. If I change "t1" to "t1s", the test still passes 77 | // NOTE: this is probably an issue because when we find a command by name, 78 | // it doesn't check both names (_.intersection) 79 | t.deepEqual(cmds.findByName(['test1', 't1']), cmd1, 'by multiple names in array'); 80 | 81 | t.deepEqual(cmds.findByName('test1', 't1'), cmd1, 'by multiple names as arguments'); 82 | t.end(); 83 | }); 84 | 85 | test('commands: runs commands', function (t) { 86 | 87 | t.plan(5); 88 | 89 | var cmdRan = false; 90 | var ranFlag = false; 91 | var ranAsyncFlag = false; 92 | 93 | var cmd = defineCommand('test') 94 | .handler(function (data, flags, done) { 95 | 96 | t.equal(data[0], 'data', 'passed in data'); 97 | t.ok(typeof done === 'function', 'passed in callback'); 98 | 99 | cmdRan = true; 100 | done(); 101 | }); 102 | 103 | cmd.flag('-t') 104 | .handler(function (val, done) { 105 | 106 | ranFlag = true; 107 | done(); 108 | }); 109 | 110 | cmd.flag('-a') 111 | .handler(function (val, done) { 112 | 113 | ranAsyncFlag = true; 114 | done(); 115 | }); 116 | 117 | var cmds = commands(cmd); 118 | 119 | cmds.run({ 120 | name:'test', 121 | data: ['data'], 122 | flags: { 123 | t: 't flag', 124 | a: true 125 | } 126 | }, function (err) { 127 | 128 | t.ok(ranFlag, 'ran flag'); 129 | t.ok(ranAsyncFlag, 'ran async flag'); 130 | t.ok(cmdRan, 'ran command'); 131 | }); 132 | }); -------------------------------------------------------------------------------- /lib/command.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | 3 | var _ = require('lodash'); 4 | var async = require('async'); 5 | var flatten = require('flat-arguments'); 6 | 7 | var flagFactory = require('./flag'); 8 | var flags = require('./flags'); 9 | var commands = require('./commands'); 10 | var name = require('./name'); 11 | var wrappers = require('./wrappers'); 12 | var flagRunner = require('./utils/flag-runner'); 13 | var bindAll = require('./utils/bind-all-fns'); 14 | 15 | var commandFactory = module.exports = function () { 16 | 17 | // TODO: test command decorators individually 18 | _.each(commandFactory.decorators, function (decorator) { 19 | 20 | Command.prototype[decorator.name] = decorator.handler; 21 | }); 22 | 23 | // Also ensure that the name is always an array 24 | // This gives us the ability to have multiple names/aliases 25 | return new Command(arguments); 26 | }; 27 | 28 | commandFactory.decorators = []; 29 | 30 | // helps avoid runtime errors with missing commands 31 | commandFactory.noop = function () { 32 | 33 | return new Command(); 34 | }; 35 | 36 | // Command Class 37 | function Command () { 38 | 39 | this.internals = { 40 | name: name(arguments), 41 | befores: wrappers(), 42 | afters: wrappers(), 43 | flags: flags(), 44 | tasks: commands(), 45 | handler: null 46 | }; 47 | 48 | _.extend(this, EventEmitter.prototype); 49 | } 50 | 51 | Command.prototype.run = function (data, flagMap, done) { 52 | 53 | done = done || function () {}; 54 | 55 | var self = this; 56 | 57 | // Execute all aspects of running a command 58 | async.series({ 59 | befores: function (beforesDone) { 60 | 61 | self.runBefores(data, flagMap, beforesDone); 62 | }, 63 | flags: function (flagsDone) { 64 | 65 | self.runFlags(flagMap, flagsDone); 66 | }, 67 | handler: function (handlerDone) { 68 | 69 | var handler = self.internals.handler; 70 | 71 | if (handler) { 72 | handler(data, flagMap, handlerDone); 73 | } 74 | else { 75 | done(); 76 | } 77 | }, 78 | afters: function (aftersDone) { 79 | 80 | self.runAfters(data, flagMap, aftersDone); 81 | } 82 | }, done); 83 | 84 | return this; 85 | }; 86 | 87 | Command.prototype.runBefores = function (data, flgs, done) { 88 | 89 | done = done || function () {}; 90 | 91 | // Set all befores as async or not 92 | this.internals.befores 93 | .run(data, flgs, done); 94 | 95 | return this; 96 | }; 97 | 98 | Command.prototype.runAfters = function (data, flgs, done) { 99 | 100 | done = done || function () {}; 101 | 102 | // Set all afters as async or not 103 | this.internals.afters 104 | .run(data, flgs, done); 105 | 106 | return this; 107 | }; 108 | 109 | Command.prototype.runFlags = function (flagMap, done) { 110 | 111 | done = done || function () {}; 112 | 113 | var allFlags = this.internals.flags.all(); 114 | 115 | flagRunner(flagMap, allFlags, done); 116 | }; 117 | 118 | Command.prototype.matchesName = function () { 119 | 120 | return this.internals.name.matches(arguments); 121 | }; 122 | 123 | Command.prototype.matchesTask = function (commandName, taskName) { 124 | 125 | return this.matchesName(commandName) && this.findTask(taskName); 126 | }; 127 | 128 | Command.prototype.handler = function (fn) { 129 | 130 | if (arguments.length === 0) { 131 | return this.internals.handler; 132 | } 133 | 134 | this.internals.handler = fn.bind(this); 135 | 136 | return this; 137 | }; 138 | 139 | Command.prototype.task = function () { 140 | 141 | var names = flatten(arguments); 142 | 143 | this.internals.tasks.add(commandFactory(names)); 144 | 145 | var task = this.findTask(names); 146 | 147 | // Ensure tasks and create tasks on itself 148 | task.task = undefined; 149 | 150 | return task; 151 | }; 152 | 153 | Command.prototype.flag = function () { 154 | var names = flatten(arguments); 155 | 156 | this.internals.flags.add(flagFactory(names)); 157 | 158 | return this.findFlag(names); 159 | }; 160 | 161 | Command.prototype.findTask = function () { 162 | 163 | return this.internals.tasks.findByName(arguments); 164 | }; 165 | 166 | Command.prototype.findFlag = function () { 167 | 168 | return this.internals.flags.findByName(arguments); 169 | }; 170 | 171 | Command.prototype.name = function (val) { 172 | 173 | if (arguments.length === 0) { 174 | return this.internals.name.all(); 175 | } 176 | 177 | this.internals.name.add(val); 178 | 179 | return this; 180 | }; 181 | 182 | Command.prototype.before = function (fn) { 183 | 184 | if (!fn) { 185 | return this.internals.befores.all(); 186 | } 187 | 188 | this.internals.befores.add(bindAll(this, arguments)); 189 | 190 | return this; 191 | }; 192 | 193 | Command.prototype.after = function (fn) { 194 | 195 | if (!fn) { 196 | return this.internals.afters.all(); 197 | } 198 | 199 | this.internals.afters.add(bindAll(this, arguments)); 200 | 201 | return this; 202 | }; 203 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | 3 | var _ = require('lodash'); 4 | var minimist = require('minimist'); 5 | var flatten = require('flat-arguments'); 6 | var async = require('async'); 7 | 8 | var commandFactory = require('./command'); 9 | var commands = require('./commands'); 10 | var flags = require('./flags'); 11 | var flagFactory = require('./flag'); 12 | var wrappers = require('./wrappers'); 13 | var parseCommand = require('./utils/parse-command'); 14 | var extendFlags = require('./utils/extend-flags'); 15 | var bindAll = require('./utils/bind-all-fns'); 16 | var flagRunner = require('./utils/flag-runner'); 17 | 18 | function parseArgv (processArgv, rawFlags) { 19 | 20 | // WIP: parse flags with defaults 21 | var argvOptions = { 22 | alias: {}, 23 | default: {} 24 | }; 25 | 26 | rawFlags.forEach(function (flag) { 27 | 28 | var names = flag.name().map(flagFactory.unprefix); 29 | var baseName = names[0]; 30 | var aliases = []; 31 | 32 | if (names.length > 1) { 33 | aliases = _.tail(names); 34 | } 35 | 36 | argvOptions.alias[baseName] = aliases; 37 | }); 38 | 39 | return minimist(_.slice(processArgv, 2), argvOptions); 40 | } 41 | 42 | var Cli = exports.Cli = function (options) { 43 | 44 | this.process = {}; 45 | this.attributes = {}; 46 | this.options = options || {}; 47 | this.internals = { 48 | commands: commands(), 49 | flags: flags(), 50 | beforeAlls: wrappers(), 51 | afterAlls: wrappers(), 52 | decorators: {}, 53 | plugins: [] 54 | }; 55 | 56 | _.extend(this, EventEmitter.prototype); 57 | }; 58 | 59 | module.exports = function createCli (options) { 60 | return new Cli(options); 61 | }; 62 | 63 | 64 | // Instantiators 65 | 66 | Cli.prototype.command = function () { 67 | 68 | var names = flatten(arguments); 69 | var command = commandFactory(names); 70 | 71 | this.internals.commands.add(command); 72 | 73 | return this.findCommand(names); 74 | }; 75 | 76 | Cli.prototype.flag = function () { 77 | 78 | var names = flatten(arguments); 79 | var flag = flagFactory(names); 80 | 81 | this.internals.flags.add(flag); 82 | 83 | return this.findFlag(names); 84 | }; 85 | 86 | Cli.prototype.beforeAll = function () { 87 | 88 | this.internals.beforeAlls.add(bindAll(this, arguments)); 89 | 90 | return this; 91 | }; 92 | 93 | Cli.prototype.afterAll = function () { 94 | 95 | this.internals.afterAlls.add(bindAll(this, arguments)); 96 | 97 | return this; 98 | }; 99 | 100 | Cli.prototype.default = function () { 101 | 102 | var command = commandFactory(); 103 | this.internals.defaultCommand = this.internals.defaultCommand || command; 104 | 105 | return command; 106 | }; 107 | 108 | Cli.prototype.run = function (processArgv, runDone) { 109 | 110 | runDone = runDone || function () {}; 111 | 112 | var self = this; 113 | var command = parseCommand(minimist(_.slice(processArgv, 2))); 114 | 115 | // Set up decorators 116 | command.decorators = this.internals.decorators.command; 117 | 118 | // Execute command, task, or nothing 119 | var commandToRun = 120 | self.findCommandTask(command.name, command.task) || 121 | self.findCommand(command.name); 122 | 123 | // Parse all falgs with aliases, defaults, etc. 124 | this.argv = parseArgv( 125 | processArgv, 126 | extendFlags( 127 | self.internals.flags.all(), 128 | commandToRun ? commandToRun.internals.flags.all() : [] 129 | ) 130 | ); 131 | command.flags = _.omit(this.argv); 132 | 133 | // Set process data 134 | this.process = Object.freeze({ 135 | command: command.name, 136 | task: command.task, 137 | data: command.data, 138 | flags: _.omit(this.argv, '_') 139 | }); 140 | 141 | // Run everything 142 | async.series({ 143 | beforeAlls: function (done) { 144 | 145 | self.runBeforeAlls(command.data, command.flags, done); 146 | }, 147 | flags: function (done) { 148 | 149 | var commandFlags; 150 | 151 | // If command that is getting run has flags that match 152 | // global flags, we need to run those too 153 | if (commandToRun) { 154 | commandFlags = commandToRun.internals.flags.all(); 155 | } 156 | 157 | self.runFlags(command.flags, done, commandFlags); 158 | }, 159 | command: function (done) { 160 | 161 | self.internals.commands.run(command, function (err) { 162 | 163 | // Try running default command 164 | if (err && self.internals.defaultCommand) { 165 | command.data = self.argv._; 166 | return self.internals.defaultCommand.run(command.data, command.flags, done); 167 | } 168 | 169 | // Finish up no matter what 170 | done(); 171 | }); 172 | }, 173 | afterAlls: function (done) { 174 | 175 | self.runAfterAlls(command.data, command.flags, done); 176 | } 177 | }, runDone); 178 | 179 | return this; 180 | }; 181 | 182 | 183 | // Runners 184 | 185 | Cli.prototype.runFlags = function (flagMap, done, _commandFlags_) { 186 | 187 | // Combine global and command-level flags 188 | var flgs = extendFlags(this.internals.flags.all(), _commandFlags_); 189 | 190 | // Run flags from flag map 191 | flagRunner(flagMap, flgs, done); 192 | }; 193 | 194 | Cli.prototype.runFlag = function (name, data, done) { 195 | 196 | done = done || function () {}; 197 | 198 | var flag = this.findFlag(name); 199 | 200 | if (!flag) { 201 | throw new Error('Flag does not exist'); 202 | } 203 | 204 | flag.run(data, done); 205 | 206 | return this; 207 | }; 208 | 209 | Cli.prototype.runBeforeAlls = function (data, flgs, done) { 210 | 211 | this.internals.beforeAlls.run(data, flgs, done); 212 | 213 | return this; 214 | }; 215 | 216 | Cli.prototype.runAfterAlls = function (data, flgs, done) { 217 | 218 | this.internals.afterAlls.run(data, flgs, done); 219 | 220 | return this; 221 | }; 222 | 223 | 224 | // Finders 225 | 226 | Cli.prototype.findCommand = function () { 227 | 228 | return this.internals.commands.findByName(arguments); 229 | }; 230 | 231 | Cli.prototype.findCommandTask = function (commandName, taskName) { 232 | 233 | var cmd = this.findCommand(commandName); 234 | 235 | if (!cmd) { 236 | return; 237 | } 238 | 239 | return cmd.findTask(taskName); 240 | }; 241 | 242 | Cli.prototype.findFlag = function () { 243 | 244 | return this.internals.flags.findByName(arguments); 245 | }; 246 | 247 | Cli.prototype.decorate = function (target, name, handler) { 248 | 249 | if (target === 'flag') { 250 | flagFactory.decorators.push({ 251 | name: name, 252 | handler: handler 253 | }); 254 | } 255 | 256 | if (target === 'command') { 257 | commandFactory.decorators.push({ 258 | name: name, 259 | handler: handler 260 | }); 261 | } 262 | 263 | return this; 264 | }; 265 | 266 | Cli.prototype.set = function (name, value) { 267 | var self = this; 268 | 269 | if (_.isObject(name)) { 270 | _.each(name, function (val, key) { 271 | 272 | self.attributes[key] = val; 273 | }); 274 | } 275 | else { 276 | 277 | this.attributes[name] = value; 278 | } 279 | 280 | return this; 281 | }; 282 | 283 | Cli.prototype.get = function (name) { 284 | 285 | return this.attributes[name]; 286 | }; 287 | 288 | Cli.prototype.register = function (plugins, done) { 289 | 290 | var self = this; 291 | 292 | done = done || function () {}; 293 | 294 | // Force object to array 295 | if (plugins.register) { 296 | plugins = [plugins]; 297 | } 298 | 299 | async.eachSeries(plugins, function (plugin, doneRegistering) { 300 | 301 | plugin.register(self, plugin.options || {}, doneRegistering); 302 | }, done); 303 | 304 | return this; 305 | }; 306 | -------------------------------------------------------------------------------- /test/command.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var command = require('../lib/command'); 4 | 5 | test('command: extends EventEmitter', function (t) { 6 | 7 | var EventEmitter = require('events').EventEmitter; 8 | var cmd = command('test'); 9 | 10 | t.ok(cmd.emit, 'emit'); 11 | t.ok(cmd.addListener, 'addListener'); 12 | t.ok(cmd.on, 'on'); 13 | t.ok(cmd.once, 'once'); 14 | t.ok(cmd.removeListener, 'removeListener'); 15 | t.ok(cmd.removeAllListeners, 'removeAllListeners'); 16 | t.ok(cmd.listeners, 'listeners'); 17 | 18 | t.end(); 19 | }); 20 | 21 | test('command: noop', function (t) { 22 | 23 | t.doesNotThrow(function () { 24 | command.noop(); 25 | }, undefined, 'defined function'); 26 | t.deepEqual(typeof command.noop(), 'object', 'blank command'); 27 | 28 | t.end(); 29 | }); 30 | 31 | test('command: handler', function (t) { 32 | 33 | var handlerCalled = false; 34 | var cmd = command('test') 35 | .handler(function (data, flags, done) { 36 | 37 | handlerCalled = true; 38 | done(); 39 | }); 40 | 41 | cmd.handler()([], {}, function () { 42 | 43 | t.equal(typeof cmd.handler(), 'function', 'gets function'); 44 | t.ok(handlerCalled, 'sets the function'); 45 | t.end(); 46 | }); 47 | 48 | }); 49 | 50 | test('command: task', function (t) { 51 | 52 | var cmd = command('test'); 53 | var task = cmd.task('task'); 54 | var handler = function () {/* handler */}; 55 | 56 | task.handler(handler); 57 | 58 | t.deepEqual(cmd.internals.tasks.all(), [task], 'sets up task in collection'); 59 | t.equal(cmd.task('task').handler().toString(), handler.bind(task).toString(), 'return task if already defined'); 60 | t.equal(task.task, undefined, 'does not allow tasks to create tasks on itself'); 61 | t.end(); 62 | }); 63 | 64 | test('command: flag', function (t) { 65 | 66 | var cmd = command('test'); 67 | var flg = cmd.flag('--test'); 68 | var handler = function () {/* handler */}; 69 | 70 | flg.handler(handler); 71 | 72 | t.deepEqual(cmd.internals.flags.all(), [flg], 'sets up flag in collection'); 73 | t.equal(cmd.flag('--test').handler().toString(), handler.bind(cmd).toString(), 'return flag if already defined'); 74 | t.end(); 75 | }); 76 | 77 | test('command: find a task', function (t) { 78 | 79 | var cmd = command('test'); 80 | 81 | t.notOk(cmd.findTask('task'), 'undefined when no tasks found'); 82 | 83 | var tsk = cmd.task('task', 't'); 84 | 85 | t.deepEqual(cmd.findTask('task'), tsk, 'by name'); 86 | t.deepEqual(cmd.findTask('task', 't'), tsk, 'by multiple names'); 87 | t.end(); 88 | }); 89 | 90 | test('command: find a flag', function (t) { 91 | 92 | var cmd = command('test'); 93 | 94 | t.notOk(cmd.findFlag('-t'), 'undefined when no flags found'); 95 | 96 | var flg = cmd.flag('--test', '-t'); 97 | 98 | t.deepEqual(cmd.findFlag('--test'), flg, 'by name'); 99 | t.deepEqual(cmd.findFlag('--test', '-t'), flg, 'by multiple names'); 100 | t.end(); 101 | }); 102 | 103 | test('command: getters and setters', function (t) { 104 | 105 | var cmd = command('test') 106 | .before(function () {}) 107 | .after(function () {}); 108 | 109 | t.deepEqual(cmd.name(), ['test'], 'name'); 110 | t.equal(cmd.before().length, 1, 'before'); 111 | t.equal(cmd.after().length, 1, 'after'); 112 | t.end(); 113 | }); 114 | 115 | test('command: matches command name', function (t) { 116 | 117 | var cmd = command('test', 't'); 118 | 119 | t.ok(cmd.matchesName('test'), 'matches single name'); 120 | t.ok(cmd.matchesName('t'), 'matches other names'); 121 | t.end(); 122 | }); 123 | 124 | test('command: matches command task name', function (t) { 125 | 126 | var cmd = command('test', 't'); 127 | var tsk = cmd.task('task', 'sk'); 128 | 129 | t.deepEqual(cmd.matchesTask('test', 'task'), tsk, 'finds task by command and command task name'); 130 | t.deepEqual(cmd.matchesTask('t', 'task'), tsk, 'finds task by any command and command task name'); 131 | t.deepEqual(cmd.matchesTask('test', 'sk'), tsk, 'finds task by command and any command task name'); 132 | t.end(); 133 | }); 134 | 135 | test('command: runs befores', function (t) { 136 | 137 | var cmd = command('test'); 138 | var beforeCalled = false; 139 | 140 | cmd.before(function (data, flags, done) { 141 | 142 | beforeCalled = true; 143 | 144 | t.deepEqual(data, ['data'], 'passes in data'); 145 | t.deepEqual(flags, {f: true}, 'passes in flags'); 146 | 147 | done(); 148 | }); 149 | 150 | cmd.runBefores(['data'], {f: true}, function () { 151 | 152 | t.ok(beforeCalled, 'executed in series'); 153 | t.end(); 154 | }); 155 | }); 156 | 157 | test('command: runs afters', function (t) { 158 | 159 | var cmd = command('test'); 160 | var afterCalled = false; 161 | 162 | cmd.after(function (data, flags, done) { 163 | 164 | afterCalled = true; 165 | 166 | t.deepEqual(data, ['data'], 'passes in data'); 167 | t.deepEqual(flags, {f: true}, 'passes in flags'); 168 | 169 | done(); 170 | }); 171 | 172 | cmd.runAfters(['data'], {f: true}, function () { 173 | 174 | t.ok(afterCalled, 'executed in series'); 175 | t.end(); 176 | }); 177 | }); 178 | 179 | test('command: runs flags', function (t) { 180 | 181 | var cmd = command('test'); 182 | var flagCalled1 = false; 183 | var flagCalled2 = false; 184 | var flag1CallCount = 0; 185 | var flag2CallCount = 0; 186 | 187 | cmd.flag('-f') 188 | .handler(function (val, done) { 189 | 190 | flag1CallCount += 1; 191 | flagCalled1 = true; 192 | t.equal(val, 'test value1', 'passes value 1'); 193 | 194 | done(); 195 | }); 196 | 197 | cmd.flag('-t') 198 | .handler(function (val, done) { 199 | 200 | flag2CallCount += 1; 201 | flagCalled2 = true; 202 | t.equal(val, 'test value2', 'passes value 2'); 203 | 204 | done(); 205 | }); 206 | 207 | cmd.runFlags({ 208 | f: 'test value1', 209 | t: 'test value2' 210 | }, function () { 211 | 212 | t.ok(flagCalled1, 'ran flag 1 handler'); 213 | t.ok(flagCalled2, 'ran flag 2 handler'); 214 | 215 | cmd.runFlags({ 216 | f: 'test value1', 217 | t: 'test value2' 218 | }, function () { 219 | 220 | t.equal(flag1CallCount, 1, 'flag 1 called only once'); 221 | t.equal(flag2CallCount, 1, 'flag 1 called only once'); 222 | t.end(); 223 | }); 224 | }); 225 | }); 226 | 227 | test('command: running the command', function (t) { 228 | 229 | var cmd = command('test'); 230 | var callstack = []; 231 | 232 | cmd.before(function (data, flags, done) { 233 | 234 | callstack.push('before'); 235 | done(); 236 | }); 237 | 238 | cmd.flag('-f') 239 | .handler(function (val, done) { 240 | 241 | callstack.push('flag'); 242 | done(); 243 | }); 244 | 245 | cmd.handler(function (data, flags, done) { 246 | 247 | callstack.push('handler'); 248 | done(); 249 | }); 250 | 251 | cmd.after(function (data, flags, done) { 252 | 253 | callstack.push('after'); 254 | done(); 255 | }); 256 | 257 | cmd.run([], { 258 | f: true 259 | }, function () { 260 | 261 | t.deepEqual(callstack, ['before', 'flag', 'handler', 'after'], 'ran command decorators in order'); 262 | t.end(); 263 | }); 264 | }); 265 | 266 | test('command: run command', function (t) { 267 | 268 | var handlerCalled = false; 269 | var cmd = command('test') 270 | // .handler(function (data1, data2, done) { 271 | .handler(function (data, flags, done) { 272 | 273 | handlerCalled = true; 274 | 275 | t.deepEqual(data[0], 'data1', 'passed in data1 to command handler'); 276 | t.deepEqual(data[1], 'data2', 'passed in data2 to command handler'); 277 | t.deepEqual(flags, {t: 'val'}, 'passed in flag map'); 278 | t.ok(typeof done, 'function', 'passed in callback to command handler'); 279 | done(); 280 | }); 281 | 282 | cmd.before(function (data, flags, done) { 283 | 284 | t.deepEqual(data, ['data1', 'data2'], 'passed in data to before'); 285 | t.ok(typeof done, 'function', 'passed in callback to before'); 286 | done(); 287 | }); 288 | 289 | cmd.flag('-t') 290 | .handler(function (val, done) { 291 | 292 | t.equal(val, 'val', 'passed val into flag handler'); 293 | t.ok(typeof done, 'function', 'passed in callback to flag handler'); 294 | done(); 295 | }); 296 | 297 | cmd.after(function (data, flags, done) { 298 | 299 | t.deepEqual(data, ['data1', 'data2'], 'passed in data to after'); 300 | t.ok(typeof done, 'function', 'passed in callback to after'); 301 | done(); 302 | }); 303 | 304 | cmd.run(['data1', 'data2'], {t: 'val'}, function (err) { 305 | 306 | t.ok(handlerCalled, 'handler called'); 307 | t.end(); 308 | }); 309 | }); 310 | 311 | test('command: finishes running command with no handler', function (t) { 312 | 313 | var beforeCalled = false; 314 | var cmd = command('test') 315 | .before(function (data, flags, done) { 316 | 317 | beforeCalled = true; 318 | done(); 319 | }); 320 | 321 | // Async mode 322 | cmd.run([], {}, function () { 323 | 324 | t.ok(beforeCalled, 'called before'); 325 | t.end(); 326 | }); 327 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nash 2 | 3 | Craft command-line masterpieces 4 | 5 | **API** 6 | 7 | * [Cli](#cli) 8 | * [run](#runargv) 9 | * [command](#commandname-names-) 10 | * [default](#defaultcallback) 11 | * [flag](#flagname-names-) 12 | * [beforeAll](#beforeallcallback-callback-) 13 | * [afterAll](#afterallcallback-callback-) 14 | * [set](#setname-value) 15 | * [get](#getname) 16 | * [register](#registerplugin-options) 17 | * [Command](#command) 18 | * [handler](#handlercallback) 19 | * [task](#taskname-name-) 20 | * [flag](#flagname-name-) 21 | * [before](#beforecallback-callback-) 22 | * [after](#aftercallback-callback-) 23 | * [name](#namename-name-) 24 | * [Flag](#flag) 25 | * [handler](#handlercallback-1) 26 | * [override](#override) 27 | * [name](#namename-name--1) 28 | * [Plugins](#plugins) 29 | 30 | ## Install 31 | 32 | ``` 33 | npm install nash --save 34 | ``` 35 | 36 | ## Usage 37 | 38 | ```js 39 | var nash = require('nash'); 40 | var cli = nash(); 41 | 42 | cli.beforeAll(function () { 43 | 44 | // Run this before all commands 45 | }); 46 | 47 | cli.flag('-p') 48 | .handler(function (value, done) { 49 | 50 | // Do something when this flag is triggered 51 | done(); 52 | }); 53 | 54 | cli.command('list') 55 | .handler(function (data, flags, done) { 56 | 57 | // Do something here 58 | done(); 59 | }); 60 | 61 | cli.run(process.argv, function (err) { 62 | 63 | // All done! 64 | }); 65 | ``` 66 | 67 | ## Cli 68 | 69 | ### run(argv[, callback]) 70 | 71 | Run the cli app with the given arguments. Normally you'd pass in `process.argv`. The callback can be used to execute more code after everything has completed. 72 | 73 | ```js 74 | var nash = require('nash'); 75 | var cli = nash(); 76 | 77 | cli.command('some-command') 78 | .handler(function (data, flags, done) { 79 | 80 | console.log('Some Command'); 81 | done(); 82 | }); 83 | 84 | cli.run(process.argv, function () { 85 | 86 | // All done 87 | }); 88 | ``` 89 | 90 | ### command(name[, names, ...]) 91 | 92 | Create a command with the given name(s). Supports a single name, and array of names, or multiple names separated by commas as arguments to the command method. Returns an instance of [`Command`](#command). 93 | 94 | ```js 95 | var nash = require('nash'); 96 | var cli = nash(); 97 | 98 | cli.command('some-command') 99 | .handler(function (data, flags, done) { 100 | 101 | // Do something here 102 | done(); 103 | }); 104 | ``` 105 | 106 | ### default(callback) 107 | 108 | Declare a default command to run if no command is provided. This is useful for small cli apps that have no commands and do only one task. This inherits the same api as a normal command. 109 | 110 | ```js 111 | var nash = require('nash'); 112 | var cli = nash(); 113 | 114 | cli.default() 115 | .handler(function (data, flags, done) { 116 | 117 | // Do stuff here 118 | done(); 119 | }); 120 | 121 | cli.run([]); 122 | ``` 123 | 124 | ### flag(name[, names, ...]) 125 | 126 | Create a flag with the given name(s). Supports a single name, and array of names, or multiple names separated by commas as arguments to the command method. Returns an instance of [`Flag`](#flag). 127 | 128 | ```js 129 | var nash = require('nash'); 130 | var cli = nash(); 131 | 132 | cli.flag('-f') 133 | .handler(function (value, done) { 134 | 135 | // Do something with this flag value 136 | done(); 137 | }); 138 | ``` 139 | 140 | ### beforeAll(callback[, callback, ...]) 141 | 142 | Add a function or functions to be called before any commands or flags are run. The callback is passed to arguments": 143 | 144 | * `data` - the values passed in from the the terminal. 145 | * `flags` - a key/value map of flags and their corresponding values 146 | 147 | ```js 148 | var nash = require('nash'); 149 | var cli = nash(); 150 | 151 | cli.beforeAll(function (data, flags, done) { 152 | 153 | // data === ['value'] 154 | // flags === {f: 'flag-value'} 155 | done(); 156 | }); 157 | 158 | cli.command('some-command') 159 | .handler(function (data, flags, doen) { 160 | 161 | done(); 162 | }) 163 | .flag('-f') 164 | .handler(function (val, done) { 165 | 166 | done(); 167 | }); 168 | 169 | // This is usually sent in via process.argv 170 | cli.run(['', '', 'some-command', 'value', '-f', 'flag-value']); 171 | ``` 172 | 173 | ### afterAll(callback[, callback, ...]) 174 | 175 | Does the same thing as `beforeAll()`, except runs the callbacks after all the commands and flags are run. 176 | 177 | ### set(name, value) 178 | 179 | Cli-level/app-level settings. Set the given `name` to the given `value 180 | 181 | * `name` - name of key. Name, can also be a key/value object of multiple values 182 | * `value` - value of name 183 | 184 | ```js 185 | 186 | var nash = require('nash'); 187 | var cli = nash(); 188 | 189 | cli.set('key', 'value'); 190 | cli.set({ 191 | key1: 'value1', 192 | key2: 'value2' 193 | }); 194 | 195 | ``` 196 | 197 | ### get(name) 198 | 199 | Cli-level/app-level getter for settings. 200 | 201 | * `name` - name of key to get 202 | 203 | ### register(plugin(s), callback) 204 | 205 | Register a plugin with your command-line application. This provides extensibility and modularity. See [Plugins](#plugins) for more information. 206 | 207 | ## Command 208 | 209 | Running `cli.command('name')` creates an instance of the Command class with following methods available: 210 | 211 | ### handler(callback) 212 | 213 | The callback gets executed when the command is called. Any values passed in to the run method on the cli get passed into the callback. You must call the `done()` callback to pass execution back to the cli. 214 | 215 | ```js 216 | var nash = require('nash'); 217 | var cli = nash(); 218 | 219 | 220 | // Sync mode 221 | cli.command('some-command') 222 | .handler(function (data, flags, done) { 223 | 224 | // value === 'value' 225 | done(); 226 | }); 227 | 228 | // Usually passed process.argv 229 | cli.run(['', '', 'some-command', 'value']); 230 | 231 | ``` 232 | 233 | ### task(name[, name, ...]) 234 | 235 | Creates a sub task of the command. This is similar to the command tasks that the Heroku toolbelt has. This creates a command that looks like `command:task` when running. Calling `task()` returns and instance of [`Command`](#command). 236 | 237 | ```js 238 | var nash = require('nash'); 239 | var cli = nash(); 240 | 241 | cli.command('command') 242 | .task('task', 'tsk') 243 | .handler(function (data, flags, done) { 244 | 245 | // Do something here 246 | done(); 247 | }); 248 | 249 | cli.run(['', '', 'command:task']); 250 | ``` 251 | 252 | ### flag(name[, name, ...]) 253 | 254 | Creates a command-specific flag that only runs for that command. Returns an instance of [`Flag`](#flag). 255 | 256 | ```js 257 | var nash = require('nash'); 258 | var cli = nash(); 259 | 260 | cli.command('command') 261 | .flag('-f', '--flag') 262 | .handler(function (value, done) { 263 | 264 | // Do something here 265 | done(); 266 | }); 267 | 268 | cli.run(['', '', 'command', '-f']); 269 | ``` 270 | 271 | ### before(callback[, callback, ...]) 272 | 273 | Add a function or functions to be called before the command and the command's flags are run. This has the same callback signature as Cli's [`beforeAll()`](#beforeallcallback-callback-) 274 | 275 | ### after(callback[, callback, ...]) 276 | 277 | This is the same as the `before()` method, except this is called after the command and all it's flags run. 278 | 279 | ### name(name[, name, ...]) 280 | 281 | Add more names to the command. Helpful if you want aliases or mispellings to trigger the command. This can also be used to get all the aliases for a given command. 282 | 283 | 284 | ```js 285 | var nash = require('nash'); 286 | var cli = nash(); 287 | 288 | cli.command('command') 289 | .name('cmd', 'commnd') 290 | ``` 291 | 292 | ## Flag 293 | 294 | Running `cli.flag('name')` or `cli.command('name').flag('name')` creates an instance of the Flag class. If created under a command, the flag only runs with that command. The Flag class has the following methods available: 295 | 296 | ### handler(callback) 297 | 298 | The callback gets executed when the flag is called. Any values passed in to the run method on the cli get passed into the callback. The `done()` callback must be called to pass execution back to the cli. 299 | 300 | ```js 301 | var nash = require('nash'); 302 | var cli = nash(); 303 | 304 | 305 | // Sync mode 306 | cli.command('some-command') 307 | .flag('-f') 308 | .handler(function (value, done) { 309 | 310 | // Do something here 311 | done(); 312 | }); 313 | 314 | 315 | // Usually passed process.argv 316 | cli.run(['', '', 'some-command', '-f']); 317 | 318 | ``` 319 | 320 | ### override() 321 | 322 | If no value or a value of `true` is passed in, this command specific flag will override the cli/global flag. If set to `false` or not called, the flag will run in series after the cli/global flag. 323 | 324 | ```js 325 | var nash = require('nash'); 326 | var cli = nash(); 327 | 328 | 329 | cli.flag('-f') 330 | .handler(function (value, done) { 331 | 332 | done(): 333 | }); 334 | 335 | cli.command('some-command') 336 | .flag('-f') 337 | .override() 338 | .handler(function (value, done) { 339 | 340 | // Only this flag runs for -f 341 | done(); 342 | }); 343 | 344 | cli.run(['', '', 'some-command', '-f']); 345 | ``` 346 | 347 | ### name(name[, name, ...]) 348 | 349 | Add more names to the flag. Helpful if you want aliases or mispellings to trigger the flag. 350 | 351 | 352 | ```js 353 | var nash = require('nash'); 354 | var cli = nash(); 355 | 356 | cli.command('command') 357 | .flag('-f') 358 | .name('--flag', '--flaggling'); 359 | ``` 360 | 361 | ## Plugins 362 | 363 | Nash lets you register plugins via the [`register`](#registerplugin-options) method on the cli object. This makes it easier to break up your app as it grows. 364 | 365 | YOu can register an array of plugins or a single plugin. Each object used to register the plugin must contain a `register` key with the value being the plugin function. See below for examples. 366 | 367 | Optionally, you can provide an options object to pass to each plugin. 368 | 369 | **Example of registering a plugin:** 370 | 371 | ```js 372 | var nash = require('nash'); 373 | var myPlugin = require('my-plugin'); 374 | var cli = nash(); 375 | 376 | cli.register([{ 377 | register: myPlugin, 378 | options: { 379 | key: 'value' 380 | } 381 | }], function (err) { 382 | 383 | // Done loading plugins 384 | }); 385 | ``` 386 | 387 | **Example plugin:** 388 | 389 | ```js 390 | module.exports = function (cli, options, done) { 391 | 392 | cli.command('something') 393 | .handler(function (data, flags, done) { 394 | 395 | // Do something here 396 | done(); 397 | }); 398 | 399 | done(); 400 | }; 401 | ``` 402 | 403 | Each plugin must export a function. This method is called and given the arguments: 404 | 405 | 1. Cli object. 406 | 2. Options you pass when registering the plugin. 407 | 3. Callback. This must be called in order to complete the registration process 408 | 409 | 410 | ## Run Tests 411 | 412 | ``` 413 | npm install 414 | npm test 415 | ``` 416 | 417 | ## License 418 | 419 | [MIT](https://github.com/scottcorgan/nash/blob/master/LICENSE) 420 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | var nash = require('../lib/index'); 4 | var command = require('../lib/command'); 5 | var commands = require('../lib/commands'); 6 | var flags = require('../lib/flags'); 7 | var flag = require('../lib/flag'); 8 | var wrappers = require('../lib/wrappers'); 9 | 10 | test('cli: defaults', function (t) { 11 | 12 | var cli = nash(); 13 | 14 | t.deepEqual(cli.internals.commands, commands(), 'blank command collection'); 15 | t.deepEqual(cli.internals.flags, flags(), 'blank flag collection'); 16 | t.deepEqual(cli.internals.beforeAlls, wrappers(), 'blank beforeAlls collection'); 17 | t.deepEqual(cli.internals.afterAlls, wrappers(), 'blank afterAlls collection'); 18 | t.end(); 19 | }); 20 | 21 | test('cli: instance options', function (t) { 22 | 23 | var options = { 24 | key1: 'value1', 25 | key2: 'value2' 26 | }; 27 | var cli = nash(options); 28 | 29 | t.deepEqual(cli.options, options, 'adds options to instance'); 30 | t.end(); 31 | }); 32 | 33 | test('cli: extends EventEmitter', function (t) { 34 | 35 | var EventEmitter = require('events').EventEmitter; 36 | var cli = nash(); 37 | 38 | t.ok(cli.emit, 'emit'); 39 | t.ok(cli.addListener, 'addListener'); 40 | t.ok(cli.on, 'on'); 41 | t.ok(cli.once, 'once'); 42 | t.ok(cli.removeListener, 'removeListener'); 43 | t.ok(cli.removeAllListeners, 'removeAllListeners'); 44 | t.ok(cli.listeners, 'listeners'); 45 | 46 | t.end(); 47 | }); 48 | 49 | test('cli: command', function (t) { 50 | 51 | var cli = nash(); 52 | var cmd = cli.command('test', 't'); 53 | var handler = function () {/* handler */}; 54 | 55 | cmd.handler(handler); 56 | 57 | t.deepEqual(cli.internals.commands.all(), [cmd], 'adds command to collection'); 58 | t.deepEqual(cli.command('test', 't').name(), command('test', 't').name(), 'creates instance of command'); 59 | t.equal(cli.command('test').handler().toString(), handler.bind(cmd).toString(), 'return command if already defined'); 60 | t.end(); 61 | }); 62 | 63 | test('cli: flags', function (t) { 64 | 65 | var handler = function () {/* handler */}; 66 | var cli = nash(); 67 | var flg = cli.flag('--test', '-t'); 68 | 69 | flg.handler(handler); 70 | 71 | t.deepEqual(cli.internals.flags.all(), [flg], 'adds flag to collection'); 72 | t.deepEqual(cli.flag('--test', '-t').name(), flag('--test', '-t').name(), 'creates instance of flag'); 73 | t.equal(cli.flag('-t').handler().toString(), handler.bind(flg).toString(), 'return flag if already defined'); 74 | t.end(); 75 | }); 76 | 77 | test.skip('cli: flag default value', function (t) { 78 | 79 | var cli = nash(); 80 | cli.flag('--default', '-d').default('default value'); 81 | cli.flag('--another', '-a').default('another default value'); 82 | 83 | 84 | cli.default() 85 | .handler(function (done) { 86 | 87 | t.equal(cli.argv.d, 'my value', 'default value'); 88 | // t.equal(cli.argv.default, 'my value', 'default value'); 89 | // t.equal(cli.argv.a, 'another default value', 'default value'); 90 | // t.equal(cli.argv.another, 'another default value', 'default value'); 91 | done(); 92 | }); 93 | 94 | 95 | cli.run(['', '', 'some', '-d', 'my value'], function () { 96 | 97 | t.end(); 98 | }); 99 | }); 100 | 101 | test('cli: runs flags', function (t) { 102 | 103 | var cli = nash(); 104 | var flagCalled1 = false; 105 | var flagCalled2 = false; 106 | var cliOverrideFlagCalled = false; 107 | var flagOverrideCalled = false; 108 | 109 | // Set up override flag 110 | var overrideFlag = flag('-o') 111 | .override() 112 | .handler(function (val, done) { 113 | 114 | flagOverrideCalled = true; 115 | done(); 116 | }); 117 | 118 | cli.flag('-f') 119 | .handler(function (val, done) { 120 | 121 | flagCalled1 = true; 122 | t.equal(val, 'test value1', 'passes value 1'); 123 | done(); 124 | }); 125 | cli.flag('-t') 126 | .handler(function (val, done) { 127 | 128 | flagCalled2 = true; 129 | t.equal(val, 'test value2', 'passes value 2'); 130 | done(); 131 | }); 132 | cli.flag('-o') 133 | .handler(function (val, done) { 134 | 135 | cliOverrideFlagCalled = true; 136 | done(); 137 | }); 138 | 139 | cli.runFlags({ 140 | f: 'test value1', 141 | t: 'test value2', 142 | o: true 143 | }, function (err) { 144 | 145 | t.ok(flagCalled1, 'ran flag 1 handler'); 146 | t.ok(flagCalled2, 'ran flag 2 handler'); 147 | t.notOk(cliOverrideFlagCalled, 'did not call cli flag'); 148 | t.ok(flagOverrideCalled, 'called flag override'); 149 | t.end(); 150 | }, 151 | // Flag override values 152 | [overrideFlag] 153 | ); 154 | }); 155 | 156 | test('cli: runs a single flag', function (t) { 157 | 158 | t.plan(2); 159 | 160 | var cli = nash(); 161 | var ranFlag = false; 162 | 163 | cli.flag('-t') 164 | .handler(function (data, done) { 165 | 166 | t.equal(data, 'data', 'passed data into flag'); 167 | 168 | ranFlag = true; 169 | done(); 170 | }); 171 | 172 | cli.runFlag('t', 'data', function () { 173 | 174 | t.ok(ranFlag, 'ran flag'); 175 | }); 176 | }); 177 | 178 | test('cli: run method is chainable', function (t) { 179 | 180 | var cli = nash(); 181 | 182 | t.deepEqual(cli.run(), cli, 'returns object'); 183 | t.end(); 184 | }); 185 | 186 | test('cli: runs command', function (t) { 187 | 188 | var cli = nash(); 189 | var handlerCalled = false; 190 | var commandFlagCalled = false; 191 | var callstack = []; 192 | 193 | cli.beforeAll(function (data, flags, done) { 194 | 195 | callstack.push('beforeAll'); 196 | done(); 197 | }); 198 | cli.afterAll(function (data, flags, done) { 199 | 200 | callstack.push('afterAll'); 201 | done(); 202 | }); 203 | cli.flag('-t') 204 | .handler(function (val, done) { 205 | 206 | callstack.push('flag'); 207 | done(); 208 | }); 209 | cli.command('test') 210 | .handler(function (data, flags, done) { 211 | 212 | callstack.push('command'); 213 | handlerCalled = true; 214 | done(); 215 | }) 216 | .flag('-t') 217 | .handler(function (val, done) { 218 | 219 | commandFlagCalled = true; 220 | done(); 221 | }); 222 | cli.run(['', '', 'test', '-t'], function () { 223 | 224 | t.ok(handlerCalled, 'runs the command'); 225 | t.deepEqual(callstack, ['beforeAll', 'flag', 'command', 'afterAll'], 'execution order'); 226 | t.ok(commandFlagCalled, 'calls cli and command flags if not overridden'); 227 | 228 | callstack = []; 229 | handlerCalled = false; 230 | 231 | cli.command('test') 232 | .handler(function (data, flags, done) { 233 | 234 | handlerCalled = true; 235 | t.equal(data[0], 'data', 'passes data to handler'); 236 | done(); 237 | }); 238 | 239 | cli.run(['', '', 'test', 'data'], function () { 240 | 241 | t.ok(handlerCalled, 'runs the command with data'); 242 | t.end(); 243 | }); 244 | }); 245 | }); 246 | 247 | test('cli: command level flags can override cli level flags', function (t) { 248 | 249 | var cli = nash(); 250 | var cliFlagCalled = false; 251 | var commandFlagCalled = false; 252 | var commandFlagCallCount = 0; 253 | 254 | cli.flag('-t') 255 | .handler(function (val, done) { 256 | 257 | cliFlagCalled = true; 258 | done(); 259 | }); 260 | 261 | cli.command('test') 262 | .flag('-t') 263 | .override() 264 | .handler(function (val, done) { 265 | 266 | commandFlagCallCount += 1; 267 | commandFlagCalled = true; 268 | done(); 269 | }); 270 | 271 | cli.run(['', '', 'test', '-t'], function () { 272 | 273 | t.ok(commandFlagCalled, 'command flag'); 274 | t.notOk(cliFlagCalled, 'cli flag'); 275 | t.equal(commandFlagCallCount, 1, 'flag only called once'); 276 | t.end(); 277 | }); 278 | }); 279 | 280 | test('cli: task level flags can override cli level flags', function (t) { 281 | 282 | var cli = nash(); 283 | var cliFlagCalled = false; 284 | var taskFlagCalled = false; 285 | var taskFlagCallCount = 0; 286 | 287 | cli.flag('-t') 288 | .handler(function (val, done) { 289 | 290 | cliFlagCalled = true; 291 | done(); 292 | }); 293 | 294 | cli.command('test') 295 | .task('task') 296 | .flag('-t') 297 | .override() 298 | .handler(function (val, done) { 299 | 300 | taskFlagCallCount += 1; 301 | taskFlagCalled = true; 302 | done(); 303 | }); 304 | 305 | cli.run(['', '', 'test:task', '-t'], function () { 306 | 307 | t.ok(taskFlagCalled, 'command flag'); 308 | t.notOk(cliFlagCalled, 'cli flag'); 309 | t.equal(taskFlagCallCount, 1, 'flag only called once'); 310 | t.end(); 311 | }); 312 | }); 313 | 314 | test('cli: sets argv on cli level', function (t) { 315 | 316 | var cli = nash(); 317 | cli.run(['', '', 'command', '-f', 'value'], function () { 318 | 319 | t.deepEqual(cli.argv, { _: [ 'command' ], f: 'value' }, 'argv set on proto'); 320 | t.end(); 321 | }); 322 | }); 323 | 324 | test('cli: runs command task', function (t) { 325 | 326 | var cli = nash(); 327 | var taskRan = false; 328 | cli.command('test') 329 | .task('task') 330 | .handler(function (data, flags, done) { 331 | 332 | t.equal(data[0], 'data', 'passes data to task handler'); 333 | taskRan = true; 334 | done(); 335 | }); 336 | 337 | cli.run(['', '', 'test:task', 'data'], function () { 338 | 339 | t.ok(taskRan, 'ran the task'); 340 | t.end(); 341 | }); 342 | }); 343 | 344 | test('cli: runs beforeAlls', function (t) { 345 | 346 | var cli = nash(); 347 | var beforeAllRan1 = false; 348 | var beforeAllRan2 = false; 349 | var beforeAllRan3 = false; 350 | 351 | var chained = cli 352 | .beforeAll(function (data, flags, done) { 353 | 354 | beforeAllRan1 = true; 355 | 356 | t.deepEqual(this, cli, 'bound to cli object'); 357 | t.deepEqual(data, ['data'], 'passed in data'); 358 | t.deepEqual(flags, {t: 'flagged'}, 'passed in flags'); 359 | done(); 360 | }) 361 | .beforeAll( 362 | function (data, flags, done) { 363 | 364 | beforeAllRan2 = true; 365 | 366 | t.deepEqual(data, ['data'], 'passed in data'); 367 | t.deepEqual(flags, {t: 'flagged'}, 'passed in flags'); 368 | done(); 369 | }, 370 | function (data, flags, done) { 371 | 372 | beforeAllRan3 = true; 373 | 374 | t.deepEqual(data, ['data'], 'passed in data'); 375 | t.deepEqual(flags, {t: 'flagged'}, 'passed in flags'); 376 | done(); 377 | } 378 | ); 379 | 380 | cli.runBeforeAlls(['data'], {t: 'flagged'}, function () { 381 | 382 | t.ok(beforeAllRan1, 'all methods ran'); 383 | t.ok(beforeAllRan2, 'all methods ran'); 384 | t.ok(beforeAllRan3, 'all methods ran'); 385 | t.deepEqual(chained, cli, 'chainable'); 386 | t.end(); 387 | }); 388 | }); 389 | 390 | test('cli: runs afterAlls', function (t) { 391 | 392 | var cli = nash(); 393 | var afterAllRan1 = false; 394 | var afterAllRan2 = false; 395 | var afterAllRan3 = false; 396 | 397 | var chained = cli 398 | .afterAll(function (data, flags, done) { 399 | 400 | afterAllRan1 = true; 401 | 402 | t.deepEqual(this, cli, 'bound to cli object'); 403 | t.deepEqual(data, ['data'], 'passed in data'); 404 | t.deepEqual(flags, {t: 'flagged'}, 'passed in flags'); 405 | done(); 406 | }) 407 | .afterAll( 408 | function (data, flags, done) { 409 | 410 | afterAllRan2 = true; 411 | 412 | t.deepEqual(data, ['data'], 'passed in data'); 413 | t.deepEqual(flags, {t: 'flagged'}, 'passed in flags'); 414 | done(); 415 | }, 416 | function (data, flags, done) { 417 | 418 | afterAllRan3 = true; 419 | 420 | t.deepEqual(data, ['data'], 'passed in data'); 421 | t.deepEqual(flags, {t: 'flagged'}, 'passed in flags'); 422 | done(); 423 | } 424 | ); 425 | 426 | cli.runAfterAlls(['data'], {t: 'flagged'}, function () { 427 | 428 | t.ok(afterAllRan1, 'all methods ran'); 429 | t.ok(afterAllRan2, 'all methods ran'); 430 | t.ok(afterAllRan3, 'all methods ran'); 431 | t.deepEqual(chained, cli, 'chainable'); 432 | t.end(); 433 | }); 434 | }); 435 | 436 | test('cli: finds a command', function (t) { 437 | 438 | var cli = nash(); 439 | var cmd = cli.command('test'); 440 | 441 | t.deepEqual(cli.findCommand('test'), cmd, 'by command name'); 442 | t.end(); 443 | }); 444 | 445 | test('cli: finds a command task', function (t) { 446 | 447 | var cli = nash(); 448 | var task = cli.command('test').task('task'); 449 | 450 | t.deepEqual(cli.findCommandTask('test', 'task'), task, 'by command name and command task name'); 451 | t.notOk(cli.findCommandTask('test', 'notTask'), 'no task found'); 452 | t.end(); 453 | }); 454 | 455 | test('cli: default command', function (t) { 456 | 457 | var cli = nash(); 458 | var commandCalledCount = 0; 459 | 460 | cli.default() 461 | .handler(function (data, flags, done) { 462 | 463 | commandCalledCount += 1; 464 | done(); 465 | }); 466 | cli.run([], function () { 467 | 468 | t.equal(commandCalledCount, 1, 'command ran first time'); 469 | 470 | var defaultCommand = cli.default(); 471 | cli.run([], function () { 472 | 473 | t.equal(commandCalledCount, 2, 'ran same default command'); 474 | 475 | t.end(); 476 | }); 477 | }); 478 | }); 479 | 480 | test('cli: one argument gets passed to default command', function (t) { 481 | 482 | t.plan(1); 483 | 484 | var cli = nash(); 485 | 486 | cli.default() 487 | .handler(function (data, flags, done) { 488 | 489 | t.equal(data[0], 'arg1', 'argument 1 passed'); 490 | done(); 491 | }); 492 | cli.run(['', '', 'arg1']); 493 | }); 494 | 495 | test('cli: two arguments get passed to default command', function (t) { 496 | 497 | t.plan(2); 498 | 499 | var cli = nash(); 500 | 501 | cli.default() 502 | .handler(function (data, flags, done) { 503 | 504 | t.equal(data[0], 'arg1', 'argument 1 passed'); 505 | t.equal(data[1], 'arg2', 'argument 2 passed'); 506 | done(); 507 | }); 508 | cli.run(['', '', 'arg1', 'arg2']); 509 | }); 510 | 511 | test('cli: global flags work with default command', function (t) { 512 | 513 | t.plan(2); 514 | 515 | var cli = nash(); 516 | var commandCalled = false; 517 | var flagCalled = false; 518 | 519 | cli.flag('--help', '-h') 520 | .handler(function (val, done) { 521 | 522 | flagCalled = true; 523 | done(); 524 | }); 525 | 526 | cli.default() 527 | .handler(function (data, flags, done) { 528 | 529 | commandCalled = true; 530 | done(); 531 | }); 532 | 533 | cli.run(['', '', '-h'], function () { 534 | 535 | t.ok(commandCalled, 'command ran'); 536 | t.ok(flagCalled, 'flag ran'); 537 | }); 538 | }); 539 | 540 | test('cli: decorates commands', function (t) { 541 | 542 | t.plan(3); 543 | 544 | var cli = nash(); 545 | 546 | var chained = cli.decorate('command', 'test', function (val) { 547 | 548 | t.equal(val, 'value', 'passed value in to decorator'); 549 | 550 | return this; 551 | }); 552 | 553 | cli.decorate('command', 'another', function () { 554 | 555 | this._something = 'another'; 556 | }); 557 | 558 | t.deepEqual(chained, cli, 'chainable'); 559 | 560 | var cmd = cli.command('testing'); 561 | cmd.test('value'); 562 | cmd.another(); 563 | 564 | t.equal(cmd._something, 'another', 'can set data on the command'); 565 | }); 566 | 567 | test('cli: decorates flags', function (t) { 568 | 569 | t.plan(1); 570 | 571 | var cli = nash(); 572 | 573 | cli.decorate('flag', 'test', function (val) { 574 | 575 | t.equal(val, 'data', 'passed data into decorator'); 576 | }); 577 | 578 | var flg = flag('-t'); 579 | 580 | flg.test('data'); 581 | }); 582 | 583 | test('cli: register an array of plugins', function (t) { 584 | 585 | t.plan(3); 586 | 587 | var cli = nash(); 588 | var commandCalled = false; 589 | var ranBeforeAll = false; 590 | 591 | var plugin1 = function (cli, options, done) { 592 | 593 | cli.beforeAll(function (data, flags, done) { 594 | 595 | ranBeforeAll = true; 596 | done(); 597 | }); 598 | 599 | cli.command('test') 600 | .handler(function (data, flags, done) { 601 | 602 | commandCalled = true; 603 | done(); 604 | }); 605 | 606 | t.deepEqual(options, {key: 'value'}, 'passed options into plugin'); 607 | 608 | done(); 609 | }; 610 | 611 | cli.register([ 612 | { 613 | register: plugin1, 614 | options: { 615 | key: 'value' 616 | } 617 | } 618 | ], function () { 619 | 620 | cli.run(['', '', 'test'], function () { 621 | 622 | t.ok(commandCalled, 'ran command from plugin'); 623 | t.ok(ranBeforeAll, 'ran before all from plugin'); 624 | }); 625 | }); 626 | }); 627 | 628 | test('cli: register a single plugin', function (t) { 629 | 630 | t.plan(3); 631 | 632 | var cli = nash(); 633 | var commandCalled = false; 634 | var ranBeforeAll = false; 635 | 636 | var plugin1 = function (cli, options, done) { 637 | 638 | cli.beforeAll(function (data, flags, done) { 639 | 640 | ranBeforeAll = true; 641 | done(); 642 | }); 643 | 644 | cli.command('test') 645 | .handler(function (data, flags, done) { 646 | 647 | commandCalled = true; 648 | done(); 649 | }); 650 | 651 | t.deepEqual(options, {key: 'value'}, 'passed options into plugin'); 652 | 653 | done(); 654 | }; 655 | 656 | cli.register({ 657 | register: plugin1, 658 | options: { 659 | key: 'value' 660 | } 661 | }, function () { 662 | 663 | cli.run(['', '', 'test'], function () { 664 | 665 | t.ok(commandCalled, 'ran command from plugin'); 666 | t.ok(ranBeforeAll, 'ran before all from plugin'); 667 | }); 668 | }); 669 | }); 670 | 671 | test('cli: registers plugin with no options', function (t) { 672 | 673 | t.plan(2); 674 | 675 | var cli = nash(); 676 | var commandCalled = false; 677 | var ranBeforeAll = false; 678 | 679 | var plugin1 = function (cli, options, done) { 680 | 681 | cli.beforeAll(function (data, flags, done) { 682 | 683 | ranBeforeAll = true; 684 | done(); 685 | }); 686 | 687 | cli.command('test') 688 | .handler(function (data, flags, done) { 689 | 690 | commandCalled = true; 691 | done(); 692 | }); 693 | 694 | done(); 695 | }; 696 | 697 | cli.register({register: plugin1}, function () { 698 | 699 | cli.run(['', '', 'test'], function () { 700 | 701 | t.ok(commandCalled, 'ran command from plugin'); 702 | t.ok(ranBeforeAll, 'ran before all from plugin'); 703 | }); 704 | }); 705 | }); 706 | 707 | test('cli: registers multiple plugins as an array', function (t) { 708 | 709 | t.plan(3); 710 | 711 | var cli = nash(); 712 | var commandCalled = false; 713 | var command2Called = false; 714 | var ranBeforeAll = false; 715 | 716 | var plugin1 = function (cli, options, done) { 717 | 718 | cli.beforeAll(function (data, flags, done) { 719 | 720 | ranBeforeAll = true; 721 | done(); 722 | }); 723 | 724 | cli.command('test') 725 | .handler(function (data, flags, done) { 726 | 727 | commandCalled = true; 728 | done(); 729 | }); 730 | 731 | done(); 732 | }; 733 | 734 | var plugin2 = function (cli, options, done) { 735 | 736 | cli.command('test2') 737 | .handler(function (data, flags, done) { 738 | 739 | command2Called = true; 740 | done(); 741 | }); 742 | 743 | done(); 744 | }; 745 | 746 | cli.register([ 747 | { 748 | register: plugin1, 749 | options: {key: 'value'} 750 | }, 751 | { 752 | register: plugin2, 753 | options: {key: 'value'} 754 | } 755 | ], function () { 756 | 757 | cli.run(['', '', 'test'], function () { 758 | 759 | cli.run(['', '', 'test2'], function () { 760 | 761 | t.ok(commandCalled, 'ran command from plugin'); 762 | t.ok(ranBeforeAll, 'ran before all from plugin'); 763 | t.ok(command2Called, 'ran command 2 from plugin'); 764 | }); 765 | }); 766 | }); 767 | }); 768 | 769 | test('cli: getters/setters', function (t) { 770 | 771 | var cli = nash(); 772 | 773 | t.ok(typeof cli.set === 'function', 'setter exists'); 774 | 775 | cli.set('key', 'value'); 776 | 777 | t.ok(typeof cli.get === 'function', 'getter exists'); 778 | t.equal(cli.get('key'), 'value', 'getter'); 779 | 780 | cli.set({ 781 | key1: 'value1', 782 | key2: 'value2' 783 | }); 784 | 785 | t.equal(cli.get('key1'), 'value1', 'set first value'); 786 | t.equal(cli.get('key2'), 'value2', 'set second value'); 787 | 788 | t.end(); 789 | }); 790 | 791 | test('cli: process data', function (t) { 792 | 793 | var cli = nash(); 794 | 795 | cli.default() 796 | .handler(function (data, flags, done) { 797 | 798 | t.equal(cli.process.command, 'command', 'command'); 799 | t.equal(cli.process.task, 'task', 'task'); 800 | t.deepEqual(cli.process.data, ['data'], 'data'); 801 | t.deepEqual(cli.process.flags, {flag: 'flag', f: 'flag'}, 'flags'); 802 | 803 | cli.process.newValue = 'new value'; 804 | 805 | t.notOk(cli.process.newValue, 'immutable'); 806 | 807 | done(); 808 | }); 809 | 810 | cli.flag('-f', '--flag'); 811 | 812 | cli.run(['', '', 'command:task', 'data', '--flag', 'flag'], function () { 813 | 814 | t.end(); 815 | }); 816 | }); 817 | --------------------------------------------------------------------------------