Browse Source

V2: New config format, features.

Jakob Borg 12 years ago
parent
commit
5beb57c2d2
58 changed files with 7926 additions and 182 deletions
  1. 1 2
      .gitignore
  2. 7 78
      README.md
  3. 36 0
      install.sh
  4. 20 0
      lib/config.js
  5. 14 0
      lib/dsglob.js
  6. 23 0
      lib/dspattern.js
  7. 54 0
      lib/shellexp.js
  8. 58 0
      lib/snapshotset.js
  9. 58 0
      lib/zfs.js
  10. 19 0
      node_modules/async/LICENSE
  11. 1414 0
      node_modules/async/README.md
  12. 11 0
      node_modules/async/component.json
  13. 955 0
      node_modules/async/lib/async.js
  14. 46 0
      node_modules/async/package.json
  15. 2 0
      node_modules/cron/.npmignore
  16. 9 0
      node_modules/cron/.travis.yml
  17. 10 0
      node_modules/cron/Makefile
  18. 166 0
      node_modules/cron/README.md
  19. 382 0
      node_modules/cron/lib/cron.js
  20. 85 0
      node_modules/cron/package.json
  21. 403 0
      node_modules/cron/tests/test-cron.js
  22. 152 0
      node_modules/cron/tests/test-crontime.js
  23. 23 0
      node_modules/ini/LICENSE
  24. 79 0
      node_modules/ini/README.md
  25. 166 0
      node_modules/ini/ini.js
  26. 29 0
      node_modules/ini/package.json
  27. 23 0
      node_modules/ini/test/bar.js
  28. 47 0
      node_modules/ini/test/fixtures/foo.ini
  29. 71 0
      node_modules/ini/test/foo.js
  30. 23 0
      node_modules/minimatch/LICENSE
  31. 218 0
      node_modules/minimatch/README.md
  32. 1079 0
      node_modules/minimatch/minimatch.js
  33. 1 0
      node_modules/minimatch/node_modules/lru-cache/.npmignore
  34. 8 0
      node_modules/minimatch/node_modules/lru-cache/AUTHORS
  35. 23 0
      node_modules/minimatch/node_modules/lru-cache/LICENSE
  36. 97 0
      node_modules/minimatch/node_modules/lru-cache/README.md
  37. 257 0
      node_modules/minimatch/node_modules/lru-cache/lib/lru-cache.js
  38. 59 0
      node_modules/minimatch/node_modules/lru-cache/package.json
  39. 25 0
      node_modules/minimatch/node_modules/lru-cache/s.js
  40. 329 0
      node_modules/minimatch/node_modules/lru-cache/test/basic.js
  41. 52 0
      node_modules/minimatch/node_modules/lru-cache/test/foreach.js
  42. 50 0
      node_modules/minimatch/node_modules/lru-cache/test/memory-leak.js
  43. 27 0
      node_modules/minimatch/node_modules/sigmund/LICENSE
  44. 53 0
      node_modules/minimatch/node_modules/sigmund/README.md
  45. 283 0
      node_modules/minimatch/node_modules/sigmund/bench.js
  46. 38 0
      node_modules/minimatch/node_modules/sigmund/package.json
  47. 39 0
      node_modules/minimatch/node_modules/sigmund/sigmund.js
  48. 24 0
      node_modules/minimatch/node_modules/sigmund/test/basic.js
  49. 36 0
      node_modules/minimatch/package.json
  50. 399 0
      node_modules/minimatch/test/basic.js
  51. 33 0
      node_modules/minimatch/test/brace-expand.js
  52. 14 0
      node_modules/minimatch/test/caching.js
  53. 274 0
      node_modules/minimatch/test/defaults.js
  54. 1 1
      package.json
  55. 27 72
      zsnapper
  56. 92 0
      zsnapper.ini
  57. 0 27
      zsnapper.json.sample
  58. 2 2
      zsnapper.xml

+ 1 - 2
.gitignore

@@ -1,3 +1,2 @@
-smartos-zsnapper.tar.gz
-node_modules
 .build
 .build
+.idea

+ 7 - 78
README.md

@@ -17,91 +17,20 @@ Installation
 SmartOS
 SmartOS
 -------
 -------
 
 
-### Build the tar.gz
+    bash <(curl -sk https://raw.github.com/calmh/zsnapper/master/install.sh)
 
 
-Two alternatives:
-
- 1. Use my "compiled" version from http://nym.se/smartos/zsnapper.tar.gz
-
- 2.  In a zone that has npm and build tools available, or on a machine running
-     Mac OS X or whatever with Node.js installed:
-
-     ```
-     # git clone https://github.com/calmh/zsnapper.git
-     # cd zsnapper
-     # fakeroot make smartos
-     ```
-
-Transfer the tarball to your GZ and unpack it from the root. Read below about
-configuring (the config file is in `/opt/local/etc`), then enable the service
-with `svccfg import /opt/custom/smf/zsnapper.xml`. Check the log
-`/var/svc/log/site-zsnapper.log` for issues.
+This will install into /opt/local/zsnapper and drop default config and manifest
+into /opt/local/etc and /opt/custom/smf respectively. If the config is already
+present it won't be overwritten, so this can be used for upgrades as well.
 
 
 Non-SmartOS Solaris, Linux, or FreeBSD
 Non-SmartOS Solaris, Linux, or FreeBSD
 --------------------------------------
 --------------------------------------
 
 
     # npm -g install zsnapper
     # npm -g install zsnapper
 
 
-Configure
----------
-
-Copy the config file `zsnapper.json.sample` (probably installed in
-`/usr/local/lib/node_modules/zsnapper` by npm) to
-`/etc/zsnapper.json` and update it with your desired configuration.
-
-It's a JSON file of the format:
-
-    {
-        <snapshot name>: {
-            "when": <cron string>,
-            "count": <number of snapshots>,
-            "datasets": [ <dataset wildcard>, ... ]
-        },
-        <snapshot name>: {
-            "when": <cron string>,
-            "count": <number of snapshots>,
-            "datasets": [ <dataset wildcard>, ... ]
-        }
-    }
-
-Where:
-
-  - *snapshot name* is a base to build snapshot names of. The current date and
-    time will be appended. Example: `daily` which will result in snapshot names
-    of the type `daily-20120515T1314900Z`.
-
-  - *cron string* is a cron-format description of when the snapshot should be
-    taken. `man 5 crontab` for details. Example: `15 0 * * 1` for snapshots at
-    00:15 the first of every month.
-
-  - *number of snapshots* is the number of snapshots that should be kept
-    historically before being destroyed.
-
-  - *dataset wildcard* is a wildcard matching datasets to snapshot.
-    Example: `zones/*-*-*-*` to match datasets under `zones` that might
-    look like uuids.
-
-Start
------
-
-To test the setup, start the service (as root) with the name of the config file
-as the only parameter.
-
-    # /usr/local/bin/zsnapper /etc/zsnapper.json
-
-A better alternative, once everything seems to work as intended, is to use the
-accompanying SMF manifest. Copy `zsnapper.xml` from
-`/usr/local/lib/node_modules/zsnapper` to your home directory and edit it to
-suit your installation. The only things you need to modify are the paths to the
-`zsnapper` executable and the `zsnapper.json` config file. Then import it:
-
-    # svccfg import zsnapper.xml
-
-The service should be started automatically, which you can verify:
-
-    # svcs site/zsnapper
-    STATE          STIME    FMRI
-    online         10:56:26 svc:/site/zsnapper:default
+Copy the zsnapper.ini to a suitable place, start zsnapper with the location of
+the config file as the only parameter. For Solaris, there's an example SMF in
+zsnapper.xml.
 
 
 License
 License
 -------
 -------

+ 36 - 0
install.sh

@@ -0,0 +1,36 @@
+#!/bin/bash
+
+branch="master"
+tar="https://codeload.github.com/calmh/zsnapper/tar.gz/$branch"
+instdir="/opt/local"
+config="/opt/local/etc/zsnapper.ini"
+smf="/opt/custom/smf/zsnapper.xml"
+
+fail() {
+echo Installation failed
+exit -1
+}
+
+echo "Installing into $instdir/zsnapper."
+
+cd "$instdir" || fail
+[ -d zsnapper.previous ] && rm -rf zsnapper.previous
+[ -d zsnapper ] && mv zsnapper zsnapper.previous
+( curl -sk "$tar" | gtar zxf - ) || fail
+mv "zsnapper-$branch" zsnapper || fail
+
+if [ ! -f "$config" ] ; then
+echo "No config file, installing default into $config."
+cp zsnapper/zsnapper.ini "$config" || fail
+fi
+
+if [ ! -f "smf" ] ; then
+echo "No SMF manifest, installing into $smf."
+cp zsnapper/zsnapper.xml "$smf" || fail
+fi
+
+echo
+echo "Installation complete."
+echo " 1. Edit the config file $config to taste."
+echo " 2. Import the SMF manifest to start the service:"
+echo "    svccfg import $smf"

+ 20 - 0
lib/config.js

@@ -0,0 +1,20 @@
+var fs = require('fs');
+var ini = require('ini');
+
+module.exports = function (name) {
+    var data = fs.readFileSync(name, 'utf-8');
+    var rawConf = ini.parse(data);
+    var jobs = [];
+    Object.keys(rawConf.schedule).forEach(function (schedName) {
+        var job = rawConf.schedule[schedName];
+        var datasets = [];
+        job.dataset.forEach(function (dsName) {
+            datasets = datasets.concat(rawConf.datasets[dsName].match);
+        });
+        job.dataset = datasets;
+        job.tag = schedName;
+        jobs.push(job);
+    });
+    return jobs;
+}
+

+ 14 - 0
lib/dsglob.js

@@ -0,0 +1,14 @@
+"use strict";
+
+var minimatch = require('minimatch');
+
+var zfs = require('./zfs');
+
+module.exports = function (glob, cb) {
+    zfs.list(['-t', 'filesystem,volume'], function (err, datasets) {
+        if (err)
+            return cb(err);
+        var found = datasets.filter(minimatch.filter(glob));
+        cb(null, found);
+    });
+};

+ 23 - 0
lib/dspattern.js

@@ -0,0 +1,23 @@
+"use strict";
+
+var async = require('async');
+var dsglob = require('./dsglob.js');
+var shellexp = require('./shellexp.js');
+
+module.exports = function (pat, cb) {
+    shellexp(pat, function (err, res) {
+        if (err) {
+            return cb(err);
+        }
+
+        async.mapSeries(res, dsglob, function (err, res) {
+            if (err) {
+                return cb(err);
+            }
+
+            var merged = [];
+            merged = merged.concat.apply(merged, res);
+            cb(null, merged);
+        });
+    });
+};

+ 54 - 0
lib/shellexp.js

@@ -0,0 +1,54 @@
+"use strict";
+
+var async = require('async');
+var exec = require('child_process').exec;
+
+function compact(list) {
+    return list.filter(function (i) {
+        if (typeof i.length !== 'undefined') {
+            return i.length > 0;
+        }
+        return !!i;
+    });
+}
+
+function combine(string, repls) {
+    var pats = string.match(/\$\([^\)]+\)/g);
+    if (!pats) {
+        return [string];
+    }
+
+    var res = [];
+    var pat = pats[0];
+    var rep = repls[pat];
+    for (var i = 0; i < rep.length; i++) {
+        res = res.concat(combine(string.replace(pat, rep[i]), repls));
+    }
+
+    return res;
+}
+
+module.exports = function (string, cb) {
+    var pats = string.match(/\$\([^\)]+\)/g);
+    if (!pats) {
+        return cb(null, [string]);
+    }
+
+    var exps = {};
+    async.forEach(pats, function (pat, cb) {
+        var cmd = pat.slice(2, -1);
+        var res = exec(cmd, function (err, stdout, stderr) {
+            if (err) {
+                return cb(err);
+            }
+
+            exps[pat] = compact(stdout.toString().split('\n'));
+            cb(null);
+        });
+    }, function (err) {
+        if (err) {
+            return cb(err);
+        }
+        cb(null, combine(string, exps));
+    });
+};

+ 58 - 0
lib/snapshotset.js

@@ -0,0 +1,58 @@
+"use strict";
+
+var async = require('async');
+var util = require('util');
+
+var zfs = require('./zfs');
+
+function snapshotName(base) {
+    var when = (new Date()).toISOString().replace(/[-:]|(\.\d\d\d)/g, '');
+    return base + '-' + when;
+}
+
+function SnapshotSet(dataset, tag) {
+    this.dataset = dataset;
+    this.tag = tag;
+}
+
+SnapshotSet.prototype.snapshot = function (cb) {
+    var name = snapshotName(this.tag);
+    var snap = this.dataset + '@' + name;
+    util.log('snapshot ' + snap)
+    zfs(['snapshot', '-r', this.dataset + '@' + name], cb);
+};
+
+SnapshotSet.prototype.list = function (cb) {
+    var self = this;
+    var re = new RegExp(self.dataset + '@' + self.tag + '-');
+
+    zfs.list(['-t', 'snapshot', '-r', '-d', '1', self.dataset], function (err, snaps) {
+        if (err) {
+            return cb(err);
+        }
+
+        var names = snaps.filter(function (n) {
+            return n.match(re);
+        });
+        names.sort();
+        cb(null, names);
+    });
+};
+
+SnapshotSet.prototype.prune = function (num, cb) {
+    var self = this;
+
+    self.list(function (err, snaps) {
+        if (err) {
+            return cb(err);
+        }
+
+        var toDestroy = snaps.slice(0, snaps.length - num);
+        async.eachSeries(toDestroy, function (snap, cb) {
+            util.log('destroy ' + snap)
+            zfs(['destroy', '-r', snap], cb);
+        }, cb);
+    });
+};
+
+module.exports = SnapshotSet;

+ 58 - 0
lib/zfs.js

@@ -0,0 +1,58 @@
+"use strict";
+
+var execFile = require('child_process').execFile;
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+
+function compact(array) {
+    return array.filter(function (i) {
+        if (typeof i.length !== 'undefined')
+            return i.length > 0;
+        return !!i;
+    });
+}
+
+function findCmd(name) {
+    "use strict";
+
+    var paths = process.env['PATH'].split(':');
+    var pathLen = paths.length;
+    for (var i = 0; i < pathLen; i++) {
+        var sp = path.resolve(paths[i]);
+        var fname = path.normalize(path.join(sp, name));
+        if (fs.existsSync(fname)) {
+            return fname;
+        }
+    }
+
+    return null;
+}
+
+var zfsBin = findCmd('zfs');
+
+function zfs(args, callback) {
+    execFile(zfsBin, args, {maxBuffer: 8000000}, function (err, stdout, stderr) {
+        if (callback && typeof callback === 'function') {
+            if (err) {
+                err.message = compact(err.message.split('\n')).join('; ').trim();
+                callback(err);
+            } else {
+                callback(null, stdout);
+            }
+        }
+    });
+}
+
+zfs.list = function list(extraParams, cb) {
+    var params = ['list', '-H', '-o', 'name'].concat(extraParams || []);
+
+    zfs(params, function (err, stdout) {
+        if (cb && typeof cb === 'function') {
+            var lines = compact(stdout.split('\n'));
+            cb(err, lines);
+        }
+    });
+};
+
+module.exports = zfs;

+ 19 - 0
node_modules/async/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2010 Caolan McMahon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

File diff suppressed because it is too large
+ 1414 - 0
node_modules/async/README.md


+ 11 - 0
node_modules/async/component.json

@@ -0,0 +1,11 @@
+{
+  "name": "async",
+  "repo": "caolan/async",
+  "description": "Higher-order functions and common patterns for asynchronous code",
+  "version": "0.1.23",
+  "keywords": [],
+  "dependencies": {},
+  "development": {},
+  "main": "lib/async.js",
+  "scripts": [ "lib/async.js" ]
+}

+ 955 - 0
node_modules/async/lib/async.js

