UNPKG

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