index.js 6.4 KB

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