Projekt

Obecné

Profil

Stáhnout (4.68 KB) Statistiky
| Větev: | Revize:
1
var concatMap = require('concat-map');
2
var balanced = require('balanced-match');
3

    
4
module.exports = expandTop;
5

    
6
var escSlash = '\0SLASH'+Math.random()+'\0';
7
var escOpen = '\0OPEN'+Math.random()+'\0';
8
var escClose = '\0CLOSE'+Math.random()+'\0';
9
var escComma = '\0COMMA'+Math.random()+'\0';
10
var escPeriod = '\0PERIOD'+Math.random()+'\0';
11

    
12
function numeric(str) {
13
  return parseInt(str, 10) == str
14
    ? parseInt(str, 10)
15
    : str.charCodeAt(0);
16
}
17

    
18
function escapeBraces(str) {
19
  return str.split('\\\\').join(escSlash)
20
            .split('\\{').join(escOpen)
21
            .split('\\}').join(escClose)
22
            .split('\\,').join(escComma)
23
            .split('\\.').join(escPeriod);
24
}
25

    
26
function unescapeBraces(str) {
27
  return str.split(escSlash).join('\\')
28
            .split(escOpen).join('{')
29
            .split(escClose).join('}')
30
            .split(escComma).join(',')
31
            .split(escPeriod).join('.');
32
}
33

    
34

    
35
// Basically just str.split(","), but handling cases
36
// where we have nested braced sections, which should be
37
// treated as individual members, like {a,{b,c},d}
38
function parseCommaParts(str) {
39
  if (!str)
40
    return [''];
41

    
42
  var parts = [];
43
  var m = balanced('{', '}', str);
44

    
45
  if (!m)
46
    return str.split(',');
47

    
48
  var pre = m.pre;
49
  var body = m.body;
50
  var post = m.post;
51
  var p = pre.split(',');
52

    
53
  p[p.length-1] += '{' + body + '}';
54
  var postParts = parseCommaParts(post);
55
  if (post.length) {
56
    p[p.length-1] += postParts.shift();
57
    p.push.apply(p, postParts);
58
  }
59

    
60
  parts.push.apply(parts, p);
61

    
62
  return parts;
63
}
64

    
65
function expandTop(str) {
66
  if (!str)
67
    return [];
68

    
69
  // I don't know why Bash 4.3 does this, but it does.
70
  // Anything starting with {} will have the first two bytes preserved
71
  // but *only* at the top level, so {},a}b will not expand to anything,
72
  // but a{},b}c will be expanded to [a}c,abc].
73
  // One could argue that this is a bug in Bash, but since the goal of
74
  // this module is to match Bash's rules, we escape a leading {}
75
  if (str.substr(0, 2) === '{}') {
76
    str = '\\{\\}' + str.substr(2);
77
  }
78

    
79
  return expand(escapeBraces(str), true).map(unescapeBraces);
80
}
81

    
82
function identity(e) {
83
  return e;
84
}
85

    
86
function embrace(str) {
87
  return '{' + str + '}';
88
}
89
function isPadded(el) {
90
  return /^-?0\d/.test(el);
91
}
92

    
93
function lte(i, y) {
94
  return i <= y;
95
}
96
function gte(i, y) {
97
  return i >= y;
98
}
99

    
100
function expand(str, isTop) {
101
  var expansions = [];
102

    
103
  var m = balanced('{', '}', str);
104
  if (!m || /\$$/.test(m.pre)) return [str];
105

    
106
  var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
107
  var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
108
  var isSequence = isNumericSequence || isAlphaSequence;
109
  var isOptions = m.body.indexOf(',') >= 0;
110
  if (!isSequence && !isOptions) {
111
    // {a},b}
112
    if (m.post.match(/,.*\}/)) {
113
      str = m.pre + '{' + m.body + escClose + m.post;
114
      return expand(str);
115
    }
116
    return [str];
117
  }
118

    
119
  var n;
120
  if (isSequence) {
121
    n = m.body.split(/\.\./);
122
  } else {
123
    n = parseCommaParts(m.body);
124
    if (n.length === 1) {
125
      // x{{a,b}}y ==> x{a}y x{b}y
126
      n = expand(n[0], false).map(embrace);
127
      if (n.length === 1) {
128
        var post = m.post.length
129
          ? expand(m.post, false)
130
          : [''];
131
        return post.map(function(p) {
132
          return m.pre + n[0] + p;
133
        });
134
      }
135
    }
136
  }
137

    
138
  // at this point, n is the parts, and we know it's not a comma set
139
  // with a single entry.
140

    
141
  // no need to expand pre, since it is guaranteed to be free of brace-sets
142
  var pre = m.pre;
143
  var post = m.post.length
144
    ? expand(m.post, false)
145
    : [''];
146

    
147
  var N;
148

    
149
  if (isSequence) {
150
    var x = numeric(n[0]);
151
    var y = numeric(n[1]);
152
    var width = Math.max(n[0].length, n[1].length)
153
    var incr = n.length == 3
154
      ? Math.abs(numeric(n[2]))
155
      : 1;
156
    var test = lte;
157
    var reverse = y < x;
158
    if (reverse) {
159
      incr *= -1;
160
      test = gte;
161
    }
162
    var pad = n.some(isPadded);
163

    
164
    N = [];
165

    
166
    for (var i = x; test(i, y); i += incr) {
167
      var c;
168
      if (isAlphaSequence) {
169
        c = String.fromCharCode(i);
170
        if (c === '\\')
171
          c = '';
172
      } else {
173
        c = String(i);
174
        if (pad) {
175
          var need = width - c.length;
176
          if (need > 0) {
177
            var z = new Array(need + 1).join('0');
178
            if (i < 0)
179
              c = '-' + z + c.slice(1);
180
            else
181
              c = z + c;
182
          }
183
        }
184
      }
185
      N.push(c);
186
    }
187
  } else {
188
    N = concatMap(n, function(el) { return expand(el, false) });
189
  }
190

    
191
  for (var j = 0; j < N.length; j++) {
192
    for (var k = 0; k < post.length; k++) {
193
      var expansion = pre + N[j] + post[k];
194
      if (!isTop || isSequence || expansion)
195
        expansions.push(expansion);
196
    }
197
  }
198

    
199
  return expansions;
200
}
201

    
(3-3/4)