coa 0.4.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const Q = require('q');
4
+
5
+ /**
6
+ * COA Object
7
+ *
8
+ * Base class for all COA-related objects
9
+ *
10
+ * --------|-----|-----|-----
11
+ * | Cmd | Opt | Arg
12
+ * --------|-----|-----|-----
13
+ * name | ✓ | ✓ | ✓
14
+ * title | ✓ | ✓ | ✓
15
+ * comp | ✓ | ✓ | ✓
16
+ * reject | ✓ | ✓ | ✓
17
+ * end | ✓ | ✓ | ✓
18
+ * apply | ✓ | ✓ | ✓
19
+ *
20
+ * @class CoaObject
21
+ */
22
+ module.exports = class CoaObject {
23
+ constructor(cmd) {
24
+ this._cmd = cmd;
25
+ this._name = null;
26
+ this._title = null;
27
+ this._comp = null;
28
+ }
29
+
30
+ /**
31
+ * Set a canonical identifier to be used anywhere in the API.
32
+ *
33
+ * @param {String} name - command, option or argument name
34
+ * @returns {COA.CoaObject} - this instance (for chainability)
35
+ */
36
+ name(name) {
37
+ this._name = name;
38
+ return this;
39
+ }
40
+
41
+ /**
42
+ * Set a long description to be used anywhere in text messages.
43
+ * @param {String} title - human readable entity title
44
+ * @returns {COA.CoaObject} - this instance (for chainability)
45
+ */
46
+ title(title) {
47
+ this._title = title;
48
+ return this;
49
+ }
50
+
51
+ /**
52
+ * Set custom additional completion for current object.
53
+ *
54
+ * @param {Function} comp - completion generation function,
55
+ * invoked in the context of object instance.
56
+ * Accepts parameters:
57
+ * - {Object} opts - completion options
58
+ * It can return promise or any other value threated as a result.
59
+ * @returns {COA.CoaObject} - this instance (for chainability)
60
+ */
61
+ comp(comp) {
62
+ this._comp = comp;
63
+ return this;
64
+ }
65
+
66
+ /**
67
+ * Apply function with arguments in a context of object instance.
68
+ *
69
+ * @param {Function} fn - body
70
+ * @param {Array.<*>} args... - arguments
71
+ * @returns {COA.CoaObject} - this instance (for chainability)
72
+ */
73
+ apply(fn) {
74
+ arguments.length > 1?
75
+ fn.apply(this, [].slice.call(arguments, 1))
76
+ : fn.call(this);
77
+
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Return reject of actions results promise with error code.
83
+ * Use in .act() for return with error.
84
+ * @param {Object} reason - reject reason
85
+ * You can customize toString() method and exitCode property
86
+ * of reason object.
87
+ * @returns {Q.promise} rejected promise
88
+ */
89
+ reject(reason) {
90
+ return Q.reject(reason);
91
+ }
92
+
93
+ /**
94
+ * Finish chain for current subcommand and return parent command instance.
95
+ * @returns {COA.Cmd} parent command
96
+ */
97
+ end() {
98
+ return this._cmd;
99
+ }
100
+ };
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ const CoaObject = require('./coaobject');
6
+
7
+ /**
8
+ * COA Parameter
9
+ *
10
+ * Base class for options and arguments
11
+ *
12
+ * --------|-----|-----|-----
13
+ * | Cmd | Opt | Arg
14
+ * --------|-----|-----|-----
15
+ * arr | | ✓ | ✓
16
+ * req | | ✓ | ✓
17
+ * val | | ✓ | ✓
18
+ * def | | ✓ | ✓
19
+ * input | | ✓ | ✓
20
+ * output | | ✓ | ✓
21
+ *
22
+ * @class CoaParam
23
+ * @extends CoaObject
24
+ */
25
+ module.exports = class CoaParam extends CoaObject {
26
+ constructor(cmd) {
27
+ super(cmd);
28
+
29
+ this._arr = false;
30
+ this._req = false;
31
+ this._val = undefined;
32
+ this._def = undefined;
33
+ }
34
+
35
+ /**
36
+ * Makes a param accepts multiple values.
37
+ * Otherwise, the value will be used by the latter passed.
38
+ *
39
+ * @returns {COA.CoaParam} - this instance (for chainability)
40
+ */
41
+ arr() {
42
+ this._arr = true;
43
+ return this;
44
+ }
45
+
46
+ /**
47
+ * Makes a param required.
48
+ *
49
+ * @returns {COA.CoaParam} - this instance (for chainability)
50
+ */
51
+ req() {
52
+ this._req = true;
53
+ return this;
54
+ }
55
+
56
+ /**
57
+ * Set a validation (or value) function for param.
58
+ * Value from command line passes through before becoming available from API.
59
+ * Using for validation and convertion simple types to any values.
60
+ *
61
+ * @param {Function} val - validating function,
62
+ * invoked in the context of option instance
63
+ * and has one parameter with value from command line.
64
+ * @returns {COA.CoaParam} - this instance (for chainability)
65
+ */
66
+ val(val) {
67
+ this._val = val;
68
+ return this;
69
+ }
70
+
71
+ /**
72
+ * Set a default value for param.
73
+ * Default value passed through validation function as ordinary value.
74
+ *
75
+ * @param {*} def - default value of function generator
76
+ * @returns {COA.CoaParam} - this instance (for chainability)
77
+ */
78
+ def(def) {
79
+ this._def = def;
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * Make option value inputting stream.
85
+ * It's add useful validation and shortcut for STDIN.
86
+ *
87
+ * @returns {COA.CoaParam} - this instance (for chainability)
88
+ */
89
+ input() {
90
+ process.stdin.pause();
91
+ return this
92
+ .def(process.stdin)
93
+ .val(function(v) {
94
+ if(typeof v !== 'string')
95
+ return v;
96
+
97
+ if(v === '-')
98
+ return process.stdin;
99
+
100
+ const s = fs.createReadStream(v, { encoding : 'utf8' });
101
+ s.pause();
102
+ return s;
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Make option value outputing stream.
108
+ * It's add useful validation and shortcut for STDOUT.
109
+ *
110
+ * @returns {COA.CoaParam} - this instance (for chainability)
111
+ */
112
+ output() {
113
+ return this
114
+ .def(process.stdout)
115
+ .val(function(v) {
116
+ if(typeof v !== 'string')
117
+ return v;
118
+
119
+ if(v === '-')
120
+ return process.stdout;
121
+
122
+ return fs.createWriteStream(v, { encoding : 'utf8' });
123
+ });
124
+ }
125
+ };
package/lib/color.js CHANGED
@@ -1,25 +1,22 @@
1
- // Generated by CoffeeScript 1.6.3
2
- var colors;
1
+ 'use strict';
3
2
 
4
- colors = {
5
- black: '30',
6
- dgray: '1;30',
7
- red: '31',
8
- lred: '1;31',
9
- green: '32',
10
- lgreen: '1;32',
11
- brown: '33',
12
- yellow: '1;33',
13
- blue: '34',
14
- lblue: '1;34',
15
- purple: '35',
16
- lpurple: '1;35',
17
- cyan: '36',
18
- lcyan: '1;36',
19
- lgray: '37',
20
- white: '1;37'
3
+ const colors = {
4
+ black : '30',
5
+ dgray : '1;30',
6
+ red : '31',
7
+ lred : '1;31',
8
+ green : '32',
9
+ lgreen : '1;32',
10
+ brown : '33',
11
+ yellow : '1;33',
12
+ blue : '34',
13
+ lblue : '1;34',
14
+ purple : '35',
15
+ lpurple : '1;35',
16
+ cyan : '36',
17
+ lcyan : '1;36',
18
+ lgray : '37',
19
+ white : '1;37'
21
20
  };
22
21
 
23
- exports.Color = function(c, str) {
24
- return ['\x1B[', colors[c], 'm', str, '\x1B[m'].join('');
25
- };
22
+ module.exports = (c, str) => `\x1B[${colors[c]}m${str}\x1B[m`;
package/lib/completion.js CHANGED
@@ -1,134 +1,176 @@
1
- // Generated by CoffeeScript 1.6.3
1
+ 'use strict';
2
+
3
+ const constants = require('constants');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const Q = require('q');
8
+
9
+ const shell = require('./shell');
10
+ const escape = shell.escape;
11
+ const unescape = shell.unescape;
12
+
2
13
  /**
3
- Most of the code adopted from the npm package shell completion code.
4
- See https://github.com/isaacs/npm/blob/master/lib/completion.js
5
- */
14
+ * Most of the code adopted from the npm package shell completion code.
15
+ * See https://github.com/isaacs/npm/blob/master/lib/completion.js
16
+ *
17
+ * @returns {COA.CoaObject}
18
+ */
19
+ module.exports = function completion() {
20
+ return this
21
+ .title('Shell completion')
22
+ .helpful()
23
+ .arg()
24
+ .name('raw')
25
+ .title('Completion words')
26
+ .arr()
27
+ .end()
28
+ .act((opts, args) => {
29
+ if(process.platform === 'win32') {
30
+ const e = new Error('shell completion not supported on windows');
31
+ e.code = 'ENOTSUP';
32
+ e.errno = constants.ENOTSUP;
33
+ return this.reject(e);
34
+ }
6
35
 
7
- var Q, complete, dumpScript, escape, getOpts, unescape;
36
+ // if the COMP_* isn't in the env, then just dump the script
37
+ if((process.env.COMP_CWORD == null)
38
+ || (process.env.COMP_LINE == null)
39
+ || (process.env.COMP_POINT == null)) {
40
+ return dumpScript(this._cmd._name);
41
+ }
8
42
 
9
- Q = require('q');
43
+ console.error('COMP_LINE: %s', process.env.COMP_LINE);
44
+ console.error('COMP_CWORD: %s', process.env.COMP_CWORD);
45
+ console.error('COMP_POINT: %s', process.env.COMP_POINT);
46
+ console.error('args: %j', args.raw);
10
47
 
11
- escape = require('./shell').escape;
48
+ // completion opts
49
+ opts = getOpts(args.raw);
12
50
 
13
- unescape = require('./shell').unescape;
51
+ // cmd
52
+ const parsed = this._cmd._parseCmd(opts.partialWords);
53
+ return Q.when(complete(parsed.cmd, parsed.opts), compls => {
54
+ console.error('filtered: %j', compls);
55
+ return console.log(compls.map(escape).join('\n'));
56
+ });
57
+ });
58
+ };
14
59
 
15
- module.exports = function() {
16
- return this.title('Shell completion').helpful().arg().name('raw').title('Completion words').arr().end().act(function(opts, args) {
17
- var argv, cmd, e, _ref;
18
- if (process.platform === 'win32') {
19
- e = new Error('shell completion not supported on windows');
20
- e.code = 'ENOTSUP';
21
- e.errno = require('constants').ENOTSUP;
22
- return this.reject(e);
23
- }
24
- if ((process.env.COMP_CWORD == null) || (process.env.COMP_LINE == null) || (process.env.COMP_POINT == null)) {
25
- return dumpScript(this._cmd._name);
26
- }
27
- console.error('COMP_LINE: %s', process.env.COMP_LINE);
28
- console.error('COMP_CWORD: %s', process.env.COMP_CWORD);
29
- console.error('COMP_POINT: %s', process.env.COMP_POINT);
30
- console.error('args: %j', args.raw);
31
- opts = getOpts(args.raw);
32
- _ref = this._cmd._parseCmd(opts.partialWords), cmd = _ref.cmd, argv = _ref.argv;
33
- return Q.when(complete(cmd, opts), function(compls) {
34
- console.error('filtered: %j', compls);
35
- return console.log(compls.map(escape).join('\n'));
60
+ function dumpScript(name) {
61
+ const defer = Q.defer();
62
+
63
+ fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) {
64
+ if(err) return defer.reject(err);
65
+ d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^\#\!.*?\n/, '');
66
+
67
+ process.stdout.on('error', onError);
68
+ process.stdout.write(d, () => defer.resolve());
36
69
  });
37
- });
38
- };
39
70
 
40
- dumpScript = function(name) {
41
- var defer, fs, path;
42
- fs = require('fs');
43
- path = require('path');
44
- defer = Q.defer();
45
- fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) {
46
- var onError;
47
- if (err) {
48
- return defer.reject(err);
49
- }
50
- d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^\#\!.*?\n/, '');
51
- onError = function(err) {
52
- if (err.errno === require('constants').EPIPE) {
71
+ return defer.promise;
72
+
73
+ function onError(err) {
74
+ // Darwin is a real dick sometimes.
75
+ //
76
+ // This is necessary because the "source" or "." program in
77
+ // bash on OS X closes its file argument before reading
78
+ // from it, meaning that you get exactly 1 write, which will
79
+ // work most of the time, and will always raise an EPIPE.
80
+ //
81
+ // Really, one should not be tossing away EPIPE errors, or any
82
+ // errors, so casually. But, without this, `. <(cmd completion)`
83
+ // can never ever work on OS X.
84
+ if(err.errno !== constants.EPIPE) return defer.reject(err);
53
85
  process.stdout.removeListener('error', onError);
54
86
  return defer.resolve();
55
- } else {
56
- return defer.reject(err);
57
- }
87
+ }
88
+ }
89
+
90
+ function getOpts(argv) {
91
+ // get the partial line and partial word, if the point isn't at the end
92
+ // ie, tabbing at: cmd foo b|ar
93
+ const line = process.env.COMP_LINE;
94
+ const w = +process.env.COMP_CWORD;
95
+ const point = +process.env.COMP_POINT;
96
+ const words = argv.map(unescape);
97
+ const word = words[w];
98
+ const partialLine = line.substr(0, point);
99
+ const partialWords = words.slice(0, w);
100
+
101
+ // figure out where in that last word the point is
102
+ let partialWord = argv[w] || '';
103
+ let i = partialWord.length;
104
+ while(partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) i--;
105
+
106
+ partialWord = unescape(partialWord.substr(0, i));
107
+ partialWord && partialWords.push(partialWord);
108
+
109
+ return {
110
+ line,
111
+ w,
112
+ point,
113
+ words,
114
+ word,
115
+ partialLine,
116
+ partialWords,
117
+ partialWord
58
118
  };
59
- process.stdout.on('error', onError);
60
- return process.stdout.write(d, function() {
61
- return defer.resolve();
62
- });
63
- });
64
- return defer.promise;
65
- };
119
+ }
66
120
 
67
- getOpts = function(argv) {
68
- var i, line, partialLine, partialWord, partialWords, point, w, word, words;
69
- line = process.env.COMP_LINE;
70
- w = +process.env.COMP_CWORD;
71
- point = +process.env.COMP_POINT;
72
- words = argv.map(unescape);
73
- word = words[w];
74
- partialLine = line.substr(0, point);
75
- partialWords = words.slice(0, w);
76
- partialWord = argv[w] || '';
77
- i = partialWord.length;
78
- while (partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) {
79
- i--;
80
- }
81
- partialWord = unescape(partialWord.substr(0, i));
82
- if (partialWord) {
83
- partialWords.push(partialWord);
84
- }
85
- return {
86
- line: line,
87
- w: w,
88
- point: point,
89
- words: words,
90
- word: word,
91
- partialLine: partialLine,
92
- partialWords: partialWords,
93
- partialWord: partialWord
94
- };
95
- };
121
+ function complete(cmd, opts) {
122
+ let optWord, optPrefix,
123
+ compls = [];
96
124
 
97
- complete = function(cmd, opts) {
98
- var compls, m, o, opt, optPrefix, optWord;
99
- compls = [];
100
- if (opts.partialWord.indexOf('-')) {
101
- compls = Object.keys(cmd._cmdsByName);
102
- } else {
103
- if (m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/)) {
104
- optWord = m[1];
105
- optPrefix = optWord + '=';
106
- } else {
107
- compls = Object.keys(cmd._optsByKey);
108
- }
109
- }
110
- if (!(o = opts.partialWords[opts.w - 1]).indexOf('-')) {
111
- optWord = o;
112
- }
113
- if (optWord && (opt = cmd._optsByKey[optWord])) {
114
- if (!opt._flag && opt._comp) {
115
- compls = Q.all([compls, opt._comp(opts)]).spread(function(c, o) {
116
- return c.concat(o.map(function(v) {
117
- return (optPrefix || '') + v;
118
- }));
119
- });
125
+ // Complete on cmds
126
+ if(opts.partialWord.indexOf('-'))
127
+ compls = Object.keys(cmd._cmdsByName);
128
+ // Complete on required opts without '-' in last partial word
129
+ // (if required not already specified)
130
+ //
131
+ // Commented out because of uselessness:
132
+ // -b, --block suggest results in '-' on cmd line;
133
+ // next completion suggest all options, because of '-'
134
+ //.concat Object.keys(cmd._optsByKey).filter (v) -> cmd._optsByKey[v]._req
135
+ else {
136
+ // complete on opt values: --opt=| case
137
+ const m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/);
138
+ if(m) {
139
+ optWord = m[1];
140
+ optPrefix = optWord + '=';
141
+ } else
142
+ // complete on opts
143
+ // don't complete on opts in case of --opt=val completion
144
+ // TODO: don't complete on opts in case of unknown arg after commands
145
+ // TODO: complete only on opts with arr() or not already used
146
+ // TODO: complete only on full opts?
147
+ compls = Object.keys(cmd._optsByKey);
120
148
  }
121
- }
122
- if (cmd._comp) {
123
- compls = Q.all([compls, cmd._comp(opts)]).spread(function(c, o) {
124
- return c.concat(o);
125
- });
126
- }
127
- return Q.when(compls, function(compls) {
128
- console.error('partialWord: %s', opts.partialWord);
129
- console.error('compls: %j', compls);
130
- return compls.filter(function(c) {
131
- return c.indexOf(opts.partialWord) === 0;
149
+
150
+ // complete on opt values: next arg case
151
+ opts.partialWords[opts.w - 1].indexOf('-') || (optWord = opts.partialWords[opts.w - 1]);
152
+
153
+ // complete on opt values: completion
154
+ let opt;
155
+ optWord
156
+ && (opt = cmd._optsByKey[optWord])
157
+ && !opt._flag
158
+ && opt._comp
159
+ && (compls = Q.join(compls,
160
+ Q.when(opt._comp(opts),
161
+ (c, o) => c.concat(o.map(v => (optPrefix || '') + v)))));
162
+
163
+ // TODO: complete on args values (context aware, custom completion?)
164
+
165
+ // custom completion on cmds
166
+ cmd._comp && (compls = Q.join(compls, Q.when(cmd._comp(opts)), (c, o) => c.concat(o)));
167
+
168
+ // TODO: context aware custom completion on cmds, opts and args
169
+ // (can depend on already entered values, especially options)
170
+
171
+ return Q.when(compls, complitions => {
172
+ console.error('partialWord: %s', opts.partialWord);
173
+ console.error('compls: %j', complitions);
174
+ return compls.filter(c => c.indexOf(opts.partialWord) === 0);
132
175
  });
133
- });
134
- };
176
+ }
package/lib/index.js CHANGED
@@ -1,10 +1,14 @@
1
- // Generated by CoffeeScript 1.6.3
2
- exports.Cmd = require('./cmd').Cmd;
3
-
4
- exports.Opt = require('./cmd').Opt;
5
-
6
- exports.Arg = require('./cmd').Arg;
7
-
8
- exports.shell = require('./shell');
9
-
10
- exports.require = require;
1
+ const
2
+ Cmd = require('./cmd'),
3
+ Opt = require('./opt'),
4
+ Arg = require('./arg'),
5
+ shell = require('./shell');
6
+
7
+ module.exports = {
8
+ Cmd : Cmd.create,
9
+ Opt : Opt.create,
10
+ Arg : Arg.create,
11
+ classes : { Cmd, Opt, Arg },
12
+ shell,
13
+ require
14
+ };