@@ -0,0 +1,955 @@
+/*global setImmediate: false, setTimeout: false, console: false */
+(function () {
+
+    var async = {};
+
+    // global on the server, window in the browser
+    var root, previous_async;
+
+    root = this;
+    if (root != null) {
+      previous_async = root.async;
+    }
+
+    async.noConflict = function () {
+        root.async = previous_async;
+        return async;
+    };
+
+    function only_once(fn) {
+        var called = false;
+        return function() {
+            if (called) throw new Error("Callback was already called.");
+            called = true;
+            fn.apply(root, arguments);
+        }
+    }
+
+    //// cross-browser compatiblity functions ////
+
+    var _each = function (arr, iterator) {
+        if (arr.forEach) {
+            return arr.forEach(iterator);
+        }
+        for (var i = 0; i < arr.length; i += 1) {
+            iterator(arr[i], i, arr);
+        }
+    };
+
+    var _map = function (arr, iterator) {
+        if (arr.map) {
+            return arr.map(iterator);
+        }
+        var results = [];
+        _each(arr, function (x, i, a) {
+            results.push(iterator(x, i, a));
+        });
+        return results;
+    };
+
+    var _reduce = function (arr, iterator, memo) {
+        if (arr.reduce) {
+            return arr.reduce(iterator, memo);
+        }
+        _each(arr, function (x, i, a) {
+            memo = iterator(memo, x, i, a);
+        });
+        return memo;
+    };
+
+    var _keys = function (obj) {
+        if (Object.keys) {
+            return Object.keys(obj);
+        }
+        var keys = [];
+        for (var k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                keys.push(k);
+            }
+        }
+        return keys;
+    };
+
+    //// exported async module functions ////
+
+    //// nextTick implementation with browser-compatible fallback ////
+    if (typeof process === 'undefined' || !(process.nextTick)) {
+        if (typeof setImmediate === 'function') {
+            async.nextTick = function (fn) {
+                // not a direct alias for IE10 compatibility
+                setImmediate(fn);
+            };
+            async.setImmediate = async.nextTick;
+        }
+        else {
+            async.nextTick = function (fn) {
+                setTimeout(fn, 0);
+            };
+            async.setImmediate = async.nextTick;
+        }
+    }
+    else {
+        async.nextTick = process.nextTick;
+        if (typeof setImmediate !== 'undefined') {
+            async.setImmediate = setImmediate;
+        }
+        else {
+            async.setImmediate = async.nextTick;
+        }
+    }
+
+    async.each = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        _each(arr, function (x) {
+            iterator(x, only_once(function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                }
+            }));
+        });
+    };
+    async.forEach = async.each;
+
+    async.eachSeries = function (arr, iterator, callback) {
+        callback = callback || function () {};
+        if (!arr.length) {
+            return callback();
+        }
+        var completed = 0;
+        var iterate = function () {
+            iterator(arr[completed], function (err) {
+                if (err) {
+                    callback(err);
+                    callback = function () {};
+                }
+                else {
+                    completed += 1;
+                    if (completed >= arr.length) {
+                        callback(null);
+                    }
+                    else {
+                        iterate();
+                    }
+                }
+            });
+        };
+        iterate();
+    };
+    async.forEachSeries = async.eachSeries;
+
+    async.eachLimit = function (arr, limit, iterator, callback) {
+        var fn = _eachLimit(limit);
+        fn.apply(null, [arr, iterator, callback]);
+    };
+    async.forEachLimit = async.eachLimit;
+
+    var _eachLimit = function (limit) {
+
+        return function (arr, iterator, callback) {
+            callback = callback || function () {};
+            if (!arr.length || limit <= 0) {
+                return callback();
+            }
+            var completed = 0;
+            var started = 0;
+            var running = 0;
+
+            (function replenish () {
+                if (completed >= arr.length) {
+                    return callback();
+                }
+
+                while (running < limit && started < arr.length) {
+                    started += 1;
+                    running += 1;
+                    iterator(arr[started - 1], function (err) {
+                        if (err) {
+                            callback(err);
+                            callback = function () {};
+                        }
+                        else {
+                            completed += 1;
+                            running -= 1;
+                            if (completed >= arr.length) {
+                                callback();
+                            }
+                            else {
+                                replenish();
+                            }
+                        }
+                    });
+                }
+            })();
+        };
+    };
+
+
+    var doParallel = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.each].concat(args));
+        };
+    };
+    var doParallelLimit = function(limit, fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [_eachLimit(limit)].concat(args));
+        };
+    };
+    var doSeries = function (fn) {
+        return function () {
+            var args = Array.prototype.slice.call(arguments);
+            return fn.apply(null, [async.eachSeries].concat(args));
+        };
+    };
+
+
+    var _asyncMap = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (err, v) {
+                results[x.index] = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, results);
+        });
+    };
+    async.map = doParallel(_asyncMap);
+    async.mapSeries = doSeries(_asyncMap);
+    async.mapLimit = function (arr, limit, iterator, callback) {
+        return _mapLimit(limit)(arr, iterator, callback);
+    };
+
+    var _mapLimit = function(limit) {
+        return doParallelLimit(limit, _asyncMap);
+    };
+
+    // reduce only has a series version, as doing reduce in parallel won't
+    // work in many situations.
+    async.reduce = function (arr, memo, iterator, callback) {
+        async.eachSeries(arr, function (x, callback) {
+            iterator(memo, x, function (err, v) {
+                memo = v;
+                callback(err);
+            });
+        }, function (err) {
+            callback(err, memo);
+        });
+    };
+    // inject alias
+    async.inject = async.reduce;
+    // foldl alias
+    async.foldl = async.reduce;
+
+    async.reduceRight = function (arr, memo, iterator, callback) {
+        var reversed = _map(arr, function (x) {
+            return x;
+        }).reverse();
+        async.reduce(reversed, memo, iterator, callback);
+    };
+    // foldr alias
+    async.foldr = async.reduceRight;
+
+    var _filter = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.filter = doParallel(_filter);
+    async.filterSeries = doSeries(_filter);
+    // select alias
+    async.select = async.filter;
+    async.selectSeries = async.filterSeries;
+
+    var _reject = function (eachfn, arr, iterator, callback) {
+        var results = [];
+        arr = _map(arr, function (x, i) {
+            return {index: i, value: x};
+        });
+        eachfn(arr, function (x, callback) {
+            iterator(x.value, function (v) {
+                if (!v) {
+                    results.push(x);
+                }
+                callback();
+            });
+        }, function (err) {
+            callback(_map(results.sort(function (a, b) {
+                return a.index - b.index;
+            }), function (x) {
+                return x.value;
+            }));
+        });
+    };
+    async.reject = doParallel(_reject);
+    async.rejectSeries = doSeries(_reject);
+
+    var _detect = function (eachfn, arr, iterator, main_callback) {
+        eachfn(arr, function (x, callback) {
+            iterator(x, function (result) {
+                if (result) {
+                    main_callback(x);
+                    main_callback = function () {};
+                }
+                else {
+                    callback();
+                }
+            });
+        }, function (err) {
+            main_callback();
+        });
+    };
+    async.detect = doParallel(_detect);
+    async.detectSeries = doSeries(_detect);
+
+    async.some = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (v) {
+                    main_callback(true);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(false);
+        });
+    };
+    // any alias
+    async.any = async.some;
+
+    async.every = function (arr, iterator, main_callback) {
+        async.each(arr, function (x, callback) {
+            iterator(x, function (v) {
+                if (!v) {
+                    main_callback(false);
+                    main_callback = function () {};
+                }
+                callback();
+            });
+        }, function (err) {
+            main_callback(true);
+        });
+    };
+    // all alias
+    async.all = async.every;
+
+    async.sortBy = function (arr, iterator, callback) {
+        async.map(arr, function (x, callback) {
+            iterator(x, function (err, criteria) {
+                if (err) {
+                    callback(err);
+                }
+                else {
+                    callback(null, {value: x, criteria: criteria});
+                }
+            });
+        }, function (err, results) {
+            if (err) {
+                return callback(err);
+            }
+            else {
+                var fn = function (left, right) {
+                    var a = left.criteria, b = right.criteria;
+                    return a < b ? -1 : a > b ? 1 : 0;
+                };
+                callback(null, _map(results.sort(fn), function (x) {
+                    return x.value;
+                }));
+            }
+        });
+    };
+
+    async.auto = function (tasks, callback) {
+        callback = callback || function () {};
+        var keys = _keys(tasks);
+        if (!keys.length) {
+            return callback(null);
+        }
+
+        var results = {};
+
+        var listeners = [];
+        var addListener = function (fn) {
+            listeners.unshift(fn);
+        };
+        var removeListener = function (fn) {
+            for (var i = 0; i < listeners.length; i += 1) {
+                if (listeners[i] === fn) {
+                    listeners.splice(i, 1);
+                    return;
+                }
+            }
+        };
+        var taskComplete = function () {
+            _each(listeners.slice(0), function (fn) {
+                fn();
+            });
+        };
+
+        addListener(function () {
+            if (_keys(results).length === keys.length) {
+                callback(null, results);
+                callback = function () {};
+            }
+        });
+
+        _each(keys, function (k) {
+            var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
+            var taskCallback = function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (args.length <= 1) {
+                    args = args[0];
+                }
+                if (err) {
+                    var safeResults = {};
+                    _each(_keys(results), function(rkey) {
+                        safeResults[rkey] = results[rkey];
+                    });
+                    safeResults[k] = args;
+                    callback(err, safeResults);
+                    // stop subsequent errors hitting callback multiple times
+                    callback = function () {};
+                }
+                else {
+                    results[k] = args;
+                    async.setImmediate(taskComplete);
+                }
+            };
+            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+            var ready = function () {
+                return _reduce(requires, function (a, x) {
+                    return (a && results.hasOwnProperty(x));
+                }, true) && !results.hasOwnProperty(k);
+            };
+            if (ready()) {
+                task[task.length - 1](taskCallback, results);
+            }
+            else {
+                var listener = function () {
+                    if (ready()) {
+                        removeListener(listener);
+                        task[task.length - 1](taskCallback, results);
+                    }
+                };
+                addListener(listener);
+            }
+        });
+    };
+
+    async.waterfall = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor !== Array) {
+          var err = new Error('First argument to waterfall must be an array of functions');
+          return callback(err);
+        }
+        if (!tasks.length) {
+            return callback();
+        }
+        var wrapIterator = function (iterator) {
+            return function (err) {
+                if (err) {
+                    callback.apply(null, arguments);
+                    callback = function () {};
+                }
+                else {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    var next = iterator.next();
+                    if (next) {
+                        args.push(wrapIterator(next));
+                    }
+                    else {
+                        args.push(callback);
+                    }
+                    async.setImmediate(function () {
+                        iterator.apply(null, args);
+                    });
+                }
+            };
+        };
+        wrapIterator(async.iterator(tasks))();
+    };
+
+    var _parallel = function(eachfn, tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            eachfn.map(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            eachfn.each(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.parallel = function (tasks, callback) {
+        _parallel({ map: async.map, each: async.each }, tasks, callback);
+    };
+
+    async.parallelLimit = function(tasks, limit, callback) {
+        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+    };
+
+    async.series = function (tasks, callback) {
+        callback = callback || function () {};
+        if (tasks.constructor === Array) {
+            async.mapSeries(tasks, function (fn, callback) {
+                if (fn) {
+                    fn(function (err) {
+                        var args = Array.prototype.slice.call(arguments, 1);
+                        if (args.length <= 1) {
+                            args = args[0];
+                        }
+                        callback.call(null, err, args);
+                    });
+                }
+            }, callback);
+        }
+        else {
+            var results = {};
+            async.eachSeries(_keys(tasks), function (k, callback) {
+                tasks[k](function (err) {
+                    var args = Array.prototype.slice.call(arguments, 1);
+                    if (args.length <= 1) {
+                        args = args[0];
+                    }
+                    results[k] = args;
+                    callback(err);
+                });
+            }, function (err) {
+                callback(err, results);
+            });
+        }
+    };
+
+    async.iterator = function (tasks) {
+        var makeCallback = function (index) {
+            var fn = function () {
+                if (tasks.length) {
+                    tasks[index].apply(null, arguments);
+                }
+                return fn.next();
+            };
+            fn.next = function () {
+                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+            };
+            return fn;
+        };
+        return makeCallback(0);
+    };
+
+    async.apply = function (fn) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return function () {
+            return fn.apply(
+                null, args.concat(Array.prototype.slice.call(arguments))
+            );
+        };
+    };
+
+    var _concat = function (eachfn, arr, fn, callback) {
+        var r = [];
+        eachfn(arr, function (x, cb) {
+            fn(x, function (err, y) {
+                r = r.concat(y || []);
+                cb(err);
+            });
+        }, function (err) {
+            callback(err, r);
+        });
+    };
+    async.concat = doParallel(_concat);
+    async.concatSeries = doSeries(_concat);
+
+    async.whilst = function (test, iterator, callback) {
+        if (test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.whilst(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doWhilst = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (test()) {
+                async.doWhilst(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.until = function (test, iterator, callback) {
+        if (!test()) {
+            iterator(function (err) {
+                if (err) {
+                    return callback(err);
+                }
+                async.until(test, iterator, callback);
+            });
+        }
+        else {
+            callback();
+        }
+    };
+
+    async.doUntil = function (iterator, test, callback) {
+        iterator(function (err) {
+            if (err) {
+                return callback(err);
+            }
+            if (!test()) {
+                async.doUntil(iterator, test, callback);
+            }
+            else {
+                callback();
+            }
+        });
+    };
+
+    async.queue = function (worker, concurrency) {
+        if (concurrency === undefined) {
+            concurrency = 1;
+        }
+        function _insert(q, data, pos, callback) {
+          if(data.constructor !== Array) {
+              data = [data];
+          }
+          _each(data, function(task) {
+              var item = {
+                  data: task,
+                  callback: typeof callback === 'function' ? callback : null
+              };
+
+              if (pos) {
+                q.tasks.unshift(item);
+              } else {
+                q.tasks.push(item);
+              }
+
+              if (q.saturated && q.tasks.length === concurrency) {
+                  q.saturated();
+              }
+              async.setImmediate(q.process);
+          });
+        }
+
+        var workers = 0;
+        var q = {
+            tasks: [],
+            concurrency: concurrency,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+              _insert(q, data, false, callback);
+            },
+            unshift: function (data, callback) {
+              _insert(q, data, true, callback);
+            },
+            process: function () {
+                if (workers < q.concurrency && q.tasks.length) {
+                    var task = q.tasks.shift();
+                    if (q.empty && q.tasks.length === 0) {
+                        q.empty();
+                    }
+                    workers += 1;
+                    var next = function () {
+                        workers -= 1;
+                        if (task.callback) {
+                            task.callback.apply(task, arguments);
+                        }
+                        if (q.drain && q.tasks.length + workers === 0) {
+                            q.drain();
+                        }
+                        q.process();
+                    };
+                    var cb = only_once(next);
+                    worker(task.data, cb);
+                }
+            },
+            length: function () {
+                return q.tasks.length;
+            },
+            running: function () {
+                return workers;
+            }
+        };
+        return q;
+    };
+
+    async.cargo = function (worker, payload) {
+        var working     = false,
+            tasks       = [];
+
+        var cargo = {
+            tasks: tasks,
+            payload: payload,
+            saturated: null,
+            empty: null,
+            drain: null,
+            push: function (data, callback) {
+                if(data.constructor !== Array) {
+                    data = [data];
+                }
+                _each(data, function(task) {
+                    tasks.push({
+                        data: task,
+                        callback: typeof callback === 'function' ? callback : null
+                    });
+                    if (cargo.saturated && tasks.length === payload) {
+                        cargo.saturated();
+                    }
+                });
+                async.setImmediate(cargo.process);
+            },
+            process: function process() {
+                if (working) return;
+                if (tasks.length === 0) {
+                    if(cargo.drain) cargo.drain();
+                    return;
+                }
+
+                var ts = typeof payload === 'number'
+                            ? tasks.splice(0, payload)
+                            : tasks.splice(0);
+
+                var ds = _map(ts, function (task) {
+                    return task.data;
+                });
+
+                if(cargo.empty) cargo.empty();
+                working = true;
+                worker(ds, function () {
+                    working = false;
+
+                    var args = arguments;
+                    _each(ts, function (data) {
+                        if (data.callback) {
+                            data.callback.apply(null, args);
+                        }
+                    });
+
+                    process();
+                });
+            },
+            length: function () {
+                return tasks.length;
+            },
+            running: function () {
+                return working;
+            }
+        };
+        return cargo;
+    };
+
+    var _console_fn = function (name) {
+        return function (fn) {
+            var args = Array.prototype.slice.call(arguments, 1);
+            fn.apply(null, args.concat([function (err) {
+                var args = Array.prototype.slice.call(arguments, 1);
+                if (typeof console !== 'undefined') {
+                    if (err) {
+                        if (console.error) {
+                            console.error(err);
+                        }
+                    }
+                    else if (console[name]) {
+                        _each(args, function (x) {
+                            console[name](x);
+                        });
+                    }
+                }
+            }]));
+        };
+    };
+    async.log = _console_fn('log');
+    async.dir = _console_fn('dir');
+    /*async.info = _console_fn('info');
+    async.warn = _console_fn('warn');
+    async.error = _console_fn('error');*/
+
+    async.memoize = function (fn, hasher) {
+        var memo = {};
+        var queues = {};
+        hasher = hasher || function (x) {
+            return x;
+        };
+        var memoized = function () {
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            var key = hasher.apply(null, args);
+            if (key in memo) {
+                callback.apply(null, memo[key]);
+            }
+            else if (key in queues) {
+                queues[key].push(callback);
+            }
+            else {
+                queues[key] = [callback];
+                fn.apply(null, args.concat([function () {
+                    memo[key] = arguments;
+                    var q = queues[key];
+                    delete queues[key];
+                    for (var i = 0, l = q.length; i < l; i++) {
+                      q[i].apply(null, arguments);
+                    }
+                }]));
+            }
+        };
+        memoized.memo = memo;
+        memoized.unmemoized = fn;
+        return memoized;
+    };
+
+    async.unmemoize = function (fn) {
+      return function () {
+        return (fn.unmemoized || fn).apply(null, arguments);
+      };
+    };
+
+    async.times = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.map(counter, iterator, callback);
+    };
+
+    async.timesSeries = function (count, iterator, callback) {
+        var counter = [];
+        for (var i = 0; i < count; i++) {
+            counter.push(i);
+        }
+        return async.mapSeries(counter, iterator, callback);
+    };
+
+    async.compose = function (/* functions... */) {
+        var fns = Array.prototype.reverse.call(arguments);
+        return function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            async.reduce(fns, args, function (newargs, fn, cb) {
+                fn.apply(that, newargs.concat([function () {
+                    var err = arguments[0];
+                    var nextargs = Array.prototype.slice.call(arguments, 1);
+                    cb(err, nextargs);
+                }]))
+            },
+            function (err, results) {
+                callback.apply(that, [err].concat(results));
+            });
+        };
+    };
+
+    var _applyEach = function (eachfn, fns /*args...*/) {
+        var go = function () {
+            var that = this;
+            var args = Array.prototype.slice.call(arguments);
+            var callback = args.pop();
+            return eachfn(fns, function (fn, cb) {
+                fn.apply(that, args.concat([cb]));
+            },
+            callback);
+        };
+        if (arguments.length > 2) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            return go.apply(this, args);
+        }
+        else {
+            return go;
+        }
+    };
+    async.applyEach = doParallel(_applyEach);
+    async.applyEachSeries = doSeries(_applyEach);
+
+    async.forever = function (fn, callback) {
+        function next(err) {
+            if (err) {
+                if (callback) {
+                    return callback(err);
+                }
+                throw err;
+            }
+            fn(next);
+        }
+        next();
+    };
+
+    // AMD / RequireJS
+    if (typeof define !== 'undefined' && define.amd) {
+        define([], function () {
+            return async;
+        });
+    }
+    // Node.js
+    else if (typeof module !== 'undefined' && module.exports) {
+        module.exports = async;
+    }
+    // included directly via <script> tag
+    else {
+        root.async = async;
+    }
+
+}());

File diff suppressed because it is too large
+ 46 - 0
node_modules/async/package.json


+ 2 - 0
node_modules/cron/.npmignore

@@ -0,0 +1,2 @@
+*.sw[a-z]
+node_modules

+ 9 - 0
node_modules/cron/.travis.yml

@@ -0,0 +1,9 @@
+language: node_js
+node_js:
+  - 0.4
+  - 0.6
+  - 0.7
+  - 0.8
+notifications:
+  email:
+    on_success: never

+ 10 - 0
node_modules/cron/Makefile

@@ -0,0 +1,10 @@
+TESTS = tests/*.js
+
+all: test
+
+test:
+	npm install .
+	@./node_modules/nodeunit/bin/nodeunit \
+		$(TESTS)
+
+.PHONY: test

+ 166 - 0
node_modules/cron/README.md

@@ -0,0 +1,166 @@
+node-cron
+=========
+
+[![Build Status](https://secure.travis-ci.org/ncb000gt/node-cron.png)](http://travis-ci.org/#!/ncb000gt/node-cron) 
+
+Originally this project was a NodeJS fork of [James Padolsey's][jamespadolsey] [cron.js](http://github.com/padolsey/cron.js).
+
+After [Craig Condon][crcn] made some updates and changes to the code base this has evolved to something that has a bit of both. The cron syntax parsing is mostly James' while using timeout instead of interval is Craig's.
+
+Additionally, this library goes beyond the basic cron syntax and allows you to supply a Date object. This will be used as the trigger for your callback. Cron syntax is still an acceptable CronTime format. Although the Cron patterns suported here extend on the standard Unix format to support seconds digits, leaving it off will default to 0 and match the Unix behavior.
+
+If You Are Submitting Bugs/Issues
+=============
+
+Because we can't magically know what you are doing to expose an issue, it is best if you provide a snippet of code. This snippet need not include your secret sauce, but it must replicate the issue you are describing. The issues that get closed without resolution tend to be the ones without code examples. Thanks.
+
+
+Versions and Backwards compatability breaks:
+==========
+
+As goes with semver, breaking backwards compatibility should be explicit in the versioning of your library. As such, we'll upgrade the version of this module in accordance with breaking changes (I'm not always great about doing it this way so if you notice that there are breaking changes that haven't been bumped appropriately please let me know). This table lists out the issues which were the reason for the break in backward compatibility.
+
+<table>
+<tr>
+<td>Node Cron Ver</td><td>Issue #</td>
+</tr>
+<tr>
+<td>1.0.0</td><td><ul><li><a href="https://github.com/ncb000gt/node-cron/pull/41">GH-41</a></li><li><a href="https://github.com/ncb000gt/node-cron/pull/36">GH-36</a></li></ul></td>
+</tr>
+</table>
+
+
+Usage (basic cron usage):
+==========
+
+    var cronJob = require('cron').CronJob;
+    new cronJob('* * * * * *', function(){
+        console.log('You will see this message every second');
+    }, null, true, "America/Los_Angeles");
+    
+
+Available Cron patterns:
+==========
+
+    Asterisk. E.g. *
+    Ranges. E.g. 1-3,5
+    Steps. E.g. */2
+    
+[Read up on cron patterns here](http://crontab.org).
+
+Another cron example
+==========
+
+    var cronJob = require('cron').CronJob;
+    var job = new cronJob('00 30 11 * * 1-5', function(){
+        // Runs every weekday (Monday through Friday)
+        // at 11:30:00 AM. It does not run on Saturday
+        // or Sunday.
+      }, function () {
+        // This function is executed when the job stops
+      }, 
+      true /* Start the job right now */,
+      timeZone /* Time zone of this job. */
+    );
+
+Another example with Date
+==========
+
+    var cronJob = require('cron').CronJob;
+    var job = new cronJob(new Date(), function(){
+        //runs once at the specified date.
+      }, function () {
+        // This function is executed when the job stops
+      }, 
+      true /* Start the job right now */,
+      timeZone /* Time zone of this job. */
+    );
+
+For good measure
+==========
+
+    var cronJob = require('cron').CronJob;
+    var job = new cronJob({
+      cronTime: '00 30 11 * * 1-5',
+      onTick: function() {
+        // Runs every weekday (Monday through Friday)
+        // at 11:30:00 AM. It does not run on Saturday
+        // or Sunday.
+      },
+      start: false,
+      timeZone: "America/Los_Angeles"
+    });
+    job.start();
+
+
+How to check if a cron pattern is valid:
+==========
+
+		try {
+			new cronJob('invalid cron pattern', function() {
+				console.log('this should not be printed');
+			})
+		} catch(ex) {
+			console.log("cron pattern not valid");
+		}
+
+
+Install
+==========
+
+    From source: `npm install`
+    From npm: `npm install cron`
+
+If you want to specify timezones, you'll need to install the [time](https://github.com/TooTallNate/node-time) module or place an entry for it in your package.json file.
+
+    `npm install time`
+
+
+API
+==========
+
+Parameter Based
+
+`CronJob`
+
+  * `constructor(cronTime, onTick, onComplete, start, timezone, context)` - Of note, the first parameter here can be a JSON object that has the below names and associated types (see examples above).
+    * `cronTime` - [REQUIRED] - The time to fire off your job. This can be in the form of cron syntax or a JS [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date) object.
+    * `onTick` - [REQUIRED] - The function to fire at the specified time.
+    * `onComplete` - [OPTIONAL] - A function that will fire when the job is complete, when it is stopped.
+    * `start` - [OPTIONAL] - Specifies whether to start the job after just before exiting the constructor.
+    * `timezone` - [OPTIONAL] - Specify the timezone for the execution. This will modify the actual time relative to your timezone.
+    * `context` - [OPTIONAL] - The context within which to execute the onTick method. This defaults to the cronjob itself allowing you to call `this.stop()`. However, if you change this you'll have access to the functions and values within your context object.
+  * `start` - Runs your job.
+  * `stop` - Stops your job.
+
+`CronTime`
+
+  * `constructor(time)`
+    * `time` - [REQUIRED] - The time to fire off your job. This can be in the form of cron syntax or a JS [Date](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date) object.
+
+Contributors
+===========
+
+* [Romain Beauxis][toots]
+* [James Padolsey][jamespadolsey]
+* [Craig Condon][crcn]
+* [Finn Herpich][errorprone]
+* [cliftonc][cliftonc]
+* [neyric][neyric]
+* [humanchimp][humanchimp]
+* [danhbear][danhbear]
+
+License
+==========
+
+MIT
+
+
+[toots]:http://github.com/toots
+[jamespadolsey]:http://github.com/padolsey
+[crcn]:http://github.com/crcn
+[cliftonc]:http://github.com/cliftonc
+[neyric]:http://github.com/neyric
+[humanchimp]:http://github.com/humanchimp
+[errorprone]:http://github.com/ErrorProne
+[danhbear]:http://github.com/danhbear

+ 382 - 0
node_modules/cron/lib/cron.js

@@ -0,0 +1,382 @@
+var CronDate = Date;
+try {
+  CronDate = require("time").Date;
+} catch(e) {
+  //no time module...leave CronDate alone. :)
+}
+
+
+function CronTime(source, zone) {
+  this.source = source;
+  this.zone   = zone;
+
+  this.second     = {};
+  this.minute     = {};
+  this.hour       = {};
+  this.dayOfWeek  = {};
+  this.dayOfMonth = {};
+  this.month      = {};
+
+  if ((this.source instanceof Date) || (this.source instanceof CronDate)) {
+    this.source = new CronDate(this.source);
+    this.realDate = true;
+  } else {
+    this._parse();
+  }
+};
+
+CronTime.map = ['second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek'];
+CronTime.constraints = [ [0, 59], [0, 59], [0, 23], [1, 31], [0, 11], [0, 6] ];
+CronTime.parseDefaults = [ '0', '*', '*', '*', '*', '*' ];
+CronTime.aliases = {
+    jan:0, feb:1, mar:2, apr:3, may:4, jun:5, jul:6, aug:7, sep:8, oct:9, nov:10, dec:11,
+    sun:0, mon:1, tue:2, wed:3, thu:4, fri:5, sat:6
+};
+
+
+CronTime.prototype = {
+  /**
+   * calculates the next send time
+   */
+  sendAt: function() {
+    var date = (this.source instanceof CronDate) ? this.source : new CronDate();
+    if (this.zone && date.setTimezone)
+      date.setTimezone(this.zone);
+    
+    //add 1 second so next time isn't now (can cause timeout to be 0)
+    if (!(this.realDate)) date.setSeconds(date.getSeconds() + 1);
+    
+    if (this.realDate) {
+      return date;
+    }
+    return this._getNextDateFrom(date);
+  },
+
+  /**
+   * Get the number of seconds in the future at which to fire our callbacks.
+   */
+  getTimeout: function() {
+    return Math.max(-1, this.sendAt().getTime() - CronDate.now());
+  },
+
+  /** 
+   * writes out a cron string
+   */
+  toString: function() {
+    return this.toJSON().join(' ');
+  },
+
+  /**
+   * Json representation of the parsed cron syntax.
+   */
+  toJSON: function() {
+    return [
+      this._wcOrAll('second'), 
+      this._wcOrAll('minute'), 
+      this._wcOrAll('hour'), 
+      this._wcOrAll('dayOfMonth'), 
+      this._wcOrAll('month'), 
+      this._wcOrAll('dayOfWeek')
+    ];
+  },
+
+  /**
+   * get next date that matches parsed cron time
+   */
+  _getNextDateFrom: function(start) {
+    var date = new CronDate(start);
+    if (this.zone && date.setTimezone)
+      date.setTimezone(start.getTimezone());
+    
+    //sanity check
+    var i = 1000;
+    while(--i) {
+      var diff = date - start;
+      
+      if (!(date.getMonth() in this.month)) {
+        date.setMonth(date.getMonth()+1);
+        date.setDate(1);
+        date.setHours(0);
+        date.setMinutes(0);
+        continue;
+      }
+
+      if (!(date.getDate() in this.dayOfMonth)) {
+        date.setDate(date.getDate()+1);
+        date.setHours(0);
+        date.setMinutes(0);
+        continue;
+      }
+
+      if (!(date.getDay() in this.dayOfWeek)) {
+        date.setDate(date.getDate()+1);
+        date.setHours(0);
+        date.setMinutes(0);
+        continue;
+      }
+      
+      if (!(date.getHours() in this.hour)) {
+        date.setHours(date.getHours() == 23 && diff > 24*60*60*1000 ? 0 : date.getHours()+1);
+        date.setMinutes(0);
+        continue;
+      }
+      
+      if (!(date.getMinutes() in this.minute)) {
+        date.setMinutes(date.getMinutes() == 59 && diff > 60*60*1000 ? 0 : date.getMinutes()+1);
+        date.setSeconds(0);
+        continue;
+      }
+      
+      if (!(date.getSeconds() in this.second)) {
+        date.setSeconds(date.getSeconds() == 59 && diff > 60*1000 ? 0 : date.getSeconds()+1);
+        continue;
+      }
+      
+      break;
+    }
+    
+    return date;
+  },
+
+  /**
+   * wildcard, or all params in array (for to string)
+   */
+  _wcOrAll: function(type) {
+    if(this._hasAll(type)) return '*';
+
+    var all = [];
+    for(var time in this[type]) {
+      all.push(time);
+    }
+
+    return all.join(',');
+  },
+
+  /**
+   */
+  _hasAll: function(type) {
+    var constrain = CronTime.constraints[CronTime.map.indexOf(type)];
+
+    for(var i = constrain[0], n = constrain[1]; i < n; i++) {
+      if(!(i in this[type])) return false;
+    }
+
+    return true;
+  },
+
+
+  /**
+   * Parse the cron syntax.
+   */
+  _parse: function() {
+    var aliases = CronTime.aliases,
+    source = this.source.replace(/[a-z]{1,3}/ig, function(alias){
+      alias = alias.toLowerCase();
+
+      if (alias in aliases) {
+        return aliases[alias];
+      }
+
+      throw new Error('Unknown alias: ' + alias);
+    }),
+    split = source.replace(/^\s\s*|\s\s*$/g, '').split(/\s+/),
+    cur, i = 0, len = CronTime.map.length;
+
+    for (; i < CronTime.map.length; i++) {
+      // If the split source string doesn't contain all digits, 
+      // assume defaults for first n missing digits.
+      // This adds support for 5-digit standard cron syntax
+      cur = split[i - (len - split.length)] || CronTime.parseDefaults[i];
+      this._parseField(cur, CronTime.map[i], CronTime.constraints[i]);
+    }
+  },
+
+  /**
+   * Parse a field from the cron syntax.
+   */
+  _parseField: function(field, type, constraints) {
+    var rangePattern = /(\d+?)(?:-(\d+?))?(?:\/(\d+?))?(?:,|$)/g,
+    typeObj = this[type],
+    diff, pointer,
+    low = constraints[0],
+    high = constraints[1];
+
+    // * is a shortcut to [lower-upper] range
+    field = field.replace(/\*/g,  low + '-' + high);
+
+    if (field.match(rangePattern)) {
+      field.replace(rangePattern, function($0, lower, upper, step) {
+                      step = parseInt(step) || 1;
+
+                      // Positive integer higher than constraints[0]
+                      lower = Math.max(low, ~~Math.abs(lower));
+
+                      // Positive integer lower than constraints[1]
+                      upper = upper ? Math.min(high, ~~Math.abs(upper)) : lower;
+
+                      // Count from the lower barrier to the upper
+                      pointer = lower;
+
+                      do {
+                          typeObj[pointer] = true
+                          pointer += step;
+                      } while(pointer <= upper);
+
+                    });
+    } else {
+      throw new Error('Field (' + field + ') cannot be parsed');
+    }
+  }
+};
+
+
+
+function CronJob(cronTime, onTick, onComplete, start, timeZone, context) {
+  if (typeof cronTime != "string" && arguments.length == 1) {
+    //crontime is an object...
+    onTick = cronTime.onTick;
+    onComplete = cronTime.onComplete;
+    context = cronTime.context;
+    start = cronTime.start;
+    timeZone = cronTime.timeZone;
+    cronTime = cronTime.cronTime;
+  }
+
+  if (timeZone && !(CronDate.prototype.setTimezone)) console.log('You specified a Timezone but have not included the `time` module. Timezone functionality is disabled. Please install the `time` module to use Timezones in your application.');
+
+  this.context = (context || this);
+  this._callbacks = [];
+  this.onComplete = onComplete;
+  this.cronTime   = new CronTime(cronTime, timeZone);
+
+  this.addCallback(onTick);
+
+  if (start) this.start();
+
+  return this;
+}
+
+CronJob.prototype = {
+  /**
+   * Add a method to fire onTick
+   */
+  addCallback: function(callback) {
+    //only functions
+    if(typeof callback == 'function') this._callbacks.push(callback);
+  },
+
+  /**
+   * Fire all callbacks registered.
+   */
+  _callback: function() {
+    for (var i = (this._callbacks.length - 1); i >= 0; i--) { 
+
+      //send this so the callback can call this.stop();
+      this._callbacks[i].call(this.context, this.onComplete);
+    }
+  },
+
+  /**
+   * Manually set the time of a job
+   */
+  setTime: function(time) {
+    if (!(time instanceof CronTime)) throw '\'time\' must be an instance of CronTime.';
+    this.stop();
+    this.cronTime = time;
+  },
+
+
+  /**
+   * Start the cronjob.
+   */
+  start: function() {
+    if(this.running) return;
+
+    var MAXDELAY = 2147483647; // The maximum number of milliseconds setTimeout will wait.
+    var self = this;
+    var timeout = this.cronTime.getTimeout();
+    var remaining = 0;
+
+    if (this.cronTime.realDate) this.runOnce = true;
+
+    // The callback wrapper checks if it needs to sleep another period or not
+    // and does the real callback logic when it's time.
+
+    function callbackWrapper() {
+
+      // If there is sleep time remaining, calculate how long and go to sleep
+      // again. This processing might make us miss the deadline by a few ms
+      // times the number of sleep sessions. Given a MAXDELAY of almost a
+      // month, this should be no issue.
+
+      if (remaining) {
+        if (remaining > MAXDELAY) {
+          remaining -= MAXDELAY;
+          timeout = MAXDELAY;
+        } else {
+          timeout = remaining;
+          remaining = 0;
+        }
+
+        self._timeout = setTimeout(callbackWrapper, timeout);
+      } else {
+
+        // We have arrived at the correct point in time.
+
+        self.running = false;
+
+        //start before calling back so the callbacks have the ability to stop the cron job
+        if (!(self.runOnce)) self.start();
+
+        self._callback();
+      }
+    }
+
+    if (timeout >= 0) {
+      this.running = true;
+
+      // Don't try to sleep more than MAXDELAY ms at a time.
+
+      if (timeout > MAXDELAY) {
+        remaining = timeout - MAXDELAY;
+        timeout = MAXDELAY;
+      }
+
+      this._timeout = setTimeout(callbackWrapper, timeout);
+    } else {
+      this.stop();
+    }
+  },
+
+  /**
+   * Stop the cronjob.
+   */
+  stop: function()
+  {
+    clearTimeout(this._timeout);
+    this.running = false;
+    if (this.onComplete) this.onComplete();
+  }
+};
+
+
+exports.job = function(cronTime, onTick, onComplete) {
+  return new CronJob(cronTime, onTick, onComplete);
+}
+
+exports.time = function(cronTime, timeZone) {
+  return new CronTime(cronTime, timeZone);
+}
+
+exports.sendAt = function(cronTime) {
+  return exports.time(cronTime).sendAt();
+}
+
+exports.timeout = function(cronTime) {
+  return exports.time(cronTime).getTimeout();
+}
+
+
+exports.CronJob = CronJob;
+exports.CronTime = CronTime;
+

File diff suppressed because it is too large
+ 85 - 0
node_modules/cron/package.json


+ 403 - 0
node_modules/cron/tests/test-cron.js

@@ -0,0 +1,403 @@
+var testCase = require('nodeunit').testCase,
+    cron = require('../lib/cron');
+
+module.exports = testCase({
+  'test second (* * * * * *)': function(assert) {
+    assert.expect(1);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+    }, null, true);
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 1250);
+  },
+  'test second with oncomplete (* * * * * *)': function(assert) {
+    assert.expect(2);
+    var c = new cron.CronJob('* * * * * *', function(done) {
+      assert.ok(true);
+    }, function () {
+      assert.ok(true);
+      assert.done();
+    }, true);
+    setTimeout(function() {
+      c.stop();
+    }, 1250);
+  },
+  'test every second for 5 seconds (* * * * * *)': function(assert) {
+    assert.expect(5);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+    }, null, true);
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 5250);
+  },
+  'test standard cron no-seconds syntax doesnt send on seconds (* * * * *)': function(assert) {
+    assert.expect(0);
+    // Delay test from running at minute boundary
+    var prepDate = new Date();
+    if (prepDate.getSeconds() >= 55) {
+      setTimeout(testRun, 5000);
+    } else {
+      testRun();
+    }
+
+    function testRun() {
+      var c = new cron.CronJob('* * * * *', function() {
+        assert.ok(true);
+      }, null, true);
+      setTimeout(function() {
+        c.stop();
+        assert.done();
+      }, 5250);
+    }
+  },
+  'test every second for 5 seconds with oncomplete (* * * * * *)': function(assert) {
+    assert.expect(6);
+    var c = new cron.CronJob('* * * * * *', function(done) {
+      assert.ok(true);
+    }, function() {
+      assert.ok(true);
+      assert.done();
+    }, true);
+    setTimeout(function() {
+      c.stop();
+    }, 5250);
+  },
+  'test every 1 second for 5 seconds (*/1 * * * * *)': function(assert) {
+    assert.expect(5);
+    var c = new cron.CronJob('*/1 * * * * *', function() {
+      assert.ok(true);
+    }, null, true);
+    setTimeout(function() {
+      assert.done();
+      c.stop();
+    }, 5250);
+  },
+  'test every 1 second for 5 seconds with oncomplete (*/1 * * * * *)': function(assert) {
+    assert.expect(6);
+    var c = new cron.CronJob('*/1 * * * * *', function(done) {
+      assert.ok(true);
+    }, function() {
+      assert.ok(true);
+      assert.done();
+    }, true);
+    setTimeout(function() {
+      c.stop();
+    }, 5250);
+  },
+  'test every second for a range ([start]-[end] * * * * *)': function(assert) {
+    assert.expect(5);
+    var prepDate = new Date();
+    if ((54 - prepDate.getSeconds()) <= 0) {
+      setTimeout(testRun, (60000 - (prepDate.getSeconds()*1000)) + 1000);
+    } else {
+      testRun();
+    }
+
+    function testRun() {
+      var d = new Date();
+      var s = d.getSeconds()+2;
+      var e = s + 6; //end value is inclusive
+      var c = new cron.CronJob(s + '-' + e +' * * * * *', function() {
+        assert.ok(true);
+      }, null, true);
+      setTimeout(function() {
+        c.stop();
+        assert.done();
+      }, 6250);
+    }
+  },
+  'test every second for a range with oncomplete ([start]-[end] * * * * *)': function(assert) {
+    assert.expect(6);
+    var prepDate = new Date();
+    if ((54 - prepDate.getSeconds()) <= 0) {
+      setTimeout(testRun, (60000 - (prepDate.getSeconds()*1000)) + 1000);
+    } else {
+      testRun();
+    }
+
+    function testRun() {
+      var d = new Date();
+      var s = d.getSeconds()+2;
+      var e = s + 6; //end value is inclusive
+      var c = new cron.CronJob(s + '-' + e +' * * * * *', function() {
+        assert.ok(true);
+      }, function() {
+        assert.ok(true);
+        assert.done();
+      }, true);
+      setTimeout(function() {
+        c.stop();
+      }, 6250);
+    }
+  },
+  'test second (* * * * * *) object constructor': function(assert) {
+    assert.expect(1);
+    var c = new cron.CronJob({
+      cronTime: '* * * * * *',
+      onTick: function() {
+        assert.ok(true);
+      },
+      start: true
+    });
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 1250);
+  },
+  'test second with oncomplete (* * * * * *) object constructor': function(assert) {
+    assert.expect(2);
+    var c = new cron.CronJob({
+      cronTime: '* * * * * *',
+      onTick: function(done) {
+        assert.ok(true);
+      },
+      onComplete: function () {
+        assert.ok(true);
+        assert.done();
+      },
+      start: true
+    });
+    setTimeout(function() {
+      c.stop();
+    }, 1250);
+  },
+  'test start/stop': function(assert) {
+    assert.expect(1);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+      this.stop();
+    });
+    c.start();
+    setTimeout(function() {
+      assert.done();
+    }, 3250);
+  },
+  'test specifying a specific date': function(assert) {
+    assert.expect(2);
+    var prepDate = new Date();
+    if ((58 - prepDate.getSeconds()) <= 0) {
+      setTimeout(testRun, (60000 - (prepDate.getSeconds()*1000)) + 1000);
+    } else {
+      testRun();
+    }
+
+    function testRun() {
+      var d = new Date();
+      var s = d.getSeconds()+1;
+      d.setSeconds(s);
+      var c = new cron.CronJob(d, function() {
+        var t = new Date();
+        assert.equal(t.getSeconds(), d.getSeconds());
+        assert.ok(true);
+      }, null, true);
+      setTimeout(function() {
+        c.stop();
+        assert.done();
+      }, 2250);
+    }
+  },
+  'test specifying a specific date with oncomplete': function(assert) {
+    assert.expect(3);
+    var prepDate = new Date();
+    if ((58 - prepDate.getSeconds()) <= 0) {
+      setTimeout(testRun, (60000 - (prepDate.getSeconds()*1000)) + 1000);
+    } else {
+      testRun();
+    }
+
+    function testRun() {
+      var d = new Date();
+      var s = d.getSeconds()+1;
+      d.setSeconds(s);
+      var c = new cron.CronJob(d, function() {
+        var t = new Date();
+        assert.equal(t.getSeconds(), d.getSeconds());
+        assert.ok(true);
+      }, function() {
+        assert.ok(true);
+        assert.done();
+      }, true);
+      setTimeout(function() {
+        c.stop();
+      }, 2250);
+    }
+  },
+  'test a job with a string and a given time zone': function (assert) {
+    assert.expect(3);
+
+    var time = require("time");
+    var zone = "America/Chicago";
+
+    // New Orleans time
+    var t = new time.Date();
+    t.setTimezone(zone);
+
+    // Current time
+    d = new Date();
+
+    // If current time is New Orleans time, switch to Los Angeles..
+    if (t.getHours() === d.getHours()) {
+      zone = "America/Los_Angeles";
+      t.setTimezone(zone);
+    }
+    assert.notEqual(d.getHours(), t.getHours());
+    assert.ok(!(Date instanceof time.Date));
+
+    // If t = 59s12m then t.setSeconds(60)
+    // becones 00s13m so we're fine just doing
+    // this and no testRun callback.
+    t.setSeconds(t.getSeconds()+1);
+    // Run a job designed to be executed at a given 
+    // time in `zone`, making sure that it is a different
+    // hour than local time.
+    var c = new cron.CronJob(t.getSeconds() + ' ' + t.getMinutes() + ' ' + t.getHours() +  ' * * *', function(){
+      assert.ok(true);
+    }, undefined, true, zone);
+
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 1250);
+  },
+  'test a job with a date and a given time zone': function (assert) {
+    assert.expect(3);
+
+    var time = require("time");
+    var zone = "America/Chicago";
+
+    // New Orleans time
+    var t = new time.Date();
+    t.setTimezone(zone);
+
+    // Current time
+    d = new Date();
+
+    // If current time is New Orleans time, switch to Los Angeles..
+    if (t.getHours() === d.getHours()) {
+      zone = "America/Los_Angeles";
+      t.setTimezone(zone);
+    }
+    assert.notEqual(d.getHours(), t.getHours());
+    assert.ok(!(Date instanceof time.Date));
+
+    if ((58 - t.getSeconds()) <= 0) {
+      setTimeout(testRun, (60000 - (t.getSeconds()*1000)) + 1000);
+    } else {
+      testRun();
+    }
+
+    function testRun() {
+      var s = d.getSeconds()+1;
+      d.setSeconds(s);
+      var c = new cron.CronJob(d, function() {
+        assert.ok(true);
+      }, null, true, zone);
+      setTimeout(function() {
+        c.stop();
+        assert.done();
+      }, 2250);
+    }
+  },
+  'test dates fire only once': function(assert) {
+    assert.expect(1);
+    var count = 0;
+    var d = new Date().getTime() + 1000;
+    var job = cron.job(new Date(d), function() { 
+      count++;
+    }); 
+    job.start();
+    setTimeout(function() {
+      job.stop();
+      assert.equal(count, 1);
+      assert.done();
+    }, 5250);
+  },
+  'test long wait should not fire immediately': function(assert) {
+    assert.expect(1);
+    var count = 0;
+    var d = new Date().getTime() + 31 * 86400 * 1000;
+    var job = cron.job(new Date(d), function() {
+      assert.ok(false);
+    });
+    job.start();
+    setTimeout(function() {
+      job.stop();
+      assert.ok(true);
+      assert.done();
+    }, 250);
+  },
+  'test start, change time, start again': function(assert) {
+    assert.expect(3);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+    });
+    var time = cron.time('*/2 * * * * *');
+    c.start();
+    setTimeout(function() {
+      c.stop();
+      c.setTime(time);
+      c.start();
+      setTimeout(function() {
+        c.stop();
+        assert.done();
+      }, 4250);
+    }, 1250);
+  },
+  'test start, change time, excpetion': function(assert) {
+    assert.expect(2);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+    });
+    var time = new Date();
+    c.start();
+    setTimeout(function() {
+      c.stop();
+      assert.throws(function() {
+        c.setTime(time);
+      });
+      assert.done();
+    }, 1250);
+  },
+  'test cronjob scoping': function(assert) {
+    assert.expect(2);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+      assert.ok(c instanceof cron.CronJob);
+    }, null, true);
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 1250);
+  },
+  'test non-cronjob scoping': function(assert) {
+    assert.expect(2);
+    var c = new cron.CronJob('* * * * * *', function() {
+      assert.ok(true);
+      assert.equal(this.hello, 'world');
+    }, null, true, null, {'hello':'world'});
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 1250);
+  },
+  'test non-cronjob scoping inside object': function(assert) {
+    assert.expect(2);
+    var c = new cron.CronJob({
+      cronTime: '* * * * * *',
+      onTick: function() {
+        assert.ok(true);
+        assert.equal(this.hello, 'world');
+      },
+      start: true,
+      context: {hello: 'world'}
+    });
+    setTimeout(function() {
+      c.stop();
+      assert.done();
+    }, 1250);
+  }
+});

