|
1 | 1 | 'use strict'; |
2 | 2 |
|
3 | | -exports.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 |
18 | | -var CONTROL = '(?:' + [ |
19 | | - '\\|\\|', '\\&\\&', ';;', '\\|\\&', '\\<\\(', '>>', '>\\&', '[&;()|<>]' |
20 | | -].join('|') + ')'; |
21 | | -var META = '|&;()<> \\t'; |
22 | | -var BAREWORD = '(\\\\[\'"' + META + ']|[^\\s\'"' + META + '])+'; |
23 | | -var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"'; |
24 | | -var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\''; |
25 | | - |
26 | | -var TOKEN = ''; |
27 | | -for (var i = 0; i < 4; i++) { |
28 | | - TOKEN += (Math.pow(16, 8) * Math.random()).toString(16); |
29 | | -} |
30 | | - |
31 | | -function 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 | | - |
183 | | -exports.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 | | -}; |
| 3 | +exports.quote = require('./quote'); |
| 4 | +exports.parse = require('./parse'); |
0 commit comments