UNPKG

4.4 kBJavaScriptView Raw
1'use strict';
2
3// fake symbols
4const $$ = key => `__Symbol@@${key}__`;
5const __QUOTE__ = $$('QUOTE');
6const __APOS__ = $$('APOS');
7const __SPA__ = $$('SPA');
8
9const RE_CAMEL_CASE = /-(\w)/g;
10
11const RE_MATCH_KEYVAL = /^((?!\d)[-~+.\w]+)([=:])(.+?)?$/;
12const RE_MATCH_QUOTES = /(["'])(?:(?!\1).)*\1/;
13
14const RE_SPECIAL_CHARS = ['"', "'", ' '];
15const RE_ESCAPE_CHARS = [/\\"/g, /\\'/g, /\\ /g];
16const RE_QUOTED_CHARS = [__QUOTE__, __APOS__, __SPA__];
17const RE_UNESCAPE_CHARS = [__QUOTE__, __APOS__, __SPA__].map(_ => new RegExp(_, 'g'));
18
19const getopts = require('getopts');
20
21// "escape" special chars
22function escape(val) {
23 return RE_ESCAPE_CHARS
24 .reduce((prev, cur, i) => prev.replace(cur, RE_QUOTED_CHARS[i]), val);
25}
26
27// "unescape" quotes
28function unescape(val) {
29 return RE_UNESCAPE_CHARS
30 .reduce((prev, cur, i) => prev.replace(cur, RE_SPECIAL_CHARS[i]), val);
31}
32
33// encode special chars within quotes
34function unquote(val, extra) {
35 while (RE_MATCH_QUOTES.test(val)) {
36 const matches = val.match(RE_MATCH_QUOTES);
37 const substr = matches[0].substr(1, matches[0].length - 2);
38
39 val = val.replace(matches[0], RE_SPECIAL_CHARS
40 .reduce((prev, cur, i) => prev.replace(cur, RE_QUOTED_CHARS[i]), substr));
41 }
42
43 /* istanbul ignore else */
44 if (extra) {
45 while (val.indexOf(' ') > -1) {
46 val = RE_SPECIAL_CHARS
47 .reduce((prev, cur, i) => prev.replace(cur, RE_QUOTED_CHARS[i]), val);
48 }
49 }
50
51 return val.split(/\s+/);
52}
53
54// execute given callback
55function evaluate(value, cb) {
56 /* istanbul ignore else */
57 if (typeof cb === 'function') {
58 if (typeof value !== 'object') {
59 return cb(value);
60 }
61
62 Object.keys(value).forEach(k => {
63 value[k] = cb(value[k], k);
64 });
65 }
66
67 return value;
68}
69
70// fast camelcasing
71function camelcase(value) {
72 return value.replace(RE_CAMEL_CASE, (_, char) => char.toUpperCase());
73}
74
75module.exports = (argv, opts, cb) => {
76 opts = opts || {};
77
78 /* istanbul ignore else */
79 if (typeof opts === 'function') {
80 cb = opts;
81 opts = {};
82 }
83
84 /* istanbul ignore else */
85 if (opts.format) {
86 cb = opts.format;
87 delete opts.format;
88 }
89
90 /* istanbul ignore else */
91 if (!Array.isArray(argv)) {
92 // "normalize" input
93 argv = escape(String(argv || ''));
94
95 let test;
96
97 do {
98 test = argv.match(RE_MATCH_QUOTES);
99
100 /* istanbul ignore else */
101 if (test) {
102 argv = argv.replace(test[0], unquote(test[0], true));
103 }
104 } while (test);
105
106 // tokenize
107 argv = argv.trim().split(/\s+/).map(unescape).filter(x => x);
108 }
109
110 const offset = argv.indexOf('--');
111 const _raw = [];
112
113 /* istanbul ignore else */
114 if (offset > -1) {
115 argv.slice(offset + 1).forEach(value => {
116 _raw.push(evaluate(value, cb));
117 });
118
119 argv.splice(offset + 1, argv.length);
120 }
121
122 const _flags = getopts(argv, opts);
123 const _extra = _flags._.slice();
124
125 const _data = {};
126 const _params = {};
127
128 delete _flags._;
129
130 Object.keys(_flags).forEach(key => {
131 const value = _flags[key];
132
133 /* istanbul ignore else */
134 if (key.indexOf('-') !== -1) {
135 delete _flags[key];
136
137 key = camelcase(key);
138 }
139
140 /* istanbul ignore else */
141 if (opts.alias && opts.alias[key] && opts.alias[key].indexOf('no-') === 0) {
142 _flags[opts.alias[key].substr(3)] = !value;
143 _flags[key] = !value;
144 }
145
146 /* istanbul ignore else */
147 if (Array.isArray(value)) {
148 _flags[key] = value.map(x => evaluate(x, cb));
149 }
150
151 /* istanbul ignore else */
152 if (typeof value === 'string') {
153 if (value.charAt() === '=') {
154 _flags[key] = evaluate(value.substr(1), cb);
155 } else {
156 _flags[key] = evaluate(value, cb);
157 }
158 }
159 });
160
161 const __ = _extra.reduce((prev, cur) => {
162 const matches = cur.match(RE_MATCH_KEYVAL);
163
164 if (matches) {
165 if (matches[2] === '=') {
166 _data[matches[1]] = evaluate(matches[3] || '', cb);
167 } else if (_params[matches[1]]) {
168 if (!Array.isArray(_params[matches[1]])) {
169 _params[matches[1]] = [_params[matches[1]]];
170 }
171
172 _params[matches[1]].push(evaluate(matches[3] || '', cb));
173 } else {
174 _params[matches[1]] = evaluate(matches[3] || '', cb);
175 }
176 } else {
177 prev.push(evaluate(cur, cb));
178 }
179
180 return prev;
181 }, []);
182
183 return {
184 _: __,
185 raw: _raw,
186 data: _data,
187 flags: _flags,
188 params: _params,
189 };
190};