+ 152 - 0
node_modules/cron/tests/test-crontime.js

@@ -0,0 +1,152 @@
+var testCase = require('nodeunit').testCase,
+    cron = require('../lib/cron');
+
+module.exports = testCase({
+        'test stars (* * * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('* * * * * *');
+            });
+            assert.done();
+        },
+        'test digit (0 * * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0 * * * * *');
+            });
+            assert.done();
+        },
+        'test multi digits (08 * * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('08 * * * * *');
+            });
+            assert.done();
+        },
+        'test all digits (08 8 8 8 8 5)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('08 * * * * *');
+            });
+            assert.done();
+        },
+        'test too many digits (08 8 8 8 8 5)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('08 * * * * *');
+            });
+            assert.done();
+        },
+        'test no second digit doesnt throw, i.e. standard cron format (8 8 8 8 5)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('* * * * *');
+            });
+            assert.done();
+        },
+        'test no second digit defaults to 0, i.e. standard cron format (8 8 8 8 5)': function(assert) {
+            assert.expect(1);
+            var now = new Date();
+            var standard = new cron.CronTime('8 8 8 8 5');
+            var extended = new cron.CronTime('0 8 8 8 8 5');
+            assert.ok(standard._getNextDateFrom(now).getTime() === extended._getNextDateFrom(now).getTime());
+            assert.done();
+        },
+        'test hyphen (0-10 * * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0-10 * * * * *');
+            });
+            assert.done();
+        },
+        'test multi hyphens (0-10 0-10 * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0-10 0-10 * * * *');
+            });
+            assert.done();
+        },
+        'test all hyphens (0-10 0-10 0-10 0-10 0-10 0-1)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0-10 0-10 0-10 0-10 0-10 0-1');
+            });
+            assert.done();
+        },
+        'test comma (0,10 * * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0,10 * * * * *');
+            });
+            assert.done();
+        },
+        'test multi commas (0,10 0,10 * * * *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0,10 0,10 * * * *');
+            });
+            assert.done();
+        },
+        'test all commas (0,10 0,10 0,10 0,10 0,10 0,1)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('0,10 0,10 0,10 0,10 0,10 0,1');
+            });
+            assert.done();
+        },
+        'test alias (* * * * jan *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('* * * * jan *');
+            });
+            assert.done();
+        },
+        'test multi aliases (* * * * jan,feb *)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('* * * * jan,feb *');
+            });
+            assert.done();
+        },
+        'test all aliases (* * * * jan,feb mon,tue)': function(assert) {
+            assert.expect(1);
+            assert.doesNotThrow(function() {
+                new cron.CronTime('* * * * jan,feb mon,tue');
+            });
+            assert.done();
+        },
+        'test unknown alias (* * * * jar *)': function(assert) {
+            assert.expect(1);
+            assert.throws(function() {
+                new cron.CronTime('* * * * jar *');
+            });
+            assert.done();
+        },
+        'test unknown alias - short (* * * * j *)': function(assert) {
+            assert.expect(1);
+            assert.throws(function() {
+                new cron.CronTime('* * * * j *');
+            });
+            assert.done();
+        },
+        'test Date': function(assert) {
+          assert.expect(1);
+          var d = new Date();
+          var ct = new cron.CronTime(d);
+          assert.equals(ct.source.getTime(), d.getTime());
+          assert.done();
+        },
+        'test day roll-over': function(assert) {
+          var numHours = 24;
+          assert.expect(numHours * 2);
+          var ct = new cron.CronTime('0 0 17 * * *');
+          
+          for (var hr = 0; hr < numHours; hr++) {
+            var start = new Date(2012, 3, 16, hr, 30, 30);
+            var next = ct._getNextDateFrom(start);
+            assert.ok(next - start < 24*60*60*1000);
+            assert.ok(next > start);
+          }
+          assert.done();
+        }
+});

