CoffeeScript や Javascript (node.js) でグロブ（ワイルドカード）が使える FileList

[ワイルドカードを正規表現に書き換えるやつ](http://qiita.com/mrpepper/items/5863d9bec7027ed93eb3)の続き。
とゆーわけで、CoffeeScript や Javascript (node.js) で、グロブが使える FileList。rubyのrakeで使えるのがどんなやつかよく知らないけど、とりあえず、**移植じゃないもーん**。（って、ちょっとドキュメント覗いてみたら、遅延評価な感じで、しかもちょこざいな機能が色々あるのね。まねしないぞ。……しないぞ！　絶対にな！）

asterisk \*, globstar \*\*, question ?, bracket [a-zA-Z], brace {foo|bar} が使えます。
* 第１引数はグロブの文字列（か、正規表現オブジェクトもOK）
* 第２引数は探索のベースとなるパス（省略すれば、カレントです）
* 返値はマッチするファイルのフルパス文字列のArray
ま、よしなに。

```coffeescript
arr = FileList("src/**/temp_*.{js,coffee}")
```

みたいな感じでさくっとディレクトリ走査。ま、GulpをCoffeeScriptに置き換える以外にも、これなら使い途がありましょう。

```CoffeeScript:FileList.coffee
fs = require('fs')

y_combi = (func) ->
  return ((p) ->
    return () ->
      return func(p(p)).apply(this,arguments)
  )((p) ->
    return () ->
      return func(p(p)).apply(this,arguments)
  )

braces2parentheses = y_combi (func) ->
  return (str) ->
    dst = str
    result = str.match(/^([^\{]*)\{([^\}]*)\}(.*)$/)
    if result?
      dst = result[1]+'('+result[2].split(',').join('|')+')'+func(result[3])
    return dst

wildcard2regexstr = (str) ->
  str = str.replace /\\/g,'\\\\'
  str = str.replace /\+/g,'\\+'
  str = str.replace /\./g,'\\.'
  str = str.replace /\(/g,'\\('
  str = str.replace /\)/g,'\\)'
  str = str.replace /\^/g,'\\^'
  str = str.replace /\$/g,'\\\$'
  str = str.replace /\|/g,'\\|'
  str = braces2parentheses str
  str = str.replace /\?/g,'[^/]'
  str = str.replace /<</g,'<<2'
  str = str.replace /<<</g,'<<3'
  str = str.replace /\/\*\*\//g,'(/.<<</|/)'
  str = str.replace /\*/g,'[^/]+'
  str = str.replace /<<</g,'*'
  str = str.replace /<<2/g,'<<'
  str = str.replace /<<3/g,'<<<'
  str += '$'

wildcard2regex = (str) ->
  return new RegExp(wildcard2regexstr str)

FileList_inside = y_combi (func) ->
  return (srcpath, regex, pathregex) ->
    arr = []
    if not fs.existsSync srcpath
      return -1
    filearray = fs.readdirSync srcpath
    for fileordir in filearray
      fname = srcpath+'/'+fileordir
      statresult = fs.statSync fname
      if statresult.isFile()
        if regex.test fname
          if pathregex.test fname
            arr.push(fname)
      else if statresult.isDirectory()
        arr = arr.concat func(fname,regex,pathregex)
    return arr

FileList = (pathname, basepath = null) ->
  judgetype = Object.prototype.toString.call(pathname).slice(8, -1)
  if not basepath?
    basepath = process.cwd()
  else if basepath == '.'
    basepath = process.cwd()
  else if /^\.\//.test basepath
    basepath = basepath.replace(/^\./,process.cwd())
  else if /^\.\./.test basepath
    basepath = process.cwd()+'/'+basepath
  while(/\/\.\./.test basepath)
    basepath = basepath.replace(/\/[^\/]+\/\.\./,'')
  basepath = basepath.replace(/\/$/,'')
  pathregex = basepath
  basepath = basepath.replace(/[\*\?\[\{].*$/,'')
  basepath = basepath.replace(/\/[^\/]*$/,'')
  pathregex = '^'+(wildcard2regexstr pathregex).replace(/\$$/,'')+'/'
  pathregex = new RegExp(pathregex)

  switch judgetype
    when 'RegExp'
      regex = pathname
    when 'String'
      regex = wildcard2regex pathname
    else
      console.log 'error'
      process.exit 1
  FileList_inside basepath,regex,pathregex

#test


for fname in FileList("*.coffee",'../../**/a??')
  console.log fname
```

JavaScript版。

```js:FileList.js
  var fs = require('fs');

  var y_combi = function(func) {
    return (function(p) {
      return function() {
        return func(p(p)).apply(this, arguments);
      };
    })(function(p) {
      return function() {
        return func(p(p)).apply(this, arguments);
      };
    });
  };

  var braces2parentheses = y_combi(function(func) {
    return function(str) {
      var dst, result;
      dst = str;
      result = str.match(/^([^\{]*)\{([^\}]*)\}(.*)$/);
      if (result != null) {
        dst = result[1] + '(' + result[2].split(',').join('|') + ')' + func(result[3]);
      }
      return dst;
    };
  });

  var wildcard2regexstr = function(str) {
    str = str.replace(/\\/g, '\\\\');
    str = str.replace(/\+/g, '\\+');
    str = str.replace(/\./g, '\\.');
    str = str.replace(/\(/g, '\\(');
    str = str.replace(/\)/g, '\\)');
    str = str.replace(/\^/g, '\\^');
    str = str.replace(/\$/g, '\\\$');
    str = str.replace(/\|/g, '\\|');
    str = braces2parentheses(str);
    str = str.replace(/\?/g, '[^/]');
    str = str.replace(/<</g, '<<2');
    str = str.replace(/<<</g, '<<3');
    str = str.replace(/\/\*\*\//g, '(/.<<</|/)');
    str = str.replace(/\*/g, '[^/]+');
    str = str.replace(/<<</g, '*');
    str = str.replace(/<<2/g, '<<');
    str = str.replace(/<<3/g, '<<<');
    return str += '$';
  };

  var wildcard2regex = function(str) {
    return new RegExp(wildcard2regexstr(str));
  };

  var FileList_inside = y_combi(function(func) {
    return function(srcpath, regex, pathregex) {
      var arr, filearray, fileordir, fname, i, len, statresult;
      arr = [];
      if (!fs.existsSync(srcpath)) {
        return -1;
      }
      filearray = fs.readdirSync(srcpath);
      for (i = 0, len = filearray.length; i < len; i++) {
        fileordir = filearray[i];
        fname = srcpath + '/' + fileordir;
        statresult = fs.statSync(fname);
        if (statresult.isFile()) {
          if (regex.test(fname)) {
            if (pathregex.test(fname)) {
              arr.push(fname);
            }
          }
        } else if (statresult.isDirectory()) {
          arr = arr.concat(func(fname, regex, pathregex));
        }
      }
      return arr;
    };
  });

  var FileList = function(pathname, basepath) {
    var judgetype, pathregex, regex;
    if (basepath == null) {
      basepath = null;
    }
    judgetype = Object.prototype.toString.call(pathname).slice(8, -1);
    if (basepath == null) {
      basepath = process.cwd();
    } else if (basepath === '.') {
      basepath = process.cwd();
    } else if (/^\.\//.test(basepath)) {
      basepath = basepath.replace(/^\./, process.cwd());
    } else if (/^\.\./.test(basepath)) {
      basepath = process.cwd() + '/' + basepath;
    }
    while (/\/\.\./.test(basepath)) {
      basepath = basepath.replace(/\/[^\/]+\/\.\./, '');
    }
    basepath = basepath.replace(/\/$/, '');
    pathregex = basepath;
    basepath = basepath.replace(/[\*\?\[\{].*$/, '');
    basepath = basepath.replace(/\/[^\/]*$/, '');
    pathregex = '^' + (wildcard2regexstr(pathregex)).replace(/\$$/, '') + '/';
    pathregex = new RegExp(pathregex);
    switch (judgetype) {
      case 'RegExp':
        regex = pathname;
        break;
      case 'String':
        regex = wildcard2regex(pathname);
        break;
      default:
        console.log('error');
        process.exit(1);
    }
    return FileList_inside(basepath, regex, pathregex);
  };

  var i,len;
  var ref = FileList("*.coffee", '../../**/a??');
  for (i = 0, len = ref.length; i < len; i++) {
    fname = ref[i];
    console.log(fname);
  }
```