Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements #28

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
112 changes: 75 additions & 37 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ function unescapeBraces(str) {
.split(escPeriod).join('.');
}

// cache regex
var rDigit = /^-?0\d/;
var rNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/;
var rAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/
var raCloseB = /,.*\}/;

// Basically just str.split(","), but handling cases
// where we have nested braced sections, which should be
// treated as individual members, like {a,{b,c},d}
// this just split on ',' but not the `,` in {}
function parseCommaParts(str) {
if (!str)
return [''];
Expand Down Expand Up @@ -76,7 +82,7 @@ function expandTop(str) {
str = '\\{\\}' + str.substr(2);
}

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

function identity(e) {
Expand All @@ -87,7 +93,7 @@ function embrace(str) {
return '{' + str + '}';
}
function isPadded(el) {
return /^-?0\d/.test(el);
return rDigit.test(el);
}

function lte(i, y) {
Expand All @@ -97,59 +103,94 @@ function gte(i, y) {
return i >= y;
}

function expand(str, isTop) {
// cache common cases for repeat0
var cache = {
1: '0',
2: '00',
3: '000',
4: '0000',
5: '00000'
};

function repeat0(len) {
var ch = '0';

// cache common use cases
if (len <= 5) return cache[len];

var zeros = ''

while (true) {
// add `ch` to `zeros` if `len` is odd
if (len & 1) zeros += ch;
// devide `len` by 2, ditch the fraction
len >>= 1;
// "double" the `ch` so this operation count grows logarithmically on `len`
// each time `ch` is "doubled", the `len` would need to be "doubled" too
// similar to finding a value in binary search tree, hence O(log(n))
if (len) ch += ch;
// `len` is `0`, return `zeros`
else return zeros;
}
}

function expand(str) {
var expansions = [];

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

var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
// ${a,b} doesn't expand
if (!m || (m.pre && m.pre.indexOf('$') === m.pre.length - 1)) return [str];

// no need to expand pre, since it is guaranteed to be free of brace-sets
var pre = m.pre;

var isNumericSequence = rNumericSequence.test(m.body);
var isAlphaSequence = rAlphaSequence.test(m.body);
var isSequence = isNumericSequence || isAlphaSequence;
var isOptions = /^(.*,)+(.+)?$/.test(m.body);
var isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) {
// {a},b}
if (m.post.match(/,.*\}/)) {
str = m.pre + '{' + m.body + escClose + m.post;
if (m.post.match(raCloseB)) {
// we want m.body to be `a},b` :)
// so this becomes options; n would be ['a}', 'b']
str = pre + '{' + m.body + escClose + m.post;
// escaped the correct } and try again
return expand(str);
}
// "plain" string
return [str];
}

var post = m.post.length
? expand(m.post)
: [''];

// n is options or sequence parts. EG: ['opt1', 'opt2'] or ['1', '3']
// N is the final parts after m.post is expanded and/or all options are expanded.
// EG: ['1', '2', '3'] if sequence
var n;
if (isSequence) {
n = m.body.split(/\.\./);
} else {
var N = [];

if (isOptions) {
n = parseCommaParts(m.body);
if (n.length === 1) {
// x{{a,b}}y ==> x{a}y x{b}y
n = expand(n[0], false).map(embrace);
n = expand(n[0]).map(embrace);
if (n.length === 1) {
var post = m.post.length
? expand(m.post, false)
: [''];
return post.map(function(p) {
return m.pre + n[0] + p;
return pre + n[0] + p;
});
}
}
}

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

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

var N;
N = concatMap(n, function(el) { return expand(el) });
} else {
// isSequence is true
n = m.body.split('..');

if (isSequence) {
var x = numeric(n[0]);
var y = numeric(n[1]);
var width = Math.max(n[0].length, n[1].length)
var incr = n.length == 3
? Math.abs(numeric(n[2]))
: 1;
Expand All @@ -161,20 +202,20 @@ function expand(str, isTop) {
}
var pad = n.some(isPadded);

N = [];

for (var i = x; test(i, y); i += incr) {
var c;
if (isAlphaSequence) {
c = String.fromCharCode(i);
if (c === '\\')
c = '';
} else {
// isNumericSequence is true
c = String(i);
if (pad) {
var width = Math.max(n[0].length, n[1].length)
var need = width - c.length;
if (need > 0) {
var z = new Array(need + 1).join('0');
var z = repeat0(need);
if (i < 0)
c = '-' + z + c.slice(1);
else
Expand All @@ -184,18 +225,15 @@ function expand(str, isTop) {
}
N.push(c);
}
} else {
N = concatMap(n, function(el) { return expand(el, false) });
}

for (var j = 0; j < N.length; j++) {
for (var k = 0; k < post.length; k++) {
var expansion = pre + N[j] + post[k];
if (!isTop || isSequence || expansion)
if (isSequence || expansion)
expansions.push(expansion);
}
}

return expansions;
}

151 changes: 150 additions & 1 deletion test/bash-results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1081,4 +1081,153 @@ A{b,{d,e},{f,g}}Z
[a}b]><><><><{}{{},a}}b
[{}{}}b]
[{}a}b]><><><><{}a,b}c
[{}a,b}c]><><><><
[{}a,b}c]><><><><{0000000000000000000000000000000000000000000001..10}
[0000000000000000000000000000000000000000000001]
[0000000000000000000000000000000000000000000002]
[0000000000000000000000000000000000000000000003]
[0000000000000000000000000000000000000000000004]
[0000000000000000000000000000000000000000000005]
[0000000000000000000000000000000000000000000006]
[0000000000000000000000000000000000000000000007]
[0000000000000000000000000000000000000000000008]
[0000000000000000000000000000000000000000000009]
[0000000000000000000000000000000000000000000010]><><><><-v{,,,,}
[-v]
[-v]
[-v]
[-v]
[-v]><><><><{3..1}
[3]
[2]
[1]><><><><{10..8}
[10]
[9]
[8]><><><><{10..08}
[10]
[09]
[08]><><><><{c..a}
[c]
[b]
[a]><><><><{4..0..2}
[4]
[2]
[0]><><><><{4..0..-2}
[4]
[2]
[0]><><><><{e..a..2}
[e]
[c]
[a]><><><><{a,b{1..3},c}
[a]
[b1]
[b2]
[b3]
[c]><><><><{{A..Z},{a..z}}
[A]
[B]
[C]
[D]
[E]
[F]
[G]
[H]
[I]
[J]
[K]
[L]
[M]
[N]
[O]
[P]
[Q]
[R]
[S]
[T]
[U]
[V]
[W]
[X]
[Y]
[Z]
[a]
[b]
[c]
[d]
[e]
[f]
[g]
[h]
[i]
[j]
[k]
[l]
[m]
[n]
[o]
[p]
[q]
[r]
[s]
[t]
[u]
[v]
[w]
[x]
[y]
[z]><><><><ppp{,config,oe{,conf}}
[ppp]
[pppconfig]
[pppoe]
[pppoeconf]><><><><a{d,c,b}e
[ade]
[ace]
[abe]><><><><{9..11}
[9]
[10]
[11]><><><><{09..11}
[09]
[10]
[11]><><><><{a..9}
[{a..9}]><><><><a{1..2}b{2..3}c
[a1b2c]
[a1b3c]
[a2b2c]
[a2b3c]><><><><{1..2}{2..3}
[12]
[13]
[22]
[23]><><><><{0..8..2}
[0]
[2]
[4]
[6]
[8]><><><><{1..8..2}
[1]
[3]
[5]
[7]><><><><{3..-2}
[3]
[2]
[1]
[0]
[-1]
[-2]><><><><1{a..b}2{b..c}3
[1a2b3]
[1a2c3]
[1b2b3]
[1b2c3]><><><><{a..b}{b..c}
[ab]
[ac]
[bb]
[bc]><><><><{a..k..2}
[a]
[c]
[e]
[g]
[i]
[k]><><><><{b..k..2}
[b]
[d]
[f]
[h]
[j]><><><><
25 changes: 25 additions & 0 deletions test/cases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,28 @@ y{},a}x
{{},a}}b
{}{{},a}}b
{}a,b}c
{0000000000000000000000000000000000000000000001..10}
-v{,,,,}
{3..1}
{10..8}
{10..08}
{c..a}
{4..0..2}
{4..0..-2}
{e..a..2}
{a,b{1..3},c}
{{A..Z},{a..z}}
ppp{,config,oe{,conf}}
a{d,c,b}e
{9..11}
{09..11}
{a..9}
a{1..2}b{2..3}c
{1..2}{2..3}
{0..8..2}
{1..8..2}
{3..-2}
1{a..b}2{b..c}3
{a..b}{b..c}
{a..k..2}
{b..k..2}
6 changes: 3 additions & 3 deletions test/dollar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ var test = require('tape');
var expand = require('..');

test('ignores ${', function(t) {
t.deepEqual(expand('${1..3}'), ['${1..3}']);
t.deepEqual(expand('${a,b}${c,d}'), ['${a,b}${c,d}']);
t.deepEqual(expand('x${a,b}x${c,d}x'), ['x${a,b}x${c,d}x']);
t.deepEqual(expand('${1..3}'), ['${1..3}'], '${1..3}');
t.deepEqual(expand('${a,b}${c,d}'), ['${a,b}${c,d}'], '${a,b}${c,d}');
t.deepEqual(expand('x${a,b}x${c,d}x'), ['x${a,b}x${c,d}x'], 'x${a,b}x${c,d}x');
t.end();
});