1 | var json = typeof JSON !== undefined ? JSON : require('jsonify');
|
2 | var map = require('array-map');
|
3 | var filter = require('array-filter');
|
4 | var reduce = require('array-reduce');
|
5 |
|
6 | exports.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 |
|
23 | var CONTROL = '(?:' + [
|
24 | '\\|\\|', '\\&\\&', ';;', '\\|\\&', '[&;()|<>]'
|
25 | ].join('|') + ')';
|
26 | var META = '|&;()<> \\t';
|
27 | var BAREWORD = '(\\\\[\'"' + META + ']|[^\\s\'"' + META + '])+';
|
28 | var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"';
|
29 | var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\'';
|
30 |
|
31 | var TOKEN = '';
|
32 | for (var i = 0; i < 4; i++) {
|
33 | TOKEN += (Math.pow(16,8)*Math.random()).toString(16);
|
34 | }
|
35 |
|
36 | exports.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 |
|
52 | function parse (s, env, opts) {
|
53 | var chunker = new RegExp([
|
54 | '(' + CONTROL + ')',
|
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 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
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 {
|
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 |
|
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 |
|
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 | }
|