+ 23 - 0
node_modules/ini/LICENSE

@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

+ 79 - 0
node_modules/ini/README.md

@@ -0,0 +1,79 @@
+An ini format parser and serializer for node.
+
+Sections are treated as nested objects.  Items before the first heading
+are saved on the object directly.
+
+## Usage
+
+Consider an ini-file `config.ini` that looks like this:
+
+    ; this comment is being ignored
+    scope = global
+
+    [database]
+    user = dbuser
+    password = dbpassword
+    database = use_this_database
+
+    [paths.default]
+    datadir = /var/lib/data
+    array[] = first value
+    array[] = second value
+    array[] = third value
+
+You can read, manipulate and write the ini-file like so:
+
+    var fs = require('fs')
+      , ini = require('ini')
+
+    var config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'))
+
+    config.scope = 'local'
+    config.database.database = 'use_another_database'
+    config.paths.default.tmpdir = '/tmp'
+    delete config.paths.default.datadir
+    config.paths.default.array.push('fourth value')
+
+    fs.writeFileSync('./config_modified.ini', ini.stringify(config, 'section'))
+
+This will result in a file called `config_modified.ini` being written to the filesystem with the following content:
+
+    [section]
+    scope = local
+    [section.database]
+    user = dbuser
+    password = dbpassword
+    database = use_another_database
+    [section.paths.default]
+    tmpdir = /tmp
+    array[] = first value
+    array[] = second value
+    array[] = third value
+    array[] = fourth value
+
+
+## API
+
+### decode(inistring)
+Decode the ini-style formatted `inistring` into a nested object.
+
+### parse(inistring)
+Alias for `decode(inistring)`
+
+### encode(object, [section])
+Encode the object `object` into an ini-style formatted string. If the optional parameter `section` is given, then all top-level properties of the object are put into this section and the `section`-string is prepended to all sub-sections, see the usage example above.
+
+### stringify(object, [section])
+Alias for `encode(object, [section])`
+
+### safe(val)
+Escapes the string `val` such that it is safe to be used as a key or value in an ini-file. Basically escapes quotes. For example
+
+    ini.safe('"unsafe string"')
+
+would result in
+
+    "\"unsafe string\""
+
+### unsafe(val)
+Unescapes the string `val`

+ 166 - 0
node_modules/ini/ini.js

