Skip to content

Commit

Permalink
add standard
Browse files Browse the repository at this point in the history
  • Loading branch information
juliangruber committed Oct 7, 2023
1 parent 1eb3fa4 commit 3494c4d
Show file tree
Hide file tree
Showing 14 changed files with 5,283 additions and 890 deletions.
20 changes: 11 additions & 9 deletions bench/bench.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import expand from '..';
import fs from 'fs';
/* global bench */

const resfile = new URL('../test/cases.txt', import.meta.url);
const cases = fs.readFileSync(resfile, 'utf8').split('\n');
import expand from '..'
import fs from 'fs'

bench('Average', function() {
cases.forEach(function(testcase) {
expand(testcase);
});
});
const resfile = new URL('../test/cases.txt', import.meta.url)
const cases = fs.readFileSync(resfile, 'utf8').split('\n')

bench('Average', function () {
cases.forEach(function (testcase) {
expand(testcase)
})
})
11 changes: 5 additions & 6 deletions example.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import expand from './index.js'

console.log(expand('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html'));
console.log(expand('http://www.numericals.com/file{1..100..10}.txt'));
console.log(expand('http://www.letters.com/file{a..z..2}.txt'));
console.log(expand('mkdir /usr/local/src/bash/{old,new,dist,bugs}'));
console.log(expand('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}'));

console.log(expand('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html'))
console.log(expand('http://www.numericals.com/file{1..100..10}.txt'))
console.log(expand('http://www.letters.com/file{a..z..2}.txt'))
console.log(expand('mkdir /usr/local/src/bash/{old,new,dist,bugs}'))
console.log(expand('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}'))
188 changes: 90 additions & 98 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import balanced from 'balanced-match';
import balanced from 'balanced-match'

const escSlash = '\0SLASH'+Math.random()+'\0';
const escOpen = '\0OPEN'+Math.random()+'\0';
const escClose = '\0CLOSE'+Math.random()+'\0';
const escComma = '\0COMMA'+Math.random()+'\0';
const escPeriod = '\0PERIOD'+Math.random()+'\0';
const escSlash = '\0SLASH' + Math.random() + '\0'
const escOpen = '\0OPEN' + Math.random() + '\0'
const escClose = '\0CLOSE' + Math.random() + '\0'
const escComma = '\0COMMA' + Math.random() + '\0'
const escPeriod = '\0PERIOD' + Math.random() + '\0'

