1 | const mri = require('mri');
|
2 | const $ = require('./utils');
|
3 |
|
4 | const ALL = '__all__';
|
5 | const DEF = '__default__';
|
6 |
|
7 | class Sade {
|
8 | constructor(name) {
|
9 | this.tree = {};
|
10 | this.name = name;
|
11 | this.ver = '0.0.0';
|
12 | this.default = '';
|
13 |
|
14 | this.command(ALL);
|
15 | this.command(`${DEF} <command>`)
|
16 | .option('-v, --version', 'Displays current version');
|
17 | this.curr = '';
|
18 | }
|
19 |
|
20 | command(str, desc, opts) {
|
21 | let cmd=[], usage=[], rgx=/(\[|<)/;
|
22 |
|
23 | str.split(/\s+/).forEach(x => {
|
24 | (rgx.test(x.charAt(0)) ? usage : cmd).push(x);
|
25 | });
|
26 |
|
27 |
|
28 | cmd = cmd.join(' ');
|
29 |
|
30 | if (cmd in this.tree) {
|
31 | throw new Error(`Command already exists: ${cmd}`);
|
32 | }
|
33 |
|
34 | this.curr = cmd;
|
35 | (opts && opts.default) && (this.default=cmd);
|
36 |
|
37 | !~cmd.indexOf('__') && usage.unshift(cmd);
|
38 | usage = usage.join(' ');
|
39 |
|
40 | this.tree[cmd] = { usage, options:[], alias:{}, default:{}, examples:[] };
|
41 | desc && this.describe(desc);
|
42 |
|
43 | return this;
|
44 | }
|
45 |
|
46 | describe(str) {
|
47 | this.tree[this.curr || DEF].describe = Array.isArray(str) ? str : $.sentences(str);
|
48 | return this;
|
49 | }
|
50 |
|
51 | option(str, desc, val) {
|
52 | let cmd = this.tree[ this.curr || ALL ];
|
53 |
|
54 | let [flag, alias] = $.parse(str);
|
55 | (alias && alias.length > 1) && ([flag, alias]=[alias, flag]);
|
56 |
|
57 | str = `--${flag}`;
|
58 | if (alias && alias.length > 0) {
|
59 | str = `-${alias}, ${str}`;
|
60 | let old = cmd.alias[alias];
|
61 | cmd.alias[alias] = (old || []).concat(flag);
|
62 | }
|
63 |
|
64 | let arr = [str, desc || ''];
|
65 |
|
66 | if (val !== void 0) {
|
67 | arr.push(val);
|
68 | cmd.default[flag] = val;
|
69 | }
|
70 |
|
71 | cmd.options.push(arr);
|
72 | return this;
|
73 | }
|
74 |
|
75 | action(handler) {
|
76 | this.tree[ this.curr || DEF ].handler = handler;
|
77 | return this;
|
78 | }
|
79 |
|
80 | example(str) {
|
81 | this.tree[ this.curr || DEF ].examples.push(str);
|
82 | return this;
|
83 | }
|
84 |
|
85 | version(str) {
|
86 | this.ver = str;
|
87 | return this;
|
88 | }
|
89 |
|
90 | parse(arr, opts={}) {
|
91 | let offset = 2;
|
92 | let alias = { h:'help', v:'version' };
|
93 | let argv = mri(arr.slice(offset), { alias });
|
94 | let bin = this.name;
|
95 |
|
96 |
|
97 | let tmp, name='';
|
98 | let i=1, len=argv._.length + 1;
|
99 | for (; i < len; i++) {
|
100 | tmp = argv._.slice(0, i).join(' ');
|
101 | if (this.tree[tmp] !== void 0) {
|
102 | name=tmp; offset=(i + 2);
|
103 | }
|
104 | }
|
105 |
|
106 | let cmd = this.tree[name];
|
107 | let isVoid = (cmd === void 0);
|
108 |
|
109 | if (isVoid) {
|
110 | if (this.default) {
|
111 | name = this.default;
|
112 | cmd = this.tree[name];
|
113 | arr.unshift(name);
|
114 | offset++;
|
115 | } else if (name) {
|
116 | return $.error(bin, `Invalid command: ${name}`);
|
117 | }
|
118 | }
|
119 |
|
120 | if (argv.version) {
|
121 | return console.log(`${bin}, ${this.ver}`);
|
122 | }
|
123 |
|
124 | if (argv.help) {
|
125 | return this.help(!isVoid && name);
|
126 | }
|
127 |
|
128 | if (cmd === void 0) {
|
129 | return $.error(bin, 'No command specified.');
|
130 | }
|
131 |
|
132 | let all = this.tree[ALL];
|
133 |
|
134 | opts.alias = Object.assign(all.alias, cmd.alias, opts.alias);
|
135 | opts.default = Object.assign(all.default, cmd.default, opts.default);
|
136 |
|
137 | let vals = mri(arr.slice(offset), opts);
|
138 | let segs = cmd.usage.split(/\s+/);
|
139 | let reqs = segs.filter(x => x.charAt(0)==='<');
|
140 | let args = vals._.splice(0, reqs.length);
|
141 |
|
142 | if (args.length < reqs.length) {
|
143 | name && (bin += ` ${name}`);
|
144 | return $.error(bin, 'Insufficient arguments!');
|
145 | }
|
146 |
|
147 | segs.filter(x => x.charAt(0)==='[').forEach(_ => {
|
148 | args.push(vals._.pop());
|
149 | });
|
150 |
|
151 | args.push(vals);
|
152 | let handler = cmd.handler;
|
153 | return opts.lazy ? { args, name, handler } : handler.apply(null, args);
|
154 | }
|
155 |
|
156 | help(str) {
|
157 | console.log(
|
158 | $.help(this.name, this.tree, str || DEF)
|
159 | );
|
160 | }
|
161 | }
|
162 |
|
163 | module.exports = str => new Sade(str);
|