UNPKG

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