/**
* @return {number}
*/
function numeric(str) {
return parseInt(str, 10) == str
function numeric (str) {
return !isNaN(str)
? parseInt(str, 10)
: str.charCodeAt(0);
: str.charCodeAt(0)
}

/**
* @param {string} str
*/
function escapeBraces(str) {
function escapeBraces (str) {
return str.split('\\\\').join(escSlash)
.split('\\{').join(escOpen)
.split('\\}').join(escClose)
.split('\\,').join(escComma)
.split('\\.').join(escPeriod);
.split('\\{').join(escOpen)
.split('\\}').join(escClose)
.split('\\,').join(escComma)
.split('\\.').join(escPeriod)
}

/**
* @param {string} str
*/
function unescapeBraces(str) {
function unescapeBraces (str) {
return str.split(escSlash).join('\\')
.split(escOpen).join('{')
.split(escClose).join('}')
.split(escComma).join(',')
.split(escPeriod).join('.');
.split(escOpen).join('{')
.split(escClose).join('}')
.split(escComma).join(',')
.split(escPeriod).join('.')
}

/**
Expand All @@ -43,37 +43,34 @@ function unescapeBraces(str) {
* treated as individual members, like {a,{b,c},d}
* @param {string} str
*/
function parseCommaParts(str) {
if (!str)
return [''];
function parseCommaParts (str) {
if (!str) { return [''] }

const parts = [];
const m = balanced('{', '}', str);
const parts = []
const m = balanced('{', '}', str)

if (!m)
return str.split(',');
if (!m) { return str.split(',') }

const {pre, body, post} = m;
const p = pre.split(',');
const { pre, body, post } = m
const p = pre.split(',')

p[p.length-1] += '{' + body + '}';
const postParts = parseCommaParts(post);
p[p.length - 1] += '{' + body + '}'
const postParts = parseCommaParts(post)
if (post.length) {
p[p.length-1] += postParts.shift();
p.push.apply(p, postParts);
p[p.length - 1] += postParts.shift()
p.push.apply(p, postParts)
}

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

return parts;
return parts
}

/**
* @param {string} str
*/
export default function expandTop(str) {
if (!str)
return [];
export default function expandTop (str) {
if (!str) { return [] }

// I don't know why Bash 4.3 does this, but it does.
// Anything starting with {} will have the first two bytes preserved
Expand All @@ -82,152 +79,147 @@ export default function expandTop(str) {
// One could argue that this is a bug in Bash, but since the goal of
// this module is to match Bash's rules, we escape a leading {}
if (str.slice(0, 2) === '{}') {
str = '\\{\\}' + str.slice(2);
str = '\\{\\}' + str.slice(2)
}

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

/**
* @param {string} str
*/
function embrace(str) {
return '{' + str + '}';
function embrace (str) {
return '{' + str + '}'
}

/**
* @param {string} el
*/
function isPadded(el) {
return /^-?0\d/.test(el);
function isPadded (el) {
return /^-?0\d/.test(el)
}

/**
* @param {number} i
* @param {number} y
*/
function lte(i, y) {
return i <= y;
function lte (i, y) {
return i <= y
}

/**
* @param {number} i
* @param {number} y
*/
function gte(i, y) {
return i >= y;
function gte (i, y) {
return i >= y
}

/**
* @param {string} str
* @param {boolean} [isTop]
*/
function expand(str, isTop) {
function expand (str, isTop) {
/** @type {string[]} */
const expansions = [];
const expansions = []

const m = balanced('{', '}', str);
if (!m) return [str];
const m = balanced('{', '}', str)
if (!m) return [str]

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

if (/\$$/.test(m.pre)) {
for (let k = 0; k < post.length; k++) {
const expansion = pre+ '{' + m.body + '}' + post[k];
expansions.push(expansion);
const expansion = pre + '{' + m.body + '}' + post[k]
expansions.push(expansion)
}
} else {
const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
const isSequence = isNumericSequence || isAlphaSequence;
const isOptions = m.body.indexOf(',') >= 0;
const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body)
const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body)
const isSequence = isNumericSequence || isAlphaSequence
const isOptions = m.body.indexOf(',') >= 0
if (!isSequence && !isOptions) {
// {a},b}
if (m.post.match(/,.*\}/)) {
str = m.pre + '{' + m.body + escClose + m.post;
return expand(str);
str = m.pre + '{' + m.body + escClose + m.post
return expand(str)
}
return [str];
return [str]
}

let n;
let n
if (isSequence) {
n = m.body.split(/\.\./);
n = m.body.split(/\.\./)
} else {
n = parseCommaParts(m.body);
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], false).map(embrace)
if (n.length === 1) {
return post.map(function(p) {
return m.pre + n[0] + p;
});
return post.map(function (p) {
return m.pre + n[0] + p
})
}
}
}

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

if (isSequence) {
const x = numeric(n[0]);
const y = numeric(n[1]);
const x = numeric(n[0])
const y = numeric(n[1])
const width = Math.max(n[0].length, n[1].length)
let incr = n.length == 3
let incr = n.length === 3
? Math.abs(numeric(n[2]))
: 1;
let test = lte;
const reverse = y < x;
: 1
let test = lte
const reverse = y < x
if (reverse) {
incr *= -1;
test = gte;
incr *= -1
test = gte
}
const pad = n.some(isPadded);
const pad = n.some(isPadded)

N = [];
N = []

for (let i = x; test(i, y); i += incr) {
let c;
let c
if (isAlphaSequence) {
c = String.fromCharCode(i);
if (c === '\\')
c = '';
c = String.fromCharCode(i)
if (c === '\\') { c = '' }
} else {
c = String(i);
c = String(i)
if (pad) {
const need = width - c.length;
const need = width - c.length
if (need > 0) {
const z = new Array(need + 1).join('0');
if (i < 0)
c = '-' + z + c.slice(1);
else
c = z + c;
const z = new Array(need + 1).join('0')
if (i < 0) { c = '-' + z + c.slice(1) } else { c = z + c }
}
}
}
N.push(c);
N.push(c)
}
} else {
N = [];
N = []

for (let j = 0; j < n.length; j++) {
N.push.apply(N, expand(n[j], false));
N.push.apply(N, expand(n[j], false))
}
}

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

return expansions;
return expansions
}
Loading

0 comments on commit 3494c4d

Please sign in to comment.