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