UNPKG

5.11 kBJavaScriptView Raw
1'use strict';
2
3exports.quote = function (xs) {
4 return xs.map(function (s) {
5 if (s && typeof s === 'object') {
6 return s.op.replace(/(.)/g, '\\$1');
7 } else if ((/["\s]/).test(s) && !(/'/).test(s)) {
8 return "'" + s.replace(/(['\\])/g, '\\$1') + "'";
9 } else if ((/["'\s]/).test(s)) {
10 return '"' + s.replace(/(["\\$`!])/g, '\\$1') + '"';
11 }
12 return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2');
13 }).join(' ');
14};
15
16// '<(' is process substitution operator and
17// can be parsed the same as control operator
18var CONTROL = '(?:' + [
19 '\\|\\|', '\\&\\&', ';;', '\\|\\&', '\\<\\(', '>>', '>\\&', '[&;()|<>]'
20].join('|') + ')';
21var META = '|&;()<> \\t';
22var BAREWORD = '(\\\\[\'"' + META + ']|[^\\s\'"' + META + '])+';
23var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"';
24var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\'';
25
26var TOKEN = '';
27for (var i = 0; i < 4; i++) {
28 TOKEN += (Math.pow(16, 8) * Math.random()).toString(16);
29}
30
31function parse(s, env, opts) {
32 var chunker = new RegExp([
33 '(' + CONTROL + ')', // control chars
34 '(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*'
35 ].join('|'), 'g');
36 var match = s.match(chunker).filter(Boolean);
37
38 if (!match) {
39 return [];
40 }
41 if (!env) {
42 env = {};
43 }
44 if (!opts) {
45 opts = {};
46 }
47
48 var commented = false;
49
50 function getVar(_, pre, key) {
51 var r = typeof env === 'function' ? env(key) : env[key];
52 if (r === undefined && key != '') {
53 r = '';
54 } else if (r === undefined) {
55 r = '$';
56 }
57
58 if (typeof r === 'object') {
59 return pre + TOKEN + JSON.stringify(r) + TOKEN;
60 }
61 return pre + r;
62 }
63
64 return match.map(function (s, j) {
65 if (commented) {
66 return void undefined;
67 }
68 if (RegExp('^' + CONTROL + '$').test(s)) {
69 return { op: s };
70 }
71
72 // Hand-written scanner/parser for Bash quoting rules:
73 //
74 // 1. inside single quotes, all characters are printed literally.
75 // 2. inside double quotes, all characters are printed literally
76 // except variables prefixed by '$' and backslashes followed by
77 // either a double quote or another backslash.
78 // 3. outside of any quotes, backslashes are treated as escape
79 // characters and not printed (unless they are themselves escaped)
80 // 4. quote context can switch mid-token if there is no whitespace
81 // between the two quote contexts (e.g. all'one'"token" parses as
82 // "allonetoken")
83 var SQ = "'";
84 var DQ = '"';
85 var DS = '$';
86 var BS = opts.escape || '\\';
87 var quote = false;
88 var esc = false;
89 var out = '';
90 var isGlob = false;
91 var i;
92
93 function parseEnvVar() {
94 i += 1;
95 var varend;
96 var varname;
97 // debugger
98 if (s.charAt(i) === '{') {
99 i += 1;
100 if (s.charAt(i) === '}') {
101 throw new Error('Bad substitution: ' + s.substr(i - 2, 3));
102 }
103 varend = s.indexOf('}', i);
104 if (varend < 0) {
105 throw new Error('Bad substitution: ' + s.substr(i));
106 }
107 varname = s.substr(i, varend - i);
108 i = varend;
109 } else if ((/[*@#?$!_-]/).test(s.charAt(i))) {
110 varname = s.charAt(i);
111 i += 1;
112 } else {
113 varend = s.substr(i).match(/[^\w\d_]/);
114 if (!varend) {
115 varname = s.substr(i);
116 i = s.length;
117 } else {
118 varname = s.substr(i, varend.index);
119 i += varend.index - 1;
120 }
121 }
122 return getVar(null, '', varname);
123 }
124
125 for (i = 0; i < s.length; i++) {
126 var c = s.charAt(i);
127 isGlob = isGlob || (!quote && (c === '*' || c === '?'));
128 if (esc) {
129 out += c;
130 esc = false;
131 } else if (quote) {
132 if (c === quote) {
133 quote = false;
134 } else if (quote == SQ) {
135 out += c;
136 } else { // Double quote
137 if (c === BS) {
138 i += 1;
139 c = s.charAt(i);
140 if (c === DQ || c === BS || c === DS) {
141 out += c;
142 } else {
143 out += BS + c;
144 }
145 } else if (c === DS) {
146 out += parseEnvVar();
147 } else {
148 out += c;
149 }
150 }
151 } else if (c === DQ || c === SQ) {
152 quote = c;
153 } else if (RegExp('^' + CONTROL + '$').test(c)) {
154 return { op: s };
155 } else if ((/^#$/).test(c)) {
156 commented = true;
157 if (out.length) {
158 return [out, { comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }];
159 }
160 return [{ comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }];
161 } else if (c === BS) {
162 esc = true;
163 } else if (c === DS) {
164 out += parseEnvVar();
165 } else {
166 out += c;
167 }
168 }
169
170 if (isGlob) {
171 return { op: 'glob', pattern: out };
172 }
173
174 return out;
175 }).reduce(function (prev, arg) { // finalize parsed aruments
176 if (arg === undefined) {
177 return prev;
178 }
179 return prev.concat(arg);
180 }, []);
181}
182
183exports.parse = function (s, env, opts) {
184 var mapped = parse(s, env, opts);
185 if (typeof env !== 'function') {
186 return mapped;
187 }
188 return mapped.reduce(function (acc, s) {
189 if (typeof s === 'object') {
190 return acc.concat(s);
191 }
192 var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g'));
193 if (xs.length === 1) {
194 return acc.concat(xs[0]);
195 }
196 return acc.concat(xs.filter(Boolean).map(function (x) {
197 if (RegExp('^' + TOKEN).test(x)) {
198 return JSON.parse(x.split(TOKEN)[1]);
199 }
200 return x;
201 }));
202 }, []);
203};