l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

index.js (4792B)


      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