8b436c3df942c3ad80700582c67233644cd0c2b5e0ea3269e7872d31f1f2e52fe6be6a64e9932b264a8049785dd67b93ce58ec2313e8c25e70379ed0ba9e6d 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { balanced } from '@isaacs/balanced-match';
  2. const escSlash = '\0SLASH' + Math.random() + '\0';
  3. const escOpen = '\0OPEN' + Math.random() + '\0';
  4. const escClose = '\0CLOSE' + Math.random() + '\0';
  5. const escComma = '\0COMMA' + Math.random() + '\0';
  6. const escPeriod = '\0PERIOD' + Math.random() + '\0';
  7. const escSlashPattern = new RegExp(escSlash, 'g');
  8. const escOpenPattern = new RegExp(escOpen, 'g');
  9. const escClosePattern = new RegExp(escClose, 'g');
  10. const escCommaPattern = new RegExp(escComma, 'g');
  11. const escPeriodPattern = new RegExp(escPeriod, 'g');
  12. const slashPattern = /\\\\/g;
  13. const openPattern = /\\{/g;
  14. const closePattern = /\\}/g;
  15. const commaPattern = /\\,/g;
  16. const periodPattern = /\\./g;
  17. function numeric(str) {
  18. return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
  19. }
  20. function escapeBraces(str) {
  21. return str
  22. .replace(slashPattern, escSlash)
  23. .replace(openPattern, escOpen)
  24. .replace(closePattern, escClose)
  25. .replace(commaPattern, escComma)
  26. .replace(periodPattern, escPeriod);
  27. }
  28. function unescapeBraces(str) {
  29. return str
  30. .replace(escSlashPattern, '\\')
  31. .replace(escOpenPattern, '{')
  32. .replace(escClosePattern, '}')
  33. .replace(escCommaPattern, ',')
  34. .replace(escPeriodPattern, '.');
  35. }
  36. /**
  37. * Basically just str.split(","), but handling cases
  38. * where we have nested braced sections, which should be
  39. * treated as individual members, like {a,{b,c},d}
  40. */
  41. function parseCommaParts(str) {
  42. if (!str) {
  43. return [''];
  44. }
  45. const parts = [];
  46. const m = balanced('{', '}', str);
  47. if (!m) {
  48. return str.split(',');
  49. }
  50. const { pre, body, post } = m;
  51. const p = pre.split(',');
  52. p[p.length - 1] += '{' + body + '}';
  53. const postParts = parseCommaParts(post);
  54. if (post.length) {
  55. ;
  56. p[p.length - 1] += postParts.shift();
  57. p.push.apply(p, postParts);
  58. }
  59. parts.push.apply(parts, p);
  60. return parts;
  61. }
  62. export function expand(str) {
  63. if (!str) {
  64. return [];
  65. }
  66. // I don't know why Bash 4.3 does this, but it does.
  67. // Anything starting with {} will have the first two bytes preserved
  68. // but *only* at the top level, so {},a}b will not expand to anything,
  69. // but a{},b}c will be expanded to [a}c,abc].
  70. // One could argue that this is a bug in Bash, but since the goal of
  71. // this module is to match Bash's rules, we escape a leading {}
  72. if (str.slice(0, 2) === '{}') {
  73. str = '\\{\\}' + str.slice(2);
  74. }
  75. return expand_(escapeBraces(str), true).map(unescapeBraces);
  76. }
  77. function embrace(str) {
  78. return '{' + str + '}';
  79. }
  80. function isPadded(el) {
  81. return /^-?0\d/.test(el);
  82. }
  83. function lte(i, y) {
  84. return i <= y;
  85. }
  86. function gte(i, y) {
  87. return i >= y;
  88. }
  89. function expand_(str, isTop) {
  90. /** @type {string[]} */
  91. const expansions = [];
  92. const m = balanced('{', '}', str);
  93. if (!m)
  94. return [str];
  95. // no need to expand pre, since it is guaranteed to be free of brace-sets
  96. const pre = m.pre;
  97. const post = m.post.length ? expand_(m.post, false) : [''];
  98. if (/\$$/.test(m.pre)) {
  99. for (let k = 0; k < post.length; k++) {
  100. const expansion = pre + '{' + m.body + '}' + post[k];
  101. expansions.push(expansion);
  102. }
  103. }
  104. else {
  105. const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
  106. const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
  107. const isSequence = isNumericSequence || isAlphaSequence;
  108. const isOptions = m.body.indexOf(',') >= 0;
  109. if (!isSequence && !isOptions) {
  110. // {a},b}
  111. if (m.post.match(/,(?!,).*\}/)) {
  112. str = m.pre + '{' + m.body + escClose + m.post;
  113. return expand_(str);
  114. }
  115. return [str];
  116. }
  117. let n;
  118. if (isSequence) {
  119. n = m.body.split(/\.\./);
  120. }
  121. else {
  122. n = parseCommaParts(m.body);
  123. if (n.length === 1 && n[0] !== undefined) {
  124. // x{{a,b}}y ==> x{a}y x{b}y
  125. n = expand_(n[0], false).map(embrace);
  126. //XXX is this necessary? Can't seem to hit it in tests.
  127. /* c8 ignore start */
  128. if (n.length === 1) {
  129. return post.map(p => m.pre + n[0] + p);
  130. }
  131. /* c8 ignore stop */
  132. }
  133. }
  134. // at this point, n is the parts, and we know it's not a comma set
  135. // with a single entry.
  136. let N;
  137. if (isSequence && n[0] !== undefined && n[1] !== undefined) {
  138. const x = numeric(n[0]);
  139. const y = numeric(n[1]);
  140. const width = Math.max(n[0].length, n[1].length);
  141. let incr = n.length === 3 && n[2] !== undefined ? Math.abs(numeric(n[2])) : 1;
  142. let test = lte;
  143. const reverse = y < x;
  144. if (reverse) {
  145. incr *= -1;
  146. test = gte;
  147. }
  148. const pad = n.some(isPadded);
  149. N = [];
  150. for (let i = x; test(i, y); i += incr) {
  151. let c;
  152. if (isAlphaSequence) {
  153. c = String.fromCharCode(i);
  154. if (c === '\\') {
  155. c = '';
  156. }
  157. }
  158. else {
  159. c = String(i);
  160. if (pad) {
  161. const need = width - c.length;
  162. if (need > 0) {
  163. const z = new Array(need + 1).join('0');
  164. if (i < 0) {
  165. c = '-' + z + c.slice(1);
  166. }
  167. else {
  168. c = z + c;
  169. }
  170. }
  171. }
  172. }
  173. N.push(c);
  174. }
  175. }
  176. else {
  177. N = [];
  178. for (let j = 0; j < n.length; j++) {
  179. N.push.apply(N, expand_(n[j], false));
  180. }
  181. }
  182. for (let j = 0; j < N.length; j++) {
  183. for (let k = 0; k < post.length; k++) {
  184. const expansion = pre + N[j] + post[k];
  185. if (!isTop || isSequence || expansion) {
  186. expansions.push(expansion);
  187. }
  188. }
  189. }
  190. }
  191. return expansions;
  192. }
  193. //# sourceMappingURL=index.js.map