@@ -0,0 +1,166 @@
+
+exports.parse = exports.decode = decode
+exports.stringify = exports.encode = encode
+
+exports.safe = safe
+exports.unsafe = unsafe
+
+var eol = process.platform === "win32" ? "\r\n" : "\n"
+
+function encode (obj, section) {
+  var children = []
+    , out = ""
+
+  Object.keys(obj).forEach(function (k, _, __) {
+    var val = obj[k]
+    if (val && Array.isArray(val)) {
+        val.forEach(function(item) {
+            out += safe(k + "[]") + " = " + safe(item) + "\n"
+        })
+    }
+    else if (val && typeof val === "object") {
+      children.push(k)
+    } else {
+      out += safe(k) + " = " + safe(val) + eol
+    }
+  })
+
+  if (section && out.length) {
+    out = "[" + safe(section) + "]" + eol + out
+  }
+
+  children.forEach(function (k, _, __) {
+    var nk = dotSplit(k).join('\\.')
+    var child = encode(obj[k], (section ? section + "." : "") + nk)
+    if (out.length && child.length) {
+      out += eol
+    }
+    out += child
+  })
+
+  return out
+}
+
+function dotSplit (str) {
+  return str.replace(/\1/g, '\2LITERAL\\1LITERAL\2')
+         .replace(/\\\./g, '\1')
+         .split(/\./).map(function (part) {
+           return part.replace(/\1/g, '\\.')
+                  .replace(/\2LITERAL\\1LITERAL\2/g, '\1')
+         })
+}
+
+function decode (str) {
+  var out = {}
+    , p = out
+    , section = null
+    , state = "START"
+           // section     |key = value
+    , re = /^\[([^\]]*)\]$|^([^=]+)(=(.*))?$/i
+    , lines = str.split(/[\r\n]+/g)
+    , section = null
+
+  lines.forEach(function (line, _, __) {
+    if (!line || line.match(/^\s*;/)) return
+    var match = line.match(re)
+    if (!match) return
+    if (match[1] !== undefined) {
+      section = unsafe(match[1])
+      p = out[section] = out[section] || {}
+      return
+    }
+    var key = unsafe(match[2])
+      , value = match[3] ? unsafe((match[4] || "")) : true
+    switch (value) {
+      case 'true':
+      case 'false':
+      case 'null': value = JSON.parse(value)
+    }
+
+    // Convert keys with '[]' suffix to an array
+    if (key.length > 2 && key.slice(-2) === "[]") {
+        key = key.substring(0, key.length - 2)
+        if (!p[key]) {
+          p[key] = []
+        }
+        else if (!Array.isArray(p[key])) {
+          p[key] = [p[key]]
+        }
+    }
+
+    // safeguard against resetting a previously defined
+    // array by accidentally forgetting the brackets
+    if (Array.isArray(p[key])) {
+      p[key].push(value)
+    }
+    else {
+      p[key] = value
+    }
+  })
+
+  // {a:{y:1},"a.b":{x:2}} --> {a:{y:1,b:{x:2}}}
+  // use a filter to return the keys that have to be deleted.
+  Object.keys(out).filter(function (k, _, __) {
+    if (!out[k] || typeof out[k] !== "object" || Array.isArray(out[k])) return false
+    // see if the parent section is also an object.
+    // if so, add it to that, and mark this one for deletion
+    var parts = dotSplit(k)
+      , p = out
+      , l = parts.pop()
+      , nl = l.replace(/\\\./g, '.')
+    parts.forEach(function (part, _, __) {
+      if (!p[part] || typeof p[part] !== "object") p[part] = {}
+      p = p[part]
+    })
+    if (p === out && nl === l) return false
+    p[nl] = out[k]
+    return true
+  }).forEach(function (del, _, __) {
+    delete out[del]
+  })
+
+  return out
+}
+
+function safe (val) {
+  return ( typeof val !== "string"
+         || val.match(/[\r\n]/)
+         || val.match(/^\[/)
+         || (val.length > 1
+             && val.charAt(0) === "\""
+             && val.slice(-1) === "\"")
+         || val !== val.trim() )
+         ? JSON.stringify(val)
+         : val.replace(/;/g, '\\;')
+}
+
+function unsafe (val, doUnesc) {
+  val = (val || "").trim()
+  if (val.charAt(0) === "\"" && val.slice(-1) === "\"") {
+    try { val = JSON.parse(val) } catch (_) {}
+  } else {
+    // walk the val to find the first not-escaped ; character
+    var esc = false
+    var unesc = "";
+    for (var i = 0, l = val.length; i < l; i++) {
+      var c = val.charAt(i)
+      if (esc) {
+        if (c === "\\" || c === ";")
+          unesc += c
+        else
+          unesc += "\\" + c
+        esc = false
+      } else if (c === ";") {
+        break
+      } else if (c === "\\") {
+        esc = true
+      } else {
+        unesc += c
+      }
+    }
+    if (esc)
+      unesc += "\\"
+    return unesc
+  }
+  return val
+}

File diff suppressed because it is too large
+ 29 - 0
node_modules/ini/package.json


+ 23 - 0
node_modules/ini/test/bar.js

@@ -0,0 +1,23 @@
+//test that parse(stringify(obj) deepEqu
+
+var ini = require('../')
+var test = require('tap').test
+
+var data = {
+  'number':  {count: 10},
+  'string':  {drink: 'white russian'},
+  'boolean': {isTrue: true},
+  'nested boolean': {theDude: {abides: true, rugCount: 1}}
+}
+
+
+test('parse(stringify(x)) deepEqual x', function (t) {
+
+  for (var k in data) {
+    var s = ini.stringify(data[k])
+    console.log(s, data[k])
+    t.deepEqual(ini.parse(s), data[k])
+  }
+
+  t.end() 
+})

+ 47 - 0
node_modules/ini/test/fixtures/foo.ini

@@ -0,0 +1,47 @@
+o = p
+
+   a with spaces   =     b  c
+
+; wrap in quotes to JSON-decode and preserve spaces
+" xa  n          p " = "\"\r\nyoyoyo\r\r\n"
+
+; wrap in quotes to get a key with a bracket, not a section.
+"[disturbing]" = hey you never know
+
+; Test arrays
+zr[] = deedee
+ar[] = one
+ar[] = three
+; This should be included in the array
+ar   = this is included
+
+; Test resetting of a value (and not turn it into an array)
+br = cold
+br = warm
+
+; a section
+[a]
+av = a val
+e = { o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }
+j = "{ o: "p", a: { av: "a val", b: { c: { e: "this [value]" } } } }"
+"[]" = a square?
+
+; Nested array
+cr[] = four
+cr[] = eight
+
+; nested child without middle parent
+; should create otherwise-empty a.b
+[a.b.c]
+e = 1
+j = 2
+
+; dots in the section name should be literally interpreted
+[x\.y\.z]
+x.y.z = xyz
+
+[x\.y\.z.a\.b\.c]
+a.b.c = abc
+
+; this next one is not a comment!  it's escaped!
+nocomment = this\; this is not a comment

+ 71 - 0
node_modules/ini/test/foo.js

@@ -0,0 +1,71 @@
+var i = require("../")
+  , tap = require("tap")
+  , test = tap.test
+  , fs = require("fs")
+  , path = require("path")
+  , fixture = path.resolve(__dirname, "./fixtures/foo.ini")
+  , data = fs.readFileSync(fixture, "utf8")
+  , d
+  , expectE = 'o = p\n'
+            + 'a with spaces = b  c\n'
+            + '" xa  n          p " = "\\"\\r\\nyoyoyo\\r\\r\\n"\n'
+            + '"[disturbing]" = hey you never know\n'
+            + 'zr[] = deedee\n'
+            + 'ar[] = one\n'
+            + 'ar[] = three\n'
+            + 'ar[] = this is included\n'
+            + 'br = warm\n'
+            + '\n'
+            + '[a]\n'
+            + 'av = a val\n'
+            + 'e = { o: p, a: '
+            + '{ av: a val, b: { c: { e: "this [value]" '
+            + '} } } }\nj = "\\"{ o: \\"p\\", a: { av:'
+            + ' \\"a val\\", b: { c: { e: \\"this [value]'
+            + '\\" } } } }\\""\n"[]" = a square?\n'
+            + 'cr[] = four\ncr[] = eight\n\n'
+            +'[a.b.c]\ne = 1\n'
+            + 'j = 2\n\n[x\\.y\\.z]\nx.y.z = xyz\n\n'
+            + '[x\\.y\\.z.a\\.b\\.c]\na.b.c = abc\n'
+            + 'nocomment = this\\; this is not a comment\n'
+  , expectD =
+    { o: 'p',
+      'a with spaces': 'b  c',
+      " xa  n          p ":'"\r\nyoyoyo\r\r\n',
+      '[disturbing]': 'hey you never know',
+      'zr': ['deedee'],
+      'ar': ['one', 'three', 'this is included'],
+      'br': 'warm',
+      a:
+       { av: 'a val',
+         e: '{ o: p, a: { av: a val, b: { c: { e: "this [value]" } } } }',
+         j: '"{ o: "p", a: { av: "a val", b: { c: { e: "this [value]" } } } }"',
+         "[]": "a square?",
+         cr: ['four', 'eight'],
+         b: { c: { e: '1', j: '2' } } },
+      'x.y.z': {
+        'x.y.z': 'xyz',
+        'a.b.c': {
+          'a.b.c': 'abc',
+          'nocomment': 'this\; this is not a comment'
+        }
+      }
+    }
+
+test("decode from file", function (t) {
+  var d = i.decode(data)
+  t.deepEqual(d, expectD)
+  t.end()
+})
+
+test("encode from data", function (t) {
+  var e = i.encode(expectD)
+  t.deepEqual(e, expectE)
+
+  var obj = {log: { type:'file', level: {label:'debug', value:10} } }
+  e = i.encode(obj)
+  t.notEqual(e.slice(0, 1), '\n', 'Never a blank first line')
+  t.notEqual(e.slice(-2), '\n\n', 'Never a blank final line')
+
+  t.end()
+})

+ 23 - 0
node_modules/minimatch/LICENSE

@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

+ 218 - 0
node_modules/minimatch/README.md

@@ -0,0 +1,218 @@
+# minimatch
+
+A minimal matching utility.
+
+[![Build Status](https://secure.travis-ci.org/isaacs/minimatch.png)](http://travis-ci.org/isaacs/minimatch)
+
+
+This is the matching library used internally by npm.
+
+Eventually, it will replace the C binding in node-glob.
+
+It works by converting glob expressions into JavaScript `RegExp`
+objects.
+
+## Usage
+
+```javascript
+var minimatch = require("minimatch")
+
+minimatch("bar.foo", "*.foo") // true!
+minimatch("bar.foo", "*.bar") // false!
+```
+
+## Features
+
+Supports these glob features:
+
+* Brace Expansion
+* Extended glob matching
+* "Globstar" `**` matching
+
+See:
+
+* `man sh`
+* `man bash`
+* `man 3 fnmatch`
+* `man 5 gitignore`
+
+### Comparisons to other fnmatch/glob implementations
+
+While strict compliance with the existing standards is a worthwhile
+goal, some discrepancies exist between minimatch and other
+implementations, and are intentional.
+
+If the pattern starts with a `!` character, then it is negated.  Set the
+`nonegate` flag to suppress this behavior, and treat leading `!`
+characters normally.  This is perhaps relevant if you wish to start the
+pattern with a negative extglob pattern like `!(a|B)`.  Multiple `!`
+characters at the start of a pattern will negate the pattern multiple
+times.
+
+If a pattern starts with `#`, then it is treated as a comment, and
+will not match anything.  Use `\#` to match a literal `#` at the
+start of a line, or set the `nocomment` flag to suppress this behavior.
+
+The double-star character `**` is supported by default, unless the
+`noglobstar` flag is set.  This is supported in the manner of bsdglob
+and bash 4.1, where `**` only has special significance if it is the only
+thing in a path part.  That is, `a/**/b` will match `a/x/y/b`, but
+`a/**b` will not.  **Note that this is different from the way that `**` is
+handled by ruby's `Dir` class.**
+
+If an escaped pattern has no matches, and the `nonull` flag is set,
+then minimatch.match returns the pattern as-provided, rather than
+interpreting the character escapes.  For example,
+`minimatch.match([], "\\*a\\?")` will return `"\\*a\\?"` rather than
+`"*a?"`.  This is akin to setting the `nullglob` option in bash, except
+that it does not resolve escaped pattern characters.
+
+If brace expansion is not disabled, then it is performed before any
+other interpretation of the glob pattern.  Thus, a pattern like
+`+(a|{b),c)}`, which would not be valid in bash or zsh, is expanded
+**first** into the set of `+(a|b)` and `+(a|c)`, and those patterns are
+checked for validity.  Since those two are valid, matching proceeds.
+
+
+## Minimatch Class
+
+Create a minimatch object by instanting the `minimatch.Minimatch` class.
+
+```javascript
+var Minimatch = require("minimatch").Minimatch
+var mm = new Minimatch(pattern, options)
+```
+
+### Properties
+
+* `pattern` The original pattern the minimatch object represents.
+* `options` The options supplied to the constructor.
+* `set` A 2-dimensional array of regexp or string expressions.
+  Each row in the
+  array corresponds to a brace-expanded pattern.  Each item in the row
+  corresponds to a single path-part.  For example, the pattern
+  `{a,b/c}/d` would expand to a set of patterns like:
+
+        [ [ a, d ]
+        , [ b, c, d ] ]
+
+    If a portion of the pattern doesn't have any "magic" in it
+    (that is, it's something like `"foo"` rather than `fo*o?`), then it
+    will be left as a string rather than converted to a regular
+    expression.
+
+* `regexp` Created by the `makeRe` method.  A single regular expression
+  expressing the entire pattern.  This is useful in cases where you wish
+  to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled.
+* `negate` True if the pattern is negated.
+* `comment` True if the pattern is a comment.
+* `empty` True if the pattern is `""`.
+
+### Methods
+
+* `makeRe` Generate the `regexp` member if necessary, and return it.
+  Will return `false` if the pattern is invalid.
+* `match(fname)` Return true if the filename matches the pattern, or
+  false otherwise.
+* `matchOne(fileArray, patternArray, partial)` Take a `/`-split
+  filename, and match it against a single row in the `regExpSet`.  This
+  method is mainly for internal use, but is exposed so that it can be
+  used by a glob-walker that needs to avoid excessive filesystem calls.
+
+All other methods are internal, and will be called as necessary.
+
+## Functions
+
+The top-level exported function has a `cache` property, which is an LRU
+cache set to store 100 items.  So, calling these methods repeatedly
+with the same pattern and options will use the same Minimatch object,
+saving the cost of parsing it multiple times.
+
+### minimatch(path, pattern, options)
+
+Main export.  Tests a path against the pattern using the options.
+
+```javascript
+var isJS = minimatch(file, "*.js", { matchBase: true })
+```
+
+### minimatch.filter(pattern, options)
+
+Returns a function that tests its
+supplied argument, suitable for use with `Array.filter`.  Example:
+
+```javascript
+var javascripts = fileList.filter(minimatch.filter("*.js", {matchBase: true}))
+```
+
+### minimatch.match(list, pattern, options)
+
+Match against the list of
+files, in the style of fnmatch or glob.  If nothing is matched, and
+options.nonull is set, then return a list containing the pattern itself.
+
+```javascript
+var javascripts = minimatch.match(fileList, "*.js", {matchBase: true}))
+```
+
+### minimatch.makeRe(pattern, options)
+
+Make a regular expression object from the pattern.
+
+## Options
+
+All options are `false` by default.
+
+### debug
+
+Dump a ton of stuff to stderr.
+
+### nobrace
+
+Do not expand `{a,b}` and `{1..3}` brace sets.
+
+### noglobstar
+
+Disable `**` matching against multiple folder names.
+
+### dot
+
+Allow patterns to match filenames starting with a period, even if
+the pattern does not explicitly have a period in that spot.
+
+Note that by default, `a/**/b` will **not** match `a/.d/b`, unless `dot`
+is set.
+
+### noext
+
+Disable "extglob" style patterns like `+(a|b)`.
+
+### nocase
+
+Perform a case-insensitive match.
+
+### nonull
+
+When a match is not found by `minimatch.match`, return a list containing
+the pattern itself.  When set, an empty list is returned if there are
+no matches.
+
+### matchBase
+
+If set, then patterns without slashes will be matched
+against the basename of the path if it contains slashes.  For example,
+`a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`.
+
+### nocomment
+
+Suppress the behavior of treating `#` at the start of a pattern as a
+comment.
+
+### nonegate
+
+Suppress the behavior of treating a leading `!` character as negation.
+
+### flipNegate
+
+Returns from negate expressions the same as if they were not negated.
+(Ie, true on a hit, false on a miss.)

File diff suppressed because it is too large
+ 1079 - 0
node_modules/minimatch/minimatch.js


+ 1 - 0
node_modules/minimatch/node_modules/lru-cache/.npmignore

@@ -0,0 +1 @@
+/node_modules

+ 8 - 0
node_modules/minimatch/node_modules/lru-cache/AUTHORS

@@ -0,0 +1,8 @@
+# Authors, sorted by whether or not they are me
+Isaac Z. Schlueter <i@izs.me>
+Carlos Brito Lage <carlos@carloslage.net>
+Marko Mikulicic <marko.mikulicic@isti.cnr.it>
+Trent Mick <trentm@gmail.com>
+Kevin O'Hara <kevinohara80@gmail.com>
+Marco Rogers <marco.rogers@gmail.com>
+Jesse Dailey <jesse.dailey@gmail.com>

+ 23 - 0
node_modules/minimatch/node_modules/lru-cache/LICENSE

@@ -0,0 +1,23 @@
+Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

+ 97 - 0
node_modules/minimatch/node_modules/lru-cache/README.md

@@ -0,0 +1,97 @@
+# lru cache
+
+A cache object that deletes the least-recently-used items.
+
+## Usage:
+
+```javascript
+var LRU = require("lru-cache")
+  , options = { max: 500
+              , length: function (n) { return n * 2 }
+              , dispose: function (key, n) { n.close() }
+              , maxAge: 1000 * 60 * 60 }
+  , cache = LRU(options)
+  , otherCache = LRU(50) // sets just the max size
+
+cache.set("key", "value")
+cache.get("key") // "value"
+
+cache.reset()    // empty the cache
+```
+
+If you put more stuff in it, then items will fall out.
+
+If you try to put an oversized thing in it, then it'll fall out right
+away.
+
+## Options
+
+* `max` The maximum size of the cache, checked by applying the length
+  function to all values in the cache.  Not setting this is kind of
+  silly, since that's the whole purpose of this lib, but it defaults
+  to `Infinity`.
+* `maxAge` Maximum age in ms.  Items are not pro-actively pruned out
+  as they age, but if you try to get an item that is too old, it'll
+  drop it and return undefined instead of giving it to you.
+* `length` Function that is used to calculate the length of stored
+  items.  If you're storing strings or buffers, then you probably want
+  to do something like `function(n){return n.length}`.  The default is
+  `function(n){return 1}`, which is fine if you want to store `n`
+  like-sized things.
+* `dispose` Function that is called on items when they are dropped
+  from the cache.  This can be handy if you want to close file
+  descriptors or do other cleanup tasks when items are no longer
+  accessible.  Called with `key, value`.  It's called *before*
+  actually removing the item from the internal cache, so if you want
+  to immediately put it back in, you'll have to do that in a
+  `nextTick` or `setTimeout` callback or it won't do anything.
+* `stale` By default, if you set a `maxAge`, it'll only actually pull
+  stale items out of the cache when you `get(key)`.  (That is, it's
+  not pre-emptively doing a `setTimeout` or anything.)  If you set
+  `stale:true`, it'll return the stale value before deleting it.  If
+  you don't set this, then it'll return `undefined` when you try to
+  get a stale entry, as if it had already been deleted.
+
+## API
+
+* `set(key, value)`
+* `get(key) => value`
+
+    Both of these will update the "recently used"-ness of the key.
+    They do what you think.
+
+* `peek(key)`
+
+    Returns the key value (or `undefined` if not found) without
+    updating the "recently used"-ness of the key.
+
+    (If you find yourself using this a lot, you *might* be using the
+    wrong sort of data structure, but there are some use cases where
+    it's handy.)
+
+* `del(key)`
+
+    Deletes a key out of the cache.
+
+* `reset()`
+
+    Clear the cache entirely, throwing away all values.
+
+* `has(key)`
+
+    Check if a key is in the cache, without updating the recent-ness
+    or deleting it for being stale.
+
+* `forEach(function(value,key,cache), [thisp])`
+
+    Just like `Array.prototype.forEach`.  Iterates over all the keys
+    in the cache, in order of recent-ness.  (Ie, more recently used
+    items are iterated over first.)
+
+* `keys()`
+
+    Return an array of the keys in the cache.
+
+* `values()`
+
+    Return an array of the values in the cache.

+ 257 - 0
node_modules/minimatch/node_modules/lru-cache/lib/lru-cache.js

@@ -0,0 +1,257 @@
+;(function () { // closure for web browsers
+
+if (typeof module === 'object' && module.exports) {
+  module.exports = LRUCache
+} else {
+  // just set the global for non-node platforms.
+  this.LRUCache = LRUCache
+}
+
+function hOP (obj, key) {
+  return Object.prototype.hasOwnProperty.call(obj, key)
+}
+
+function naiveLength () { return 1 }
+
+function LRUCache (options) {
+  if (!(this instanceof LRUCache)) {
+    return new LRUCache(options)
+  }
+
+  var max
+  if (typeof options === 'number') {
+    max = options
+    options = { max: max }
+  }
+
+  if (!options) options = {}
+
+  max = options.max
+
+  var lengthCalculator = options.length || naiveLength
+
+  if (typeof lengthCalculator !== "function") {
+    lengthCalculator = naiveLength
+  }
+
+  if (!max || !(typeof max === "number") || max <= 0 ) {
+    // a little bit silly.  maybe this should throw?
+    max = Infinity
+  }
+
+  var allowStale = options.stale || false
+
+  var maxAge = options.maxAge || null
+
+  var dispose = options.dispose
+
+  var cache = Object.create(null) // hash of items by key
+    , lruList = Object.create(null) // list of items in order of use recency
+    , mru = 0 // most recently used
+    , lru = 0 // least recently used
+    , length = 0 // number of items in the list
+    , itemCount = 0
+
+
+  // resize the cache when the max changes.
+  Object.defineProperty(this, "max",
+    { set : function (mL) {
+        if (!mL || !(typeof mL === "number") || mL <= 0 ) mL = Infinity
+        max = mL
+        // if it gets above double max, trim right away.
+        // otherwise, do it whenever it's convenient.
+        if (length > max) trim()
+      }
+    , get : function () { return max }
+    , enumerable : true
+    })
+
+  // resize the cache when the lengthCalculator changes.
+  Object.defineProperty(this, "lengthCalculator",
+    { set : function (lC) {
+        if (typeof lC !== "function") {
+          lengthCalculator = naiveLength
+          length = itemCount
+          for (var key in cache) {
+            cache[key].length = 1
+          }
+        } else {
+          lengthCalculator = lC
+          length = 0
+          for (var key in cache) {
+            cache[key].length = lengthCalculator(cache[key].value)
+            length += cache[key].length
+          }
+        }
+
+        if (length > max) trim()
+      }
+    , get : function () { return lengthCalculator }
+    , enumerable : true
+    })
+
+  Object.defineProperty(this, "length",
+    { get : function () { return length }
+    , enumerable : true
+    })
+
+
+  Object.defineProperty(this, "itemCount",
+    { get : function () { return itemCount }
+    , enumerable : true
+    })
+
+  this.forEach = function (fn, thisp) {
+    thisp = thisp || this
+    var i = 0;
+    for (var k = mru - 1; k >= 0 && i < itemCount; k--) if (lruList[k]) {
+      i++
+      var hit = lruList[k]
+      fn.call(thisp, hit.value, hit.key, this)
+    }
+  }
+
+  this.keys = function () {
+    var keys = new Array(itemCount)
+    var i = 0
+    for (var k = mru - 1; k >= 0 && i < itemCount; k--) if (lruList[k]) {
+      var hit = lruList[k]
+      keys[i++] = hit.key
+    }
+    return keys
+  }
+
+  this.values = function () {
+    var values = new Array(itemCount)
+    var i = 0
+    for (var k = mru - 1; k >= 0 && i < itemCount; k--) if (lruList[k]) {
+      var hit = lruList[k]
+      values[i++] = hit.value
+    }
+    return values
+  }
+
+  this.reset = function () {
+    if (dispose) {
+      for (var k in cache) {
+        dispose(k, cache[k].value)
+      }
+    }
+    cache = {}
+    lruList = {}
+    lru = 0
+    mru = 0
+    length = 0
+    itemCount = 0
+  }
+
+  // Provided for debugging/dev purposes only. No promises whatsoever that
+  // this API stays stable.
+  this.dump = function () {
+    return cache
+  }
+
+  this.dumpLru = function () {
+    return lruList
+  }
+
+  this.set = function (key, value) {
+    if (hOP(cache, key)) {
+      // dispose of the old one before overwriting
+      if (dispose) dispose(key, cache[key].value)
+      if (maxAge) cache[key].now = Date.now()
+      cache[key].value = value
+      this.get(key)
+      return true
+    }
+
+    var len = lengthCalculator(value)
+    var age = maxAge ? Date.now() : 0
+    var hit = new Entry(key, value, mru++, len, age)
+
+    // oversized objects fall out of cache automatically.
+    if (hit.length > max) {
+      if (dispose) dispose(key, value)
+      return false
+    }
+
+    length += hit.length
+    lruList[hit.lu] = cache[key] = hit
+    itemCount ++
+
+    if (length > max) trim()
+    return true
+  }
+
+  this.has = function (key) {
+    if (!hOP(cache, key)) return false
+    var hit = cache[key]
+    if (maxAge && (Date.now() - hit.now > maxAge)) {
+      return false
+    }
+    return true
+  }
+
+  this.get = function (key) {
+    return get(key, true)
+  }
+
+  this.peek = function (key) {
+    return get(key, false)
+  }
+
+  function get (key, doUse) {
+    var hit = cache[key]
+    if (hit) {
+      if (maxAge && (Date.now() - hit.now > maxAge)) {
+        del(hit)
+        if (!allowStale) hit = undefined
+      } else {
+        if (doUse) use(hit)
+      }
+      if (hit) hit = hit.value
+    }
+    return hit
+  }
+
+  function use (hit) {
+    shiftLU(hit)
+    hit.lu = mru ++
+    lruList[hit.lu] = hit
+  }
+
+  this.del = function (key) {
+    del(cache[key])
+  }
+
+  function trim () {
+    while (lru < mru && length > max)
+      del(lruList[lru])
+  }
+
+  function shiftLU(hit) {
+    delete lruList[ hit.lu ]
+    while (lru < mru && !lruList[lru]) lru ++
+  }
+
+  function del(hit) {
+    if (hit) {
+      if (dispose) dispose(hit.key, hit.value)
+      length -= hit.length
+      itemCount --
+      delete cache[ hit.key ]
+      shiftLU(hit)
+    }
+  }
+}
+
+// classy, since V8 prefers predictable objects.
+function Entry (key, value, mru, len, age) {
+  this.key = key
+  this.value = value
+  this.lu = mru
+  this.length = len
+  this.now = age
+}
+
+})()

File diff suppressed because it is too large
+ 59 - 0
node_modules/minimatch/node_modules/lru-cache/package.json


+ 25 - 0
node_modules/minimatch/node_modules/lru-cache/s.js

@@ -0,0 +1,25 @@
+var LRU = require('lru-cache');
+
+var max = +process.argv[2] || 10240;
+var more = 1024;
+
+var cache = LRU({
+  max: max, maxAge: 86400e3
+});
+
+// fill cache
+for (var i = 0; i < max; ++i) {
+  cache.set(i, {});
+}
+
+var start = process.hrtime();
+
+// adding more items
+for ( ; i < max+more; ++i) {
+  cache.set(i, {});
+}
+
+var end = process.hrtime(start);
+var msecs = end[0] * 1E3 + end[1] / 1E6;
+
+console.log('adding %d items took %d ms', more, msecs.toPrecision(5));

+ 329 - 0
node_modules/minimatch/node_modules/lru-cache/test/basic.js

@@ -0,0 +1,329 @@
+var test = require("tap").test
+  , LRU = require("../")
+
+test("basic", function (t) {
+  var cache = new LRU({max: 10})
+  cache.set("key", "value")
+  t.equal(cache.get("key"), "value")
+  t.equal(cache.get("nada"), undefined)
+  t.equal(cache.length, 1)
+  t.equal(cache.max, 10)
+  t.end()
+})
+
+test("least recently set", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.set("c", "C")
+  t.equal(cache.get("c"), "C")
+  t.equal(cache.get("b"), "B")
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("lru recently gotten", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.get("a")
+  cache.set("c", "C")
+  t.equal(cache.get("c"), "C")
+  t.equal(cache.get("b"), undefined)
+  t.equal(cache.get("a"), "A")
+  t.end()
+})
+
+test("del", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.del("a")
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("max", function (t) {
+  var cache = new LRU(3)
+
+  // test changing the max, verify that the LRU items get dropped.
+  cache.max = 100
+  for (var i = 0; i < 100; i ++) cache.set(i, i)
+  t.equal(cache.length, 100)
+  for (var i = 0; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+  cache.max = 3
+  t.equal(cache.length, 3)
+  for (var i = 0; i < 97; i ++) {
+    t.equal(cache.get(i), undefined)
+  }
+  for (var i = 98; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+
+  // now remove the max restriction, and try again.
+  cache.max = "hello"
+  for (var i = 0; i < 100; i ++) cache.set(i, i)
+  t.equal(cache.length, 100)
+  for (var i = 0; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+  // should trigger an immediate resize
+  cache.max = 3
+  t.equal(cache.length, 3)
+  for (var i = 0; i < 97; i ++) {
+    t.equal(cache.get(i), undefined)
+  }
+  for (var i = 98; i < 100; i ++) {
+    t.equal(cache.get(i), i)
+  }
+  t.end()
+})
+
+test("reset", function (t) {
+  var cache = new LRU(10)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  cache.reset()
+  t.equal(cache.length, 0)
+  t.equal(cache.max, 10)
+  t.equal(cache.get("a"), undefined)
+  t.equal(cache.get("b"), undefined)
+  t.end()
+})
+
+
+// Note: `<cache>.dump()` is a debugging tool only. No guarantees are made
+// about the format/layout of the response.
+test("dump", function (t) {
+  var cache = new LRU(10)
+  var d = cache.dump();
+  t.equal(Object.keys(d).length, 0, "nothing in dump for empty cache")
+  cache.set("a", "A")
+  var d = cache.dump()  // { a: { key: "a", value: "A", lu: 0 } }
+  t.ok(d.a)
+  t.equal(d.a.key, "a")
+  t.equal(d.a.value, "A")
+  t.equal(d.a.lu, 0)
+
+  cache.set("b", "B")
+  cache.get("b")
+  d = cache.dump()
+  t.ok(d.b)
+  t.equal(d.b.key, "b")
+  t.equal(d.b.value, "B")
+  t.equal(d.b.lu, 2)
+
+  t.end()
+})
+
+
+test("basic with weighed length", function (t) {
+  var cache = new LRU({
+    max: 100,
+    length: function (item) { return item.size }
+  })
+  cache.set("key", {val: "value", size: 50})
+  t.equal(cache.get("key").val, "value")
+  t.equal(cache.get("nada"), undefined)
+  t.equal(cache.lengthCalculator(cache.get("key")), 50)
+  t.equal(cache.length, 50)
+  t.equal(cache.max, 100)
+  t.end()
+})
+
+
+test("weighed length item too large", function (t) {
+  var cache = new LRU({
+    max: 10,
+    length: function (item) { return item.size }
+  })
+  t.equal(cache.max, 10)
+
+  // should fall out immediately
+  cache.set("key", {val: "value", size: 50})
+
+  t.equal(cache.length, 0)
+  t.equal(cache.get("key"), undefined)
+  t.end()
+})
+
+test("least recently set with weighed length", function (t) {
+  var cache = new LRU({
+    max:8,
+    length: function (item) { return item.length }
+  })
+  cache.set("a", "A")
+  cache.set("b", "BB")
+  cache.set("c", "CCC")
+  cache.set("d", "DDDD")
+  t.equal(cache.get("d"), "DDDD")
+  t.equal(cache.get("c"), "CCC")
+  t.equal(cache.get("b"), undefined)
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})
+
+test("lru recently gotten with weighed length", function (t) {
+  var cache = new LRU({
+    max: 8,
+    length: function (item) { return item.length }
+  })
+  cache.set("a", "A")
+  cache.set("b", "BB")
+  cache.set("c", "CCC")
+  cache.get("a")
+  cache.get("b")
+  cache.set("d", "DDDD")
+  t.equal(cache.get("c"), undefined)
+  t.equal(cache.get("d"), "DDDD")
+  t.equal(cache.get("b"), "BB")
+  t.equal(cache.get("a"), "A")
+  t.end()
+})
+
+test("set returns proper booleans", function(t) {
+  var cache = new LRU({
+    max: 5,
+    length: function (item) { return item.length }
+  })
+
+  t.equal(cache.set("a", "A"), true)
+
+  // should return false for max exceeded
+  t.equal(cache.set("b", "donuts"), false)
+
+  t.equal(cache.set("b", "B"), true)
+  t.equal(cache.set("c", "CCCC"), true)
+  t.end()
+})
+
+test("drop the old items", function(t) {
+  var cache = new LRU({
+    max: 5,
+    maxAge: 50
+  })
+
+  cache.set("a", "A")
+
+  setTimeout(function () {
+    cache.set("b", "b")
+    t.equal(cache.get("a"), "A")
+  }, 25)
+
+  setTimeout(function () {
+    cache.set("c", "C")
+    // timed out
+    t.notOk(cache.get("a"))
+  }, 60)
+
+  setTimeout(function () {
+    t.notOk(cache.get("b"))
+    t.equal(cache.get("c"), "C")
+  }, 90)
+
+  setTimeout(function () {
+    t.notOk(cache.get("c"))
+    t.end()
+  }, 155)
+})
+
+test("disposal function", function(t) {
+  var disposed = false
+  var cache = new LRU({
+    max: 1,
+    dispose: function (k, n) {
+      disposed = n
+    }
+  })
+
+  cache.set(1, 1)
+  cache.set(2, 2)
+  t.equal(disposed, 1)
+  cache.set(3, 3)
+  t.equal(disposed, 2)
+  cache.reset()
+  t.equal(disposed, 3)
+  t.end()
+})
+
+test("disposal function on too big of item", function(t) {
+  var disposed = false
+  var cache = new LRU({
+    max: 1,
+    length: function (k) {
+      return k.length
+    },
+    dispose: function (k, n) {
+      disposed = n
+    }
+  })
+  var obj = [ 1, 2 ]
+
+  t.equal(disposed, false)
+  cache.set("obj", obj)
+  t.equal(disposed, obj)
+  t.end()
+})
+
+test("has()", function(t) {
+  var cache = new LRU({
+    max: 1,
+    maxAge: 10
+  })
+
+  cache.set('foo', 'bar')
+  t.equal(cache.has('foo'), true)
+  cache.set('blu', 'baz')
+  t.equal(cache.has('foo'), false)
+  t.equal(cache.has('blu'), true)
+  setTimeout(function() {
+    t.equal(cache.has('blu'), false)
+    t.end()
+  }, 15)
+})
+
+test("stale", function(t) {
+  var cache = new LRU({
+    maxAge: 10,
+    stale: true
+  })
+
+  cache.set('foo', 'bar')
+  t.equal(cache.get('foo'), 'bar')
+  t.equal(cache.has('foo'), true)
+  setTimeout(function() {
+    t.equal(cache.has('foo'), false)
+    t.equal(cache.get('foo'), 'bar')
+    t.equal(cache.get('foo'), undefined)
+    t.end()
+  }, 15)
+})
+
+test("lru update via set", function(t) {
+  var cache = LRU({ max: 2 });
+
+  cache.set('foo', 1);
+  cache.set('bar', 2);
+  cache.del('bar');
+  cache.set('baz', 3);
+  cache.set('qux', 4);
+
+  t.equal(cache.get('foo'), undefined)
+  t.equal(cache.get('bar'), undefined)
+  t.equal(cache.get('baz'), 3)
+  t.equal(cache.get('qux'), 4)
+  t.end()
+})
+
+test("least recently set w/ peek", function (t) {
+  var cache = new LRU(2)
+  cache.set("a", "A")
+  cache.set("b", "B")
+  t.equal(cache.peek("a"), "A")
+  cache.set("c", "C")
+  t.equal(cache.get("c"), "C")
+  t.equal(cache.get("b"), "B")
+  t.equal(cache.get("a"), undefined)
+  t.end()
+})

+ 52 - 0
node_modules/minimatch/node_modules/lru-cache/test/foreach.js

@@ -0,0 +1,52 @@
+var test = require('tap').test
+var LRU = require('../')
+
+test('forEach', function (t) {
+  var l = new LRU(5)
+  for (var i = 0; i < 10; i ++) {
+    l.set(i.toString(), i.toString(2))
+  }
+
+  var i = 9
+  l.forEach(function (val, key, cache) {
+    t.equal(cache, l)
+    t.equal(key, i.toString())
+    t.equal(val, i.toString(2))
+    i -= 1
+  })
+
+  // get in order of most recently used
+  l.get(6)
+  l.get(8)
+
+  var order = [ 8, 6, 9, 7, 5 ]
+  var i = 0
+
+  l.forEach(function (val, key, cache) {
+    var j = order[i ++]
+    t.equal(cache, l)
+    t.equal(key, j.toString())
+    t.equal(val, j.toString(2))
+  })
+
+  t.end()
+})
+
+test('keys() and values()', function (t) {
+  var l = new LRU(5)
+  for (var i = 0; i < 10; i ++) {
+    l.set(i.toString(), i.toString(2))
+  }
+
+  t.similar(l.keys(), ['9', '8', '7', '6', '5'])
+  t.similar(l.values(), ['1001', '1000', '111', '110', '101'])
+
+  // get in order of most recently used
+  l.get(6)
+  l.get(8)
+
+  t.similar(l.keys(), ['8', '6', '9', '7', '5'])
+  t.similar(l.values(), ['1000', '110', '1001', '111', '101'])
+
+  t.end()
+})

+ 50 - 0
node_modules/minimatch/node_modules/lru-cache/test/memory-leak.js

@@ -0,0 +1,50 @@
+#!/usr/bin/env node --expose_gc
+
+var weak = require('weak');
+var test = require('tap').test
+var LRU = require('../')
+var l = new LRU({ max: 10 })
+var refs = 0
+function X() {
+  refs ++
+  weak(this, deref)
+}
+
+function deref() {
+  refs --
+}
+
+test('no leaks', function (t) {
+  // fill up the cache
+  for (var i = 0; i < 100; i++) {
+    l.set(i, new X);
+    // throw some gets in there, too.
+    if (i % 2 === 0)
+      l.get(i / 2)
+  }
+
+  gc()
+
+  var start = process.memoryUsage()
+
+  // capture the memory
+  var startRefs = refs
+
+  // do it again, but more
+  for (var i = 0; i < 10000; i++) {
+    l.set(i, new X);
+    // throw some gets in there, too.
+    if (i % 2 === 0)
+      l.get(i / 2)
+  }
+
+  gc()
+
+  var end = process.memoryUsage()
+  t.equal(refs, startRefs, 'no leaky refs')
+
+  console.error('start: %j\n' +
+                'end:   %j', start, end);
+  t.pass();
+  t.end();
+})

+ 27 - 0
node_modules/minimatch/node_modules/sigmund/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) Isaac Z. Schlueter ("Author")
+All rights reserved.
+
+The BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 53 - 0
node_modules/minimatch/node_modules/sigmund/README.md

@@ -0,0 +1,53 @@
+# sigmund
+
+Quick and dirty signatures for Objects.
+
+This is like a much faster `deepEquals` comparison, which returns a
+string key suitable for caches and the like.
+
+## Usage
+
+```javascript
+function doSomething (someObj) {
+  var key = sigmund(someObj, maxDepth) // max depth defaults to 10
+  var cached = cache.get(key)
+  if (cached) return cached)
+
+  var result = expensiveCalculation(someObj)
+  cache.set(key, result)
+  return result
+}
+```
+
+The resulting key will be as unique and reproducible as calling
+`JSON.stringify` or `util.inspect` on the object, but is much faster.
+In order to achieve this speed, some differences are glossed over.
+For example, the object `{0:'foo'}` will be treated identically to the
+array `['foo']`.
+
+Also, just as there is no way to summon the soul from the scribblings
+of a cocain-addled psychoanalyst, there is no way to revive the object
+from the signature string that sigmund gives you.  In fact, it's
+barely even readable.
+
+As with `sys.inspect` and `JSON.stringify`, larger objects will
+produce larger signature strings.
+
+Because sigmund is a bit less strict than the more thorough
+alternatives, the strings will be shorter, and also there is a
+slightly higher chance for collisions.  For example, these objects
+have the same signature:
+
+    var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}
+    var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}
+
+Like a good Freudian, sigmund is most effective when you already have
+some understanding of what you're looking for.  It can help you help
+yourself, but you must be willing to do some work as well.
+
+Cycles are handled, and cyclical objects are silently omitted (though
+the key is included in the signature output.)
+
+The second argument is the maximum depth, which defaults to 10,
+because that is the maximum object traversal depth covered by most
+insurance carriers.

+ 283 - 0
node_modules/minimatch/node_modules/sigmund/bench.js

@@ -0,0 +1,283 @@
+// different ways to id objects
+// use a req/res pair, since it's crazy deep and cyclical
+
+// sparseFE10 and sigmund are usually pretty close, which is to be expected,
+// since they are essentially the same algorithm, except that sigmund handles
+// regular expression objects properly.
+
+
+var http = require('http')
+var util = require('util')
+var sigmund = require('./sigmund.js')
+var sreq, sres, creq, cres, test
+
+http.createServer(function (q, s) {
+  sreq = q
+  sres = s
+  sres.end('ok')
+  this.close(function () { setTimeout(function () {
+    start()
+  }, 200) })
+}).listen(1337, function () {
+  creq = http.get({ port: 1337 })
+  creq.on('response', function (s) { cres = s })
+})
+
+function start () {
+  test = [sreq, sres, creq, cres]
+  // test = sreq
+  // sreq.sres = sres
+  // sreq.creq = creq
+  // sreq.cres = cres
+
+  for (var i in exports.compare) {
+    console.log(i)
+    var hash = exports.compare[i]()
+    console.log(hash)
+    console.log(hash.length)
+    console.log('')
+  }
+
+  require('bench').runMain()
+}
+
+function customWs (obj, md, d) {
+  d = d || 0
+  var to = typeof obj
+  if (to === 'undefined' || to === 'function' || to === null) return ''
+  if (d > md || !obj || to !== 'object') return ('' + obj).replace(/[\n ]+/g, '')
+
+  if (Array.isArray(obj)) {
+    return obj.map(function (i, _, __) {
+      return customWs(i, md, d + 1)
+    }).reduce(function (a, b) { return a + b }, '')
+  }
+
+  var keys = Object.keys(obj)
+  return keys.map(function (k, _, __) {
+    return k + ':' + customWs(obj[k], md, d + 1)
+  }).reduce(function (a, b) { return a + b }, '')
+}
+
+function custom (obj, md, d) {
+  d = d || 0
+  var to = typeof obj
+  if (to === 'undefined' || to === 'function' || to === null) return ''
+  if (d > md || !obj || to !== 'object') return '' + obj
+
+  if (Array.isArray(obj)) {
+    return obj.map(function (i, _, __) {
+      return custom(i, md, d + 1)
+    }).reduce(function (a, b) { return a + b }, '')
+  }
+
+  var keys = Object.keys(obj)
+  return keys.map(function (k, _, __) {
+    return k + ':' + custom(obj[k], md, d + 1)
+  }).reduce(function (a, b) { return a + b }, '')
+}
+
+function sparseFE2 (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    Object.keys(v).forEach(function (k, _, __) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') return
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') return
+      soFar += k + ':'
+      ch(v[k], depth + 1)
+    })
+    soFar += '}'
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+function sparseFE (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    Object.keys(v).forEach(function (k, _, __) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') return
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') return
+      soFar += k
+      ch(v[k], depth + 1)
+    })
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+function sparse (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    for (var k in v) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') continue
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') continue
+      soFar += k
+      ch(v[k], depth + 1)
+    }
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+function noCommas (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    for (var k in v) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') continue
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') continue
+      soFar += k + ':'
+      ch(v[k], depth + 1)
+    }
+    soFar += '}'
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+
+function flatten (obj, maxDepth) {
+  var seen = []
+  var soFar = ''
+  function ch (v, depth) {
+    if (depth > maxDepth) return
+    if (typeof v === 'function' || typeof v === 'undefined') return
+    if (typeof v !== 'object' || !v) {
+      soFar += v
+      return
+    }
+    if (seen.indexOf(v) !== -1 || depth === maxDepth) return
+    seen.push(v)
+    soFar += '{'
+    for (var k in v) {
+      // pseudo-private values.  skip those.
+      if (k.charAt(0) === '_') continue
+      var to = typeof v[k]
+      if (to === 'function' || to === 'undefined') continue
+      soFar += k + ':'
+      ch(v[k], depth + 1)
+      soFar += ','
+    }
+    soFar += '}'
+  }
+  ch(obj, 0)
+  return soFar
+}
+
+exports.compare =
+{
+  // 'custom 2': function () {
+  //   return custom(test, 2, 0)
+  // },
+  // 'customWs 2': function () {
+  //   return customWs(test, 2, 0)
+  // },
+  'JSON.stringify (guarded)': function () {
+    var seen = []
+    return JSON.stringify(test, function (k, v) {
+      if (typeof v !== 'object' || !v) return v
+      if (seen.indexOf(v) !== -1) return undefined
+      seen.push(v)
+      return v
+    })
+  },
+
+  'flatten 10': function () {
+    return flatten(test, 10)
+  },
+
+  // 'flattenFE 10': function () {
+  //   return flattenFE(test, 10)
+  // },
+
+  'noCommas 10': function () {
+    return noCommas(test, 10)
+  },
+
+  'sparse 10': function () {
+    return sparse(test, 10)
+  },
+
+  'sparseFE 10': function () {
+    return sparseFE(test, 10)
+  },
+
+  'sparseFE2 10': function () {
+    return sparseFE2(test, 10)
+  },
+
+  sigmund: function() {
+    return sigmund(test, 10)
+  },
+
+
+  // 'util.inspect 1': function () {
+  //   return util.inspect(test, false, 1, false)
+  // },
+  // 'util.inspect undefined': function () {
+  //   util.inspect(test)
+  // },
+  // 'util.inspect 2': function () {
+  //   util.inspect(test, false, 2, false)
+  // },
+  // 'util.inspect 3': function () {
+  //   util.inspect(test, false, 3, false)
+  // },
+  // 'util.inspect 4': function () {
+  //   util.inspect(test, false, 4, false)
+  // },
+  // 'util.inspect Infinity': function () {
+  //   util.inspect(test, false, Infinity, false)
+  // }
+}
+
+/** results
+**/

File diff suppressed because it is too large
+ 38 - 0
node_modules/minimatch/node_modules/sigmund/package.json


+ 39 - 0
node_modules/minimatch/node_modules/sigmund/sigmund.js

@@ -0,0 +1,39 @@
+module.exports = sigmund
+function sigmund (subject, maxSessions) {
+    maxSessions = maxSessions || 10;
+    var notes = [];
+    var analysis = '';
+    var RE = RegExp;
+
+    function psychoAnalyze (subject, session) {
+        if (session > maxSessions) return;
+
+        if (typeof subject === 'function' ||
+            typeof subject === 'undefined') {
+            return;
+        }
+
+        if (typeof subject !== 'object' || !subject ||
+            (subject instanceof RE)) {
+            analysis += subject;
+            return;
+        }
+
+        if (notes.indexOf(subject) !== -1 || session === maxSessions) return;
+
+        notes.push(subject);
+        analysis += '{';
+        Object.keys(subject).forEach(function (issue, _, __) {
+            // pseudo-private values.  skip those.
+            if (issue.charAt(0) === '_') return;
+            var to = typeof subject[issue];
+            if (to === 'function' || to === 'undefined') return;
+            analysis += issue;
+            psychoAnalyze(subject[issue], session + 1);
+        });
+    }
+    psychoAnalyze(subject, 0);
+    return analysis;
+}
+
+// vim: set softtabstop=4 shiftwidth=4:

+ 24 - 0
node_modules/minimatch/node_modules/sigmund/test/basic.js

@@ -0,0 +1,24 @@
+var test = require('tap').test
+var sigmund = require('../sigmund.js')
+
+
+// occasionally there are duplicates
+// that's an acceptable edge-case.  JSON.stringify and util.inspect
+// have some collision potential as well, though less, and collision
+// detection is expensive.
+var hash = '{abc/def/g{0h1i2{jkl'
+var obj1 = {a:'b',c:/def/,g:['h','i',{j:'',k:'l'}]}
+var obj2 = {a:'b',c:'/def/',g:['h','i','{jkl']}
+
+var obj3 = JSON.parse(JSON.stringify(obj1))
+obj3.c = /def/
+obj3.g[2].cycle = obj3
+var cycleHash = '{abc/def/g{0h1i2{jklcycle'
+
+test('basic', function (t) {
+    t.equal(sigmund(obj1), hash)
+    t.equal(sigmund(obj2), hash)
+    t.equal(sigmund(obj3), cycleHash)
+    t.end()
+})
+

File diff suppressed because it is too large
+ 36 - 0
node_modules/minimatch/package.json


+ 399 - 0
node_modules/minimatch/test/basic.js

@@ -0,0 +1,399 @@
+// http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test
+//
+// TODO: Some of these tests do very bad things with backslashes, and will
+// most likely fail badly on windows.  They should probably be skipped.
+
+var tap = require("tap")
+  , globalBefore = Object.keys(global)
+  , mm = require("../")
+  , files = [ "a", "b", "c", "d", "abc"
+            , "abd", "abe", "bb", "bcd"
+            , "ca", "cb", "dd", "de"
+            , "bdir/", "bdir/cfile"]
+  , next = files.concat([ "a-b", "aXb"
+                        , ".x", ".y" ])
+
+
+var patterns =
+  [ "http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test"
+  , ["a*", ["a", "abc", "abd", "abe"]]
+  , ["X*", ["X*"], {nonull: true}]
+
+  // allow null glob expansion
+  , ["X*", []]
+
+  // isaacs: Slightly different than bash/sh/ksh
+  // \\* is not un-escaped to literal "*" in a failed match,
+  // but it does make it get treated as a literal star
+  , ["\\*", ["\\*"], {nonull: true}]
+  , ["\\**", ["\\**"], {nonull: true}]
+  , ["\\*\\*", ["\\*\\*"], {nonull: true}]
+
+  , ["b*/", ["bdir/"]]
+  , ["c*", ["c", "ca", "cb"]]
+  , ["**", files]
+
+  , ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
+  , ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
+
+  , "legendary larry crashes bashes"
+  , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
+  , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
+
+  , "character classes"
+  , ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
+  , ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
+     "bdir/", "ca", "cb", "dd", "de"]]
+  , ["a*[^c]", ["abd", "abe"]]
+  , function () { files.push("a-b", "aXb") }
+  , ["a[X-]b", ["a-b", "aXb"]]
+  , function () { files.push(".x", ".y") }
+  , ["[^a-c]*", ["d", "dd", "de"]]
+  , function () { files.push("a*b/", "a*b/ooo") }
+  , ["a\\*b/*", ["a*b/ooo"]]
+  , ["a\\*?/*", ["a*b/ooo"]]
+  , ["*\\\\!*", [], {null: true}, ["echo !7"]]
+  , ["*\\!*", ["echo !7"], null, ["echo !7"]]
+  , ["*.\\*", ["r.*"], null, ["r.*"]]
+  , ["a[b]c", ["abc"]]
+  , ["a[\\b]c", ["abc"]]
+  , ["a?c", ["abc"]]
+  , ["a\\*c", [], {null: true}, ["abc"]]
+  , ["", [""], { null: true }, [""]]
+
+  , "http://www.opensource.apple.com/source/bash/bash-23/" +
+    "bash/tests/glob-test"
+  , function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
+  , ["*/man*/bash.*", ["man/man1/bash.1"]]
+  , ["man/man1/bash.1", ["man/man1/bash.1"]]
+  , ["a***c", ["abc"], null, ["abc"]]
+  , ["a*****?c", ["abc"], null, ["abc"]]
+  , ["?*****??", ["abc"], null, ["abc"]]
+  , ["*****??", ["abc"], null, ["abc"]]
+  , ["?*****?c", ["abc"], null, ["abc"]]
+  , ["?***?****c", ["abc"], null, ["abc"]]
+  , ["?***?****?", ["abc"], null, ["abc"]]
+  , ["?***?****", ["abc"], null, ["abc"]]
+  , ["*******c", ["abc"], null, ["abc"]]
+  , ["*******?", ["abc"], null, ["abc"]]
+  , ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+  , ["[-abc]", ["-"], null, ["-"]]
+  , ["[abc-]", ["-"], null, ["-"]]
+  , ["\\", ["\\"], null, ["\\"]]
+  , ["[\\\\]", ["\\"], null, ["\\"]]
+  , ["[[]", ["["], null, ["["]]
+  , ["[", ["["], null, ["["]]
+  , ["[*", ["[abc"], null, ["[abc"]]
+  , "a right bracket shall lose its special meaning and\n" +
+    "represent itself in a bracket expression if it occurs\n" +
+    "first in the list.  -- POSIX.2 2.8.3.2"
+  , ["[]]", ["]"], null, ["]"]]
+  , ["[]-]", ["]"], null, ["]"]]
+  , ["[a-\z]", ["p"], null, ["p"]]
+  , ["??**********?****?", [], { null: true }, ["abc"]]
+  , ["??**********?****c", [], { null: true }, ["abc"]]
+  , ["?************c****?****", [], { null: true }, ["abc"]]
+  , ["*c*?**", [], { null: true }, ["abc"]]
+  , ["a*****c*?**", [], { null: true }, ["abc"]]
+  , ["a********???*******", [], { null: true }, ["abc"]]
+  , ["[]", [], { null: true }, ["a"]]
+  , ["[abc", [], { null: true }, ["["]]
+
+  , "nocase tests"
+  , ["XYZ", ["xYz"], { nocase: true, null: true }
+    , ["xYz", "ABC", "IjK"]]
+  , ["ab*", ["ABC"], { nocase: true, null: true }
+    , ["xYz", "ABC", "IjK"]]
+  , ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
+    , ["xYz", "ABC", "IjK"]]
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  , "onestar/twostar"
+  , ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
+  , ["{/?,*}", ["/a", "bb"], {null: true}
+    , ["/a", "/b/b", "/a/b/c", "bb"]]
+
+  , "dots should not match unless requested"
+  , ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
+
+  // .. and . can only match patterns starting with .,
+  // even when options.dot is set.
+  , function () {
+      files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
+    }
+  , ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
+  , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
+  , ["a/*/b", ["a/c/b"], {dot:false}]
+  , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
+
+
+  // this also tests that changing the options needs
+  // to change the cache key, even if the pattern is
+  // the same!
+  , ["**", ["a/b","a/.d",".a/.d"], { dot: true }
+    , [ ".a/.d", "a/.d", "a/b"]]
+
+  , "paren sets cannot contain slashes"
+  , ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
+
+  // brace sets trump all else.
+  //
+  // invalid glob pattern.  fails on bash4 and bsdglob.
+  // however, in this implementation, it's easier just
+  // to do the intuitive thing, and let brace-expansion
+  // actually come before parsing any extglob patterns,
+  // like the documentation seems to say.
+  //
+  // XXX: if anyone complains about this, either fix it
+  // or tell them to grow up and stop complaining.
+  //
+  // bash/bsdglob says this:
+  // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
+  // but we do this instead:
+  , ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
+
+  // test partial parsing in the presence of comment/negation chars
+  , ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
+  , ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
+
+  // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
+  , ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
+    , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
+    , {}
+    , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
+
+
+  // crazy nested {,,} and *(||) tests.
+  , function () {
+      files = [ "a", "b", "c", "d"
+              , "ab", "ac", "ad"
+              , "bc", "cb"
+              , "bc,d", "c,db", "c,d"
+              , "d)", "(b|c", "*(b|c"
+              , "b|c", "b|cc", "cb|c"
+              , "x(a|b|c)", "x(a|c)"
+              , "(a|b|c)", "(a|c)"]
+    }
+  , ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
+  , ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
+  // a
+  // *(b|c)
+  // *(b|d)
+  , ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
+  , ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
+
+
+  // test various flag settings.
+  , [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
+    , { noext: true } ]
+  , ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
+    , ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
+  , ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
+
+
+  // begin channelling Boole and deMorgan...
+  , "negation tests"
+  , function () {
+      files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
+    }
+
+  // anything that is NOT a* matches.
+  , ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
+
+  // anything that IS !a* matches.
+  , ["!a*", ["!ab", "!abc"], {nonegate: true}]
+
+  // anything that IS a* matches
+  , ["!!a*", ["a!b"]]
+
+  // anything that is NOT !a* matches
+  , ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
+
+  // negation nestled within a pattern
+  , function () {
+      files = [ "foo.js"
+              , "foo.bar"
+              // can't match this one without negative lookbehind.
+              , "foo.js.js"
+              , "blar.js"
+              , "foo."
+              , "boo.js.boo" ]
+    }
+  , ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
+
+  // https://github.com/isaacs/minimatch/issues/5
+  , function () {
+      files = [ 'a/b/.x/c'
+              , 'a/b/.x/c/d'
+              , 'a/b/.x/c/d/e'
+              , 'a/b/.x'
+              , 'a/b/.x/'
+              , 'a/.x/b'
+              , '.x'
+              , '.x/'
+              , '.x/a'
+              , '.x/a/b'
+              , 'a/.x/b/.x/c'
+              , '.x/.x' ]
+  }
+  , ["**/.x/**", [ '.x/'
+                 , '.x/a'
+                 , '.x/a/b'
+                 , 'a/.x/b'
+                 , 'a/b/.x/'
+                 , 'a/b/.x/c'
+                 , 'a/b/.x/c/d'
+                 , 'a/b/.x/c/d/e' ] ]
+
+  ]
+
+var regexps =
+  [ '/^(?:(?=.)a[^/]*?)$/',
+    '/^(?:(?=.)X[^/]*?)$/',
+    '/^(?:(?=.)X[^/]*?)$/',
+    '/^(?:\\*)$/',
+    '/^(?:(?=.)\\*[^/]*?)$/',
+    '/^(?:\\*\\*)$/',
+    '/^(?:(?=.)b[^/]*?\\/)$/',
+    '/^(?:(?=.)c[^/]*?)$/',
+    '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
+    '/^(?:\\.\\.\\/(?!\\.)(?=.)[^/]*?\\/)$/',
+    '/^(?:s\\/(?=.)\\.\\.[^/]*?\\/)$/',
+    '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/1\\/)$/',
+    '/^(?:\\/\\^root:\\/\\{s\\/(?=.)\\^[^:][^/]*?:[^:][^/]*?:\\([^:]\\)[^/]*?\\.[^/]*?\\$\\/\u0001\\/)$/',
+    '/^(?:(?!\\.)(?=.)[a-c]b[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[a-y][^/]*?[^c])$/',
+    '/^(?:(?=.)a[^/]*?[^c])$/',
+    '/^(?:(?=.)a[X-]b)$/',
+    '/^(?:(?!\\.)(?=.)[^a-c][^/]*?)$/',
+    '/^(?:a\\*b\\/(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?=.)a\\*[^/]\\/(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\\\\\![^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\![^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\.\\*)$/',
+    '/^(?:(?=.)a[b]c)$/',
+    '/^(?:(?=.)a[b]c)$/',
+    '/^(?:(?=.)a[^/]c)$/',
+    '/^(?:a\\*c)$/',
+    'false',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\/(?=.)man[^/]*?\\/(?=.)bash\\.[^/]*?)$/',
+    '/^(?:man\\/man1\\/bash\\.1)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+    '/^(?:(?=.)a[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/]k[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/][^/]*?[^/]*?cd[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?k[^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[-abc])$/',
+    '/^(?:(?!\\.)(?=.)[abc-])$/',
+    '/^(?:\\\\)$/',
+    '/^(?:(?!\\.)(?=.)[\\\\])$/',
+    '/^(?:(?!\\.)(?=.)[\\[])$/',
+    '/^(?:\\[)$/',
+    '/^(?:(?=.)\\[(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[\\]])$/',
+    '/^(?:(?!\\.)(?=.)[\\]-])$/',
+    '/^(?:(?!\\.)(?=.)[a-z])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/])$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?c)$/',
+    '/^(?:(?!\\.)(?=.)[^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?c[^/]*?[^/][^/]*?[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/][^/][^/][^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?)$/',
+    '/^(?:\\[\\])$/',
+    '/^(?:\\[abc)$/',
+    '/^(?:(?=.)XYZ)$/i',
+    '/^(?:(?=.)ab[^/]*?)$/i',
+    '/^(?:(?!\\.)(?=.)[ia][^/][ck])$/i',
+    '/^(?:\\/(?!\\.)(?=.)[^/]*?|(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:\\/(?!\\.)(?=.)[^/]|(?!\\.)(?=.)[^/]*?)$/',
+    '/^(?:(?:(?!(?:\\/|^)\\.).)*?)$/',
+    '/^(?:a\\/(?!(?:^|\\/)\\.{1,2}(?:$|\\/))(?=.)[^/]*?\\/b)$/',
+    '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
+    '/^(?:a\\/(?!\\.)(?=.)[^/]*?\\/b)$/',
+    '/^(?:a\\/(?=.)\\.[^/]*?\\/b)$/',
+    '/^(?:(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\/b\\))$/',
+    '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
+    '/^(?:(?=.)\\[(?=.)\\!a[^/]*?)$/',
+    '/^(?:(?=.)\\[(?=.)#a[^/]*?)$/',
+    '/^(?:(?=.)\\+\\(a\\|[^/]*?\\|c\\\\\\\\\\|d\\\\\\\\\\|e\\\\\\\\\\\\\\\\\\|f\\\\\\\\\\\\\\\\\\|g)$/',
+    '/^(?:(?!\\.)(?=.)(?:a|b)*|(?!\\.)(?=.)(?:a|c)*)$/',
+    '/^(?:a|(?!\\.)(?=.)[^/]*?\\(b\\|c|d\\))$/',
+    '/^(?:a|(?!\\.)(?=.)(?:b|c)*|(?!\\.)(?=.)(?:b|d)*)$/',
+    '/^(?:(?!\\.)(?=.)(?:a|b|c)*|(?!\\.)(?=.)(?:a|c)*)$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\(a\\|b\\|c\\)|(?!\\.)(?=.)[^/]*?\\(a\\|c\\))$/',
+    '/^(?:(?=.)a[^/]b)$/',
+    '/^(?:(?=.)#[^/]*?)$/',
+    '/^(?!^(?:(?=.)a[^/]*?)$).*$/',
+    '/^(?:(?=.)\\!a[^/]*?)$/',
+    '/^(?:(?=.)a[^/]*?)$/',
+    '/^(?!^(?:(?=.)\\!a[^/]*?)$).*$/',
+    '/^(?:(?!\\.)(?=.)[^/]*?\\.(?:(?!js)[^/]*?))$/',
+    '/^(?:(?:(?!(?:\\/|^)\\.).)*?\\/\\.x\\/(?:(?!(?:\\/|^)\\.).)*?)$/' ]
+var re = 0;
+
+tap.test("basic tests", function (t) {
+  var start = Date.now()
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  patterns.forEach(function (c) {
+    if (typeof c === "function") return c()
+    if (typeof c === "string") return t.comment(c)
+
+    var pattern = c[0]
+      , expect = c[1].sort(alpha)
+      , options = c[2] || {}
+      , f = c[3] || files
+      , tapOpts = c[4] || {}
+
+    // options.debug = true
+    var m = new mm.Minimatch(pattern, options)
+    var r = m.makeRe()
+    var expectRe = regexps[re++]
+    tapOpts.re = String(r) || JSON.stringify(r)
+    tapOpts.files = JSON.stringify(f)
+    tapOpts.pattern = pattern
+    tapOpts.set = m.set
+    tapOpts.negated = m.negate
+
+    var actual = mm.match(f, pattern, options)
+    actual.sort(alpha)
+
+    t.equivalent( actual, expect
+                , JSON.stringify(pattern) + " " + JSON.stringify(expect)
+                , tapOpts )
+
+    t.equal(tapOpts.re, expectRe, tapOpts)
+  })
+
+  t.comment("time=" + (Date.now() - start) + "ms")
+  t.end()
+})
+
+tap.test("global leak test", function (t) {
+  var globalAfter = Object.keys(global)
+  t.equivalent(globalAfter, globalBefore, "no new globals, please")
+  t.end()
+})
+
+function alpha (a, b) {
+  return a > b ? 1 : -1
+}

+ 33 - 0
node_modules/minimatch/test/brace-expand.js

@@ -0,0 +1,33 @@
+var tap = require("tap")
+  , minimatch = require("../")
+
+tap.test("brace expansion", function (t) {
+  // [ pattern, [expanded] ]
+  ; [ [ "a{b,c{d,e},{f,g}h}x{y,z}"
+      , [ "abxy"
+        , "abxz"
+        , "acdxy"
+        , "acdxz"
+        , "acexy"
+        , "acexz"
+        , "afhxy"
+        , "afhxz"
+        , "aghxy"
+        , "aghxz" ] ]
+    , [ "a{1..5}b"
+      , [ "a1b"
+        , "a2b"
+        , "a3b"
+        , "a4b"
+        , "a5b" ] ]
+    , [ "a{b}c", ["a{b}c"] ]
+  ].forEach(function (tc) {
+    var p = tc[0]
+      , expect = tc[1]
+    t.equivalent(minimatch.braceExpand(p), expect, p)
+  })
+  console.error("ending")
+  t.end()
+})
+
+

+ 14 - 0
node_modules/minimatch/test/caching.js

@@ -0,0 +1,14 @@
+var Minimatch = require("../minimatch.js").Minimatch
+var tap = require("tap")
+tap.test("cache test", function (t) {
+  var mm1 = new Minimatch("a?b")
+  var mm2 = new Minimatch("a?b")
+  t.equal(mm1, mm2, "should get the same object")
+  // the lru should drop it after 100 entries
+  for (var i = 0; i < 100; i ++) {
+    new Minimatch("a"+i)
+  }
+  mm2 = new Minimatch("a?b")
+  t.notEqual(mm1, mm2, "cache should have dropped")
+  t.end()
+})

+ 274 - 0
node_modules/minimatch/test/defaults.js

@@ -0,0 +1,274 @@
+// http://www.bashcookbook.com/bashinfo/source/bash-1.14.7/tests/glob-test
+//
+// TODO: Some of these tests do very bad things with backslashes, and will
+// most likely fail badly on windows.  They should probably be skipped.
+
+var tap = require("tap")
+  , globalBefore = Object.keys(global)
+  , mm = require("../")
+  , files = [ "a", "b", "c", "d", "abc"
+            , "abd", "abe", "bb", "bcd"
+            , "ca", "cb", "dd", "de"
+            , "bdir/", "bdir/cfile"]
+  , next = files.concat([ "a-b", "aXb"
+                        , ".x", ".y" ])
+
+tap.test("basic tests", function (t) {
+  var start = Date.now()
+
+  // [ pattern, [matches], MM opts, files, TAP opts]
+  ; [ "http://www.bashcookbook.com/bashinfo" +
+      "/source/bash-1.14.7/tests/glob-test"
+    , ["a*", ["a", "abc", "abd", "abe"]]
+    , ["X*", ["X*"], {nonull: true}]
+
+    // allow null glob expansion
+    , ["X*", []]
+
+    // isaacs: Slightly different than bash/sh/ksh
+    // \\* is not un-escaped to literal "*" in a failed match,
+    // but it does make it get treated as a literal star
+    , ["\\*", ["\\*"], {nonull: true}]
+    , ["\\**", ["\\**"], {nonull: true}]
+    , ["\\*\\*", ["\\*\\*"], {nonull: true}]
+
+    , ["b*/", ["bdir/"]]
+    , ["c*", ["c", "ca", "cb"]]
+    , ["**", files]
+
+    , ["\\.\\./*/", ["\\.\\./*/"], {nonull: true}]
+    , ["s/\\..*//", ["s/\\..*//"], {nonull: true}]
+
+    , "legendary larry crashes bashes"
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"
+      , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\\1/"], {nonull: true}]
+    , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"
+      , ["/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/"], {nonull: true}]
+
+    , "character classes"
+    , ["[a-c]b*", ["abc", "abd", "abe", "bb", "cb"]]
+    , ["[a-y]*[^c]", ["abd", "abe", "bb", "bcd",
+       "bdir/", "ca", "cb", "dd", "de"]]
+    , ["a*[^c]", ["abd", "abe"]]
+    , function () { files.push("a-b", "aXb") }
+    , ["a[X-]b", ["a-b", "aXb"]]
+    , function () { files.push(".x", ".y") }
+    , ["[^a-c]*", ["d", "dd", "de"]]
+    , function () { files.push("a*b/", "a*b/ooo") }
+    , ["a\\*b/*", ["a*b/ooo"]]
+    , ["a\\*?/*", ["a*b/ooo"]]
+    , ["*\\\\!*", [], {null: true}, ["echo !7"]]
+    , ["*\\!*", ["echo !7"], null, ["echo !7"]]
+    , ["*.\\*", ["r.*"], null, ["r.*"]]
+    , ["a[b]c", ["abc"]]
+    , ["a[\\b]c", ["abc"]]
+    , ["a?c", ["abc"]]
+    , ["a\\*c", [], {null: true}, ["abc"]]
+    , ["", [""], { null: true }, [""]]
+
+    , "http://www.opensource.apple.com/source/bash/bash-23/" +
+      "bash/tests/glob-test"
+    , function () { files.push("man/", "man/man1/", "man/man1/bash.1") }
+    , ["*/man*/bash.*", ["man/man1/bash.1"]]
+    , ["man/man1/bash.1", ["man/man1/bash.1"]]
+    , ["a***c", ["abc"], null, ["abc"]]
+    , ["a*****?c", ["abc"], null, ["abc"]]
+    , ["?*****??", ["abc"], null, ["abc"]]
+    , ["*****??", ["abc"], null, ["abc"]]
+    , ["?*****?c", ["abc"], null, ["abc"]]
+    , ["?***?****c", ["abc"], null, ["abc"]]
+    , ["?***?****?", ["abc"], null, ["abc"]]
+    , ["?***?****", ["abc"], null, ["abc"]]
+    , ["*******c", ["abc"], null, ["abc"]]
+    , ["*******?", ["abc"], null, ["abc"]]
+    , ["a*cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??k***", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??***k", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a**?**cd**?**??***k**", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["a****c**?**??*****", ["abcdecdhjk"], null, ["abcdecdhjk"]]
+    , ["[-abc]", ["-"], null, ["-"]]
+    , ["[abc-]", ["-"], null, ["-"]]
+    , ["\\", ["\\"], null, ["\\"]]
+    , ["[\\\\]", ["\\"], null, ["\\"]]
+    , ["[[]", ["["], null, ["["]]
+    , ["[", ["["], null, ["["]]
+    , ["[*", ["[abc"], null, ["[abc"]]
+    , "a right bracket shall lose its special meaning and\n" +
+      "represent itself in a bracket expression if it occurs\n" +
+      "first in the list.  -- POSIX.2 2.8.3.2"
+    , ["[]]", ["]"], null, ["]"]]
+    , ["[]-]", ["]"], null, ["]"]]
+    , ["[a-\z]", ["p"], null, ["p"]]
+    , ["??**********?****?", [], { null: true }, ["abc"]]
+    , ["??**********?****c", [], { null: true }, ["abc"]]
+    , ["?************c****?****", [], { null: true }, ["abc"]]
+    , ["*c*?**", [], { null: true }, ["abc"]]
+    , ["a*****c*?**", [], { null: true }, ["abc"]]
+    , ["a********???*******", [], { null: true }, ["abc"]]
+    , ["[]", [], { null: true }, ["a"]]
+    , ["[abc", [], { null: true }, ["["]]
+
+    , "nocase tests"
+    , ["XYZ", ["xYz"], { nocase: true, null: true }
+      , ["xYz", "ABC", "IjK"]]
+    , ["ab*", ["ABC"], { nocase: true, null: true }
+      , ["xYz", "ABC", "IjK"]]
+    , ["[ia]?[ck]", ["ABC", "IjK"], { nocase: true, null: true }
+      , ["xYz", "ABC", "IjK"]]
+
+    // [ pattern, [matches], MM opts, files, TAP opts]
+    , "onestar/twostar"
+    , ["{/*,*}", [], {null: true}, ["/asdf/asdf/asdf"]]
+    , ["{/?,*}", ["/a", "bb"], {null: true}
+      , ["/a", "/b/b", "/a/b/c", "bb"]]
+
+    , "dots should not match unless requested"
+    , ["**", ["a/b"], {}, ["a/b", "a/.d", ".a/.d"]]
+
+    // .. and . can only match patterns starting with .,
+    // even when options.dot is set.
+    , function () {
+        files = ["a/./b", "a/../b", "a/c/b", "a/.d/b"]
+      }
+    , ["a/*/b", ["a/c/b", "a/.d/b"], {dot: true}]
+    , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: true}]
+    , ["a/*/b", ["a/c/b"], {dot:false}]
+    , ["a/.*/b", ["a/./b", "a/../b", "a/.d/b"], {dot: false}]
+
+
+    // this also tests that changing the options needs
+    // to change the cache key, even if the pattern is
+    // the same!
+    , ["**", ["a/b","a/.d",".a/.d"], { dot: true }
+      , [ ".a/.d", "a/.d", "a/b"]]
+
+    , "paren sets cannot contain slashes"
+    , ["*(a/b)", ["*(a/b)"], {nonull: true}, ["a/b"]]
+
+    // brace sets trump all else.
+    //
+    // invalid glob pattern.  fails on bash4 and bsdglob.
+    // however, in this implementation, it's easier just
+    // to do the intuitive thing, and let brace-expansion
+    // actually come before parsing any extglob patterns,
+    // like the documentation seems to say.
+    //
+    // XXX: if anyone complains about this, either fix it
+    // or tell them to grow up and stop complaining.
+    //
+    // bash/bsdglob says this:
+    // , ["*(a|{b),c)}", ["*(a|{b),c)}"], {}, ["a", "ab", "ac", "ad"]]
+    // but we do this instead:
+    , ["*(a|{b),c)}", ["a", "ab", "ac"], {}, ["a", "ab", "ac", "ad"]]
+
+    // test partial parsing in the presence of comment/negation chars
+    , ["[!a*", ["[!ab"], {}, ["[!ab", "[ab"]]
+    , ["[#a*", ["[#ab"], {}, ["[#ab", "[ab"]]
+
+    // like: {a,b|c\\,d\\\|e} except it's unclosed, so it has to be escaped.
+    , ["+(a|*\\|c\\\\|d\\\\\\|e\\\\\\\\|f\\\\\\\\\\|g"
+      , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g"]
+      , {}
+      , ["+(a|b\\|c\\\\|d\\\\|e\\\\\\\\|f\\\\\\\\|g", "a", "b\\c"]]
+
+
+    // crazy nested {,,} and *(||) tests.
+    , function () {
+        files = [ "a", "b", "c", "d"
+                , "ab", "ac", "ad"
+                , "bc", "cb"
+                , "bc,d", "c,db", "c,d"
+                , "d)", "(b|c", "*(b|c"
+                , "b|c", "b|cc", "cb|c"
+                , "x(a|b|c)", "x(a|c)"
+                , "(a|b|c)", "(a|c)"]
+      }
+    , ["*(a|{b,c})", ["a", "b", "c", "ab", "ac"]]
+    , ["{a,*(b|c,d)}", ["a","(b|c", "*(b|c", "d)"]]
+    // a
+    // *(b|c)
+    // *(b|d)
+    , ["{a,*(b|{c,d})}", ["a","b", "bc", "cb", "c", "d"]]
+    , ["*(a|{b|c,c})", ["a", "b", "c", "ab", "ac", "bc", "cb"]]
+
+
+    // test various flag settings.
+    , [ "*(a|{b|c,c})", ["x(a|b|c)", "x(a|c)", "(a|b|c)", "(a|c)"]
+      , { noext: true } ]
+    , ["a?b", ["x/y/acb", "acb/"], {matchBase: true}
+      , ["x/y/acb", "acb/", "acb/d/e", "x/y/acb/d"] ]
+    , ["#*", ["#a", "#b"], {nocomment: true}, ["#a", "#b", "c#d"]]
+
+
+    // begin channelling Boole and deMorgan...
+    , "negation tests"
+    , function () {
+        files = ["d", "e", "!ab", "!abc", "a!b", "\\!a"]
+      }
+
+    // anything that is NOT a* matches.
+    , ["!a*", ["\\!a", "d", "e", "!ab", "!abc"]]
+
+    // anything that IS !a* matches.
+    , ["!a*", ["!ab", "!abc"], {nonegate: true}]
+
+    // anything that IS a* matches
+    , ["!!a*", ["a!b"]]
+
+    // anything that is NOT !a* matches
+    , ["!\\!a*", ["a!b", "d", "e", "\\!a"]]
+
+    // negation nestled within a pattern
+    , function () {
+        files = [ "foo.js"
+                , "foo.bar"
+                // can't match this one without negative lookbehind.
+                , "foo.js.js"
+                , "blar.js"
+                , "foo."
+                , "boo.js.boo" ]
+      }
+    , ["*.!(js)", ["foo.bar", "foo.", "boo.js.boo"] ]
+
+    ].forEach(function (c) {
+      if (typeof c === "function") return c()
+      if (typeof c === "string") return t.comment(c)
+
+      var pattern = c[0]
+        , expect = c[1].sort(alpha)
+        , options = c[2] || {}
+        , f = c[3] || files
+        , tapOpts = c[4] || {}
+
+      // options.debug = true
+      var Class = mm.defaults(options).Minimatch
+      var m = new Class(pattern, {})
+      var r = m.makeRe()
+      tapOpts.re = String(r) || JSON.stringify(r)
+      tapOpts.files = JSON.stringify(f)
+      tapOpts.pattern = pattern
+      tapOpts.set = m.set
+      tapOpts.negated = m.negate
+
+      var actual = mm.match(f, pattern, options)
+      actual.sort(alpha)
+
+      t.equivalent( actual, expect
+                  , JSON.stringify(pattern) + " " + JSON.stringify(expect)
+                  , tapOpts )
+    })
+
+  t.comment("time=" + (Date.now() - start) + "ms")
+  t.end()
+})
+
+tap.test("global leak test", function (t) {
+  var globalAfter = Object.keys(global)
+  t.equivalent(globalAfter, globalBefore, "no new globals, please")
+  t.end()
+})
+
+function alpha (a, b) {
+  return a > b ? 1 : -1
+}

+ 1 - 1
package.json

@@ -16,7 +16,7 @@
     "async": "~0.2.5",
     "async": "~0.2.5",
     "cron": "~1.0.1",
     "cron": "~1.0.1",
     "minimatch": "~0.2.11",
     "minimatch": "~0.2.11",
-    "zfs": "~1.0.0"
+    "ini": "~1.1.0"
   },
   },
   "repository": {
   "repository": {
     "type": "git",
     "type": "git",

+ 27 - 72
zsnapper

@@ -4,91 +4,46 @@
 
 
 var CronJob = require('cron').CronJob;
 var CronJob = require('cron').CronJob;
 var async = require('async');
 var async = require('async');
-var fs = require('fs')
-var minimatch = require('minimatch')
 var util = require('util');
 var util = require('util');
-var zfs = require('zfs')
 
 
-// Generate a snapshot name given a base.
-function snapshotName(base) {
-    var when = (new Date()).toISOString().replace(/[-:]|(\.\d\d\d)/g, '');
-    return base + '-' + when;
-}
-
-// Create a snapshot on the specified dataset with the specified name, exluding all datasets in excl.
-function createSnapshot(ds, sn, cb) {
-    util.log('Create snapshot ' + ds + '@' + sn);
-    zfs.snapshot({ dataset: ds, name: sn, recursive: true }, cb);
-}
-
-// Keep only num snapshots with the specified base on the specified dataset
-function cleanSnapshots(patterns, base, num, cb) {
-    zfs.list({ type: 'snapshot' }, function (err, snaps) {
-        var allSnaps = {};
-        patterns.forEach(function (pat) {
-            snaps.forEach(function (s) {
-                var fields = s.name.split('@');
-                var parts = fields[1].split('-');
-                if (minimatch(fields[0], pat) && parts[0] === base) {
-                    var l = allSnaps[fields[0]] || [];
-                    l.push(s.name);
-                    allSnaps[fields[0]] = l;
-                }
-            });
-        });
+var config = require('./lib/config.js');
+var dspattern = require('./lib/dspattern.js');
+var SnapshotSet = require('./lib/snapshotset.js');
 
 
-        async.eachLimit(Object.keys(allSnaps), 10, function (dsName, cb) {
-            var snapNames = allSnaps[dsName];
-            if (snapNames.length > num) {
-                snapNames.sort();
+function applySnap(job, ds, cb) {
+    util.log('Process ' + ds + '@' + job.tag);
+    var s = new SnapshotSet(ds, job.tag);
+    s.snapshot(function (err) {
+        if (err) {
+            cb(err);
+        }
 
 
-                // Get the ones that exceed the specified number of snapshots.
-                var toDestroy = snapNames.slice(0, snapNames.length - num);
-
-                // Destroy them, one after the other.
-                async.eachSeries(toDestroy, function (sn, cb) {
-                    util.log('Destroy snapshot ' + sn + ' (cleaned)');
-                    zfs.destroy({ name: sn, recursive: true }, cb);
-                }, cb);
-            } else {
-                cb(null);
-            }
-        }, cb);
+        s.prune(job.keep, cb);
     });
     });
 }
 }
 
 
-function loadConfig() {
-    var data = fs.readFileSync(process.argv[2], 'utf-8');
-    var confObj = JSON.parse(data);
-    return confObj;
-}
+function snap(job) {
+    async.mapSeries(job.dataset, dspattern, function (err, datasets) {
+        if (err) {
+            throw err;
+        }
 
 
-function snap(datasets, snapBase, num) {
-    var snapId = snapshotName(snapBase);
-    zfs.list({type: 'filesystem,volume'}, function (err, dss) {
-        dss = dss.map(function (s) { return s.name; });
-        var toSnap = dss.filter(function (ds) {
-            return datasets.filter(function (pat) { return minimatch(ds, pat) }).length > 0;
-        });
+        var merged = [];
+        merged = merged.concat.apply(merged, datasets);
 
 
-        async.eachLimit(toSnap, 10, function (ds, cb) {
-            createSnapshot(ds, snapId, cb);
-        }, function () {
-            cleanSnapshots(datasets, snapBase, num, function () {
-                util.log("Done " + snapId);
-            });
+        async.forEachSeries(merged, function (ds, cb) {
+            applySnap(job, ds, cb);
         });
         });
     });
     });
 }
 }
 
 
 var cronJobs = [];
 var cronJobs = [];
-var conf = loadConfig();
-Object.keys(conf).forEach(function (snapBase) {
-    var config = conf[snapBase];
-    var job = new CronJob('00 ' + config.when, function () {
-        snap(config.datasets, snapBase, parseInt(config.count, 10));
+var jobs = config(process.argv[2]);
+
+jobs.forEach(function (job) {
+    util.log('Scheduling ' + job.tag + ' at ' + job.schedule)
+    var cronJob = new CronJob('00 ' + job.schedule, function () {
+        snap(job);
     }, null, true);
     }, null, true);
-    cronJobs.push(job);
+    cronJobs.push(cronJob);
 });
 });
-
-// vim: set filetype=javascript:

+ 92 - 0
zsnapper.ini

@@ -0,0 +1,92 @@
+;;; Datasets ;;;
+
+; Define datasets to be snapshotted. Each section should be named
+; [datasets.<name>] where name is an alphanumeric word. Each section contains
+; one or more match[] definitions. Each match[] should match one or more ZFS
+; datasets.
+;
+; Wildcards (* and ?) can be used and are interpreted as common shell globs.
+; Note that these match dataset names as reported by zfs list, not mountpoint
+; in the filesystem.
+;
+; A special form of shell expansion $(command) can also be used. The command
+; is executed and each line returned on stdout is substituted as a match.
+
+
+; Match datasets for installed VMs but not images. This is done by calling
+; vmadm list to get a list of uuids and matching with a single * to capture
+; ${uuid}-disk* as well as just ${uuid} itself.
+
+[datasets.vm]
+match[] = zones/$(vmadm list -p -o uuid)*
+
+; Match some system directories.
+
+[datasets.system]
+match[] = zones/opt
+match[] = zones/usbkey
+match[] = zones/var
+
+; Match an extra dataset I use for NFS exports.
+
+[datasets.extra]
+match[] = zones/srv
+
+; Match selected extra datasets.
+
+[datasets.selextra]
+match[] = zones/srv/git
+match[] = zones/srv/foto
+
+;;; Schedules ;;;
+
+; Define schedules to be executed. Each schedule is named by [schedule.<name>].
+; The name will be present in the snapshot name, i.e. for the dataset
+; "zones/srv" and schedule "hourly", snapshots will be created named similarly
+; to "zones/src@hourly-20130602T200000Z". The schedule field is a common cron
+; string defining when the job is triggered. The dataset[] field refers to the
+; dataset definitions from above. Finally, the keep field defines how many
+; snapshots are kept historically.
+
+
+; Keep an hours worth of five-minute snapshots of all interesting datasets.
+
+[schedule.quick]
+schedule = */5 * * * *
+keep = 12
+dataset[] = vm
+dataset[] = system
+dataset[] = extra
+
+; Keep 12 hourly snapshots of everything.
+
+[schedule.hourly]
+schedule = 0 * * * *
+keep = 12
+dataset[] = vm
+dataset[] = system
+dataset[] = extra
+
+; Keep a week of daily snapshots of VMs and the extra datasets.
+
+[schedule.daily]
+schedule = 0 0 * * *
+keep = 7
+dataset[] = vm
+dataset[] = extra
+
+; Keep a month of weekly snapshots of VMs and the extra datasets.
+
+[schedule.weekly]
+schedule = 0 0 * * 1
+keep = 4
+dataset[] = vm
+dataset[] = selextra
+
+; Keep a year of monthly snapshots for the extra datasets.
+
+[schedule.monthly]
+schedule = 0 0 1 * *
+keep = 12
+dataset[] = vm
+dataset[] = selextra

+ 0 - 27
zsnapper.json.sample

@@ -1,27 +0,0 @@
-{
-    "quick": {
-        "when": "*/5 * * * *",
-        "count": 12,
-        "datasets": [ "zones/*-*-*-*", "zones/config", "zones/opt", "zones/usbkey", "zones/var" ]
-    },
-    "hourly": {
-        "when": "0 * * * *",
-        "count": 24,
-        "datasets": [ "zones/*-*-*-*", "zones/config", "zones/opt", "zones/usbkey", "zones/var" ]
-    },
-    "daily": {
-        "when": "00 05 * * *",
-        "count": 10,
-        "datasets": [ "zones/*-*-*-*", "zones/config", "zones/opt", "zones/usbkey", "zones/var" ]
-    },
-    "weekly": {
-        "when": "00 05 * * 1",
-        "count": 4,
-        "datasets": [ "zones/*-*-*-*" ]
-    },
-    "monthly": {
-        "when": "00 05 1 * *",
-        "count": 12,
-        "datasets": [ "zones/*-*-*-*" ]
-    }
-}

+ 2 - 2
zsnapper.xml

@@ -12,7 +12,7 @@
 		<method_context>
 		<method_context>
 		</method_context>
 		</method_context>
 
 
-		<exec_method type="method" name="start" exec="/opt/local/bin/zsnapper %{config_file}" timeout_seconds="60"/>
+		<exec_method type="method" name="start" exec="/opt/local/zsnapper/zsnapper %{config_file}" timeout_seconds="60"/>
 		<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
 		<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
 
 
 		<property_group name="startd" type="framework">
 		<property_group name="startd" type="framework">
@@ -21,7 +21,7 @@
 		</property_group>
 		</property_group>
 
 
 		<property_group name="application" type="application">
 		<property_group name="application" type="application">
-			<propval name="config_file" type="astring" value="/opt/local/etc/zsnapper.json"/>
+			<propval name="config_file" type="astring" value="/opt/local/etc/zsnapper.ini"/>
 		</property_group>
 		</property_group>
 
 
 		<stability value="Evolving"/>
 		<stability value="Evolving"/>