<commit_before>/* node.js library for Plurk API 2.0
 */

var fs = require('fs');
var urlparse = require('url').parse;

var OAuth = require('oauth').OAuth;

var clientOAuthGen = function (https, consumerKey, consumerSecret) {
    var requestTokenUrl = https ? "https://www.plurk.com/OAuth/request_token"
                             : "http://www.plurk.com/OAuth/request_token";
    var accessTokenUrl = https ? "https://www.plurk.com/OAuth/access_token"
                            : "http://www.plurk.com/OAuth/access_token";
    return new OAuth(requestTokenUrl,
                     accessTokenUrl,
                     consumerKey,
                     consumerSecret,
                     "1.0",
                     null,
                     "HMAC-SHA1"
    );
};

// accessToken and accessToken are optional
var PlurkClient = function (https,
                       consumerKey, consumerSecret,
                       accessToken, accessTokenSecret) {
    this.endpoint = "http://www.plurk.com/";
    this.oAuth = clientOAuthGen(https, consumerKey, consumerSecret);

    this.getOAuthRequestToken = this.getRequestToken =
        function (extraParams, cb) {
            this.oAuth.getOAuthRequestToken(extraParams, cb);
        };

    this.getOAuthAccessToken = this.getAccessToken =
        function (oauth_token, oauth_token_secret, oauth_verifier, cb) {
            this.oAuth.getOAuthAccessToken(oauth_token, oauth_token_secret,
                                           oauth_verifier,
                                           cb);
        };

    this.accessToken = accessToken;
    this.accessTokenSecret = accessTokenSecret;
};

var getClient = function (json) {
    var data = JSON.parse(json);
    return new PlurkClient(data.https,
                      data.consumerKey, data.consumerSecret,
                      data.accessToken, data.accessTokenSecret);
}

// fromFile(filename, ['utf8'], function(err, client))
module.exports.fromFile = function (filename, encoding, cb) {
    var innerEnc = encoding;
    var innerCb = cb;
    if (typeof encoding === 'function') {
        innerEnc = 'utf8';
        innerCb = encoding;
    }
    fs.readFile(filename, enc, function(err, json) {
        if (err) {
            innerCb(err, json);
        } else {
            innerCb(err, getClient(json));
        }
    })
}

// fromFileSync(filename, ['utf8'])
module.exports.fromFileSync = function (filename, encoding) {
    var innerEnc = encoding;
    if (innerEnc == null) {
        innerEnc = 'utf8';
    }
    return getClient(fs.readFileSync(filename, innerEnc));
}

PlurkClient.prototype.getAuthPage = function(requestToken) {
    return "http://www.plurk.com/OAuth/authorize?oauth_token=" + requestToken
}
PlurkClient.prototype.getAuthPageMobile = function(requestToken) {
    return "http://www.plurk.com/m/authorize?oauth_token=" + requestToken
}

PlurkClient.prototype.join = function(path) {
    if (path.indexOf("/APP/") === 0) {
        return this.endpoint + path.substr(1);
    } else if (path.indexOf("APP/") === 0) {
        return this.endpoint + path
    } else if (path.indexOf("/") === 0) {
        return this.endpoint + "APP" + path;
    } else {
        return this.endpoint + "APP/" + path;
    }
};

var boundCb = function (cb) {
    if (!cb) {
        return function () {}; // or return;
    }
    return function (err, json) {
        var data;
        try {
            data = JSON.parse(json);
        } catch (e) { // JSON.parse throws SyntaxError.
            if (err) {
                cb(err, json);
            } else {
                cb({exception: e}, null);
            }
            return;
        }
        cb(err, data);
    }
}

// err is set to "missing new_offset" if any error occurred
var boundCometCb = function (cb, cometUrl) {
    if (!cb) {
        return function () {};
    }
    return function (err, jsonP) {
        if (err) {
            cb(err, jsonP);
            return;
        }
        var onError = function () {
            cb("missing new_offset", jsonP);
        }
        var from = jsonP.indexOf('{');
        var to = jsonP.lastIndexOf('}') + 1;
        var json = jsonP.substring(from, to);
        var data;
        try {
            data = JSON.parse(json);
        } catch (e) {
            onError();
            return;
        }
        if (data["new_offset"] == null) {
            onError();
            return;
        }
        cb(err, data, cometUrl + "&offset=" + data["new_offset"]);
    }
}

// rq(api, obj, function(err, data) [, accessToken, accessTokenSecret])
PlurkClient.prototype.rq = function(api, obj, callback, accessToken, accessTokenSecret) {
    var path = this.join(api);
    if (accessToken == null) accessToken = this.accessToken;
    if (accessTokenSecret == null) accessTokenSecret = this.accessTokenSecret;
    this.oAuth.post(path, accessToken, accessTokenSecret, obj, boundCb(callback));
};

PlurkClient.prototype.post = function(api, accessToken, accessTokenSecret, obj, callback) {
    var path = this.join(api);
    this.oAuth.post(path, accessToken, accessTokenSecret, obj, boundCb(callback));
};

var startComet = function (self, accessToken, accessTokenSecret, callback) {
    self.post('/APP/Realtime/getUserChannel', accessToken, accessTokenSecret, null, function (err, data) {
        if (err) {
            callback(err, data);
        } else {
            var requestUrl = data["comet_server"];
            var cometUrl = /[^?]+/.exec(requestUrl) + "?channel=" + data["channel_name"];
            self.oAuth.get(requestUrl,
                           accessToken,
                           accessTokenSecret,
                           boundCometCb(callback, cometUrl));
        }
    });
}

var pollComet = function (self, requestUrl, accessToken, accessTokenSecret, callback) {
    var channel = urlparse(requestUrl, true).query["channel"];
    var cometUrl = /[^?]+/.exec(requestUrl) + "?channel=" + channel;
    self.oAuth.get(requestUrl,
                   accessToken,
                   accessTokenSecret,
                   boundCometCb(callback, cometUrl));
}

// startComet(function(err, data, cometUrl) [, accessToken, accessTokenSecret])
PlurkClient.prototype.startComet = function(callback, accessToken, accessTokenSecret) {
    if (accessToken == null) accessToken = this.accessToken;
    if (accessTokenSecret == null) accessTokenSecret = this.accessTokenSecret;
    startComet(this, accessToken, accessTokenSecret, callback);
};

// comet(cometUrl, function(err, data, cometUrl) [, accessToken, accessTokenSecret])
PlurkClient.prototype.comet = function(cometUrl, callback, accessToken, accessTokenSecret) {
    if (accessToken == null) accessToken = this.accessToken;
    if (accessTokenSecret == null) accessTokenSecret = this.accessTokenSecret;
    pollComet(this, cometUrl, accessToken, accessTokenSecret, callback);
};

var limitedTo = {}

limitedTo.parse = function (strOrArray) {
    if (Array.isArray(strOrArray)) {
        return strOrArray;
    } else if (typeof strOrArray === 'string') {
        var str = strOrArray;
        var pattern = /[0-9]+/g;
        var matches = str.match(pattern);
        if (matches) {
            return matches.map(function (id) { return parseInt(id); });
        } else {
            return null;
        }
    } else {
        return null;
    }
}

limitedTo.stringify = function (array) {
    return array.reduce(function (memo, id) {
        return memo + "|" + id + "|";
    }, "");
}

var base36 = {}

var baseTable = "0123456789abcdefghijklmnopqrstuvwxyz".split("");

base36.decode = function (value) {
    var reverse = value.toLowerCase().split("").reverse();
    return reverse.reduce(function (memo, ch, index) {
        return memo + baseTable.indexOf(ch) * Math.pow(36, index);
    }, 0);
}

base36.encode = function (value) {
    if (!value || value <= 0) {
        return "0";
    }
    var divide = value;
    var result = "";
    while (divide !== 0) {
        result = baseTable[divide % 36] + result;
        divide = Math.floor(divide / 36);
    }
    return result;
}

var urlMatch = {};

// decode base36 plurk_id to decimal if decode is set
urlMatch.plurk = function (url, decode) {
    var PLURKPATTERN = new RegExp("plurk\.com/(m/)?p/([0-9a-z]+)");
    var result = PLURKPATTERN.exec(url);
    if (result) {
        var plurk = result[2];
        if (plurk) {
            if (decode) {
               return base36.decode(plurk);
            } else {
                return plurk;
            }
        }
    }
    return null;
}

urlMatch.user = function (url) {
    var USERPATTERN = new RegExp("plurk\.com/(m/u/)?([0-9a-zA-Z_]+)(/#)?"); // 0-9, a-z and underscore
    var result = USERPATTERN.exec(url);
    if (result) {
        var user = result[2];
        if (user) {
            if (url.lastIndexOf(result[0]) === (url.length - result[0].length)) { // match trailing
                return user;
            }
        }
    }
    return null;
}

module.exports.limitedTo = limitedTo;

module.exports.base36 = base36;

module.exports.urlMatch = urlMatch;

module.exports.PlurkClient = PlurkClient;
<commit_msg>Send object error instead of string error on boundCometCb.<commit_after>/* node.js library for Plurk API 2.0
 */

var fs = require('fs');
var urlparse = require('url').parse;

var OAuth = require('oauth').OAuth;

var clientOAuthGen = function (https, consumerKey, consumerSecret) {
    var requestTokenUrl = https ? "https://www.plurk.com/OAuth/request_token"
                             : "http://www.plurk.com/OAuth/request_token";
    var accessTokenUrl = https ? "https://www.plurk.com/OAuth/access_token"
                            : "http://www.plurk.com/OAuth/access_token";
    return new OAuth(requestTokenUrl,
                     accessTokenUrl,
                     consumerKey,
                     consumerSecret,
                     "1.0",
                     null,
                     "HMAC-SHA1"
    );
};

// accessToken and accessToken are optional
var PlurkClient = function (https,
                       consumerKey, consumerSecret,
                       accessToken, accessTokenSecret) {
    this.endpoint = "http://www.plurk.com/";
    this.oAuth = clientOAuthGen(https, consumerKey, consumerSecret);

    this.getOAuthRequestToken = this.getRequestToken =
        function (extraParams, cb) {
            this.oAuth.getOAuthRequestToken(extraParams, cb);
        };

    this.getOAuthAccessToken = this.getAccessToken =
        function (oauth_token, oauth_token_secret, oauth_verifier, cb) {
            this.oAuth.getOAuthAccessToken(oauth_token, oauth_token_secret,
                                           oauth_verifier,
                                           cb);
        };

    this.accessToken = accessToken;
    this.accessTokenSecret = accessTokenSecret;
};

var getClient = function (json) {
    var data = JSON.parse(json);
    return new PlurkClient(data.https,
                      data.consumerKey, data.consumerSecret,
                      data.accessToken, data.accessTokenSecret);
}

// fromFile(filename, ['utf8'], function(err, client))
module.exports.fromFile = function (filename, encoding, cb) {
    var innerEnc = encoding;
    var innerCb = cb;
    if (typeof encoding === 'function') {
        innerEnc = 'utf8';
        innerCb = encoding;
    }
    fs.readFile(filename, enc, function(err, json) {
        if (err) {
            innerCb(err, json);
        } else {
            innerCb(err, getClient(json));
        }
    })
}

// fromFileSync(filename, ['utf8'])
module.exports.fromFileSync = function (filename, encoding) {
    var innerEnc = encoding;
    if (innerEnc == null) {
        innerEnc = 'utf8';
    }
    return getClient(fs.readFileSync(filename, innerEnc));
}

PlurkClient.prototype.getAuthPage = function(requestToken) {
    return "http://www.plurk.com/OAuth/authorize?oauth_token=" + requestToken
}
PlurkClient.prototype.getAuthPageMobile = function(requestToken) {
    return "http://www.plurk.com/m/authorize?oauth_token=" + requestToken
}

PlurkClient.prototype.join = function(path) {
    if (path.indexOf("/APP/") === 0) {
        return this.endpoint + path.substr(1);
    } else if (path.indexOf("APP/") === 0) {
        return this.endpoint + path
    } else if (path.indexOf("/") === 0) {
        return this.endpoint + "APP" + path;
    } else {
        return this.endpoint + "APP/" + path;
    }
};

var boundCb = function (cb) {
    if (!cb) {
        return function () {}; // or return;
    }
    return function (err, json) {
        var data;
        try {
            data = JSON.parse(json);
        } catch (e) { // JSON.parse throws SyntaxError.
            if (err) {
                cb(err, json);
            } else {
                cb({exception: e}, null);
            }
            return;
        }
        cb(err, data);
    }
}

// err is set to "missing new_offset" if any error occurred
var boundCometCb = function (cb, cometUrl) {
    if (!cb) {
        return function () {};
    }
    return function (err, jsonP) {
        if (err) {
            cb(err, jsonP);
            return;
        }
        var onError = function () {
            cb({error: "missing new_offset"}, jsonP);
        }
        var from = jsonP.indexOf('{');
        var to = jsonP.lastIndexOf('}') + 1;
        var json = jsonP.substring(from, to);
        var data;
        try {
            data = JSON.parse(json);
        } catch (e) {
            onError();
            return;
        }
        if (data["new_offset"] == null) {
            onError();
            return;
        }
        cb(err, data, cometUrl + "&offset=" + data["new_offset"]);
    }
}

// rq(api, obj, function(err, data) [, accessToken, accessTokenSecret])
PlurkClient.prototype.rq = function(api, obj, callback, accessToken, accessTokenSecret) {
    var path = this.join(api);
    if (accessToken == null) accessToken = this.accessToken;
    if (accessTokenSecret == null) accessTokenSecret = this.accessTokenSecret;
    this.oAuth.post(path, accessToken, accessTokenSecret, obj, boundCb(callback));
};

PlurkClient.prototype.post = function(api, accessToken, accessTokenSecret, obj, callback) {
    var path = this.join(api);
    this.oAuth.post(path, accessToken, accessTokenSecret, obj, boundCb(callback));
};

var startComet = function (self, accessToken, accessTokenSecret, callback) {
    self.post('/APP/Realtime/getUserChannel', accessToken, accessTokenSecret, null, function (err, data) {
        if (err) {
            callback(err, data);
        } else {
            var requestUrl = data["comet_server"];
            var cometUrl = /[^?]+/.exec(requestUrl) + "?channel=" + data["channel_name"];
            self.oAuth.get(requestUrl,
                           accessToken,
                           accessTokenSecret,
                           boundCometCb(callback, cometUrl));
        }
    });
}

var pollComet = function (self, requestUrl, accessToken, accessTokenSecret, callback) {
    var channel = urlparse(requestUrl, true).query["channel"];
    var cometUrl = /[^?]+/.exec(requestUrl) + "?channel=" + channel;
    self.oAuth.get(requestUrl,
                   accessToken,
                   accessTokenSecret,
                   boundCometCb(callback, cometUrl));
}

// startComet(function(err, data, cometUrl) [, accessToken, accessTokenSecret])
PlurkClient.prototype.startComet = function(callback, accessToken, accessTokenSecret) {
    if (accessToken == null) accessToken = this.accessToken;
    if (accessTokenSecret == null) accessTokenSecret = this.accessTokenSecret;
    startComet(this, accessToken, accessTokenSecret, callback);
};

// comet(cometUrl, function(err, data, cometUrl) [, accessToken, accessTokenSecret])
PlurkClient.prototype.comet = function(cometUrl, callback, accessToken, accessTokenSecret) {
    if (accessToken == null) accessToken = this.accessToken;
    if (accessTokenSecret == null) accessTokenSecret = this.accessTokenSecret;
    pollComet(this, cometUrl, accessToken, accessTokenSecret, callback);
};

var limitedTo = {}

limitedTo.parse = function (strOrArray) {
    if (Array.isArray(strOrArray)) {
        return strOrArray;
    } else if (typeof strOrArray === 'string') {
        var str = strOrArray;
        var pattern = /[0-9]+/g;
        var matches = str.match(pattern);
        if (matches) {
            return matches.map(function (id) { return parseInt(id); });
        } else {
            return null;
        }
    } else {
        return null;
    }
}

limitedTo.stringify = function (array) {
    return array.reduce(function (memo, id) {
        return memo + "|" + id + "|";
    }, "");
}

var base36 = {}

var baseTable = "0123456789abcdefghijklmnopqrstuvwxyz".split("");

base36.decode = function (value) {
    var reverse = value.toLowerCase().split("").reverse();
    return reverse.reduce(function (memo, ch, index) {
        return memo + baseTable.indexOf(ch) * Math.pow(36, index);
    }, 0);
}

base36.encode = function (value) {
    if (!value || value <= 0) {
        return "0";
    }
    var divide = value;
    var result = "";
    while (divide !== 0) {
        result = baseTable[divide % 36] + result;
        divide = Math.floor(divide / 36);
    }
    return result;
}

var urlMatch = {};

// decode base36 plurk_id to decimal if decode is set
urlMatch.plurk = function (url, decode) {
    var PLURKPATTERN = new RegExp("plurk\.com/(m/)?p/([0-9a-z]+)");
    var result = PLURKPATTERN.exec(url);
    if (result) {
        var plurk = result[2];
        if (plurk) {
            if (decode) {
               return base36.decode(plurk);
            } else {
                return plurk;
            }
        }
    }
    return null;
}

urlMatch.user = function (url) {
    var USERPATTERN = new RegExp("plurk\.com/(m/u/)?([0-9a-zA-Z_]+)(/#)?"); // 0-9, a-z and underscore
    var result = USERPATTERN.exec(url);
    if (result) {
        var user = result[2];
        if (user) {
            if (url.lastIndexOf(result[0]) === (url.length - result[0].length)) { // match trailing
                return user;
            }
        }
    }
    return null;
}

module.exports.limitedTo = limitedTo;

module.exports.base36 = base36;

module.exports.urlMatch = urlMatch;

module.exports.PlurkClient = PlurkClient;
<|endoftext|>


<commit_before>'use strict';

var Promise = require('bluebird');
var PriorityQueue = require('js-priority-queue');
var cp = require('child_process');
var uuid = require('uuid');
var _ = require('lodash');

var _initialized = false, _options, _addsPending = 0, _toRemove = 0, _addResolver;

/*
numChildren: In a single core environment, we'll still use one thread and let the OS manage
resources. In a multicore environment, we'll leave one core for Node and use the rest for child threads.
 */
var _defaultOptions = {
  numChildren: require('os').cpus().length -1 || 1,
  priority: true,
  addTimout: 30000
};

var _functionDefaults = {
  priority: 100,
};

/*
A container for our child threads.
 */
var _children = {};

/*
This object is mainly for holding the resolver functions, but also keeps a record of each payload.
 */
var _inProcess = {};

var _priorities = {};

var _queue = new PriorityQueue({comparator: function(a,b){ return a.priority - b.priority;}});

var Tadpole = {

  spawn: function(userOptions){

    if (_initialized) throw new Error('Cannot re-initialize Tadpole.');

    userOptions = userOptions || {};
    _.defaults(userOptions, _defaultOptions);

    for (var i = 0; i < userOptions.numChildren; i++){

      var id = uuid.v4();
      var child = cp.fork(__dirname + '/child.js', {execArgv: []});

      child.on('error', function(error){
        console.error('Error in child process: ' + error);
      });

      //when we get the result of reach transaction, this function is called to get the next operation in the queue
      child.on('message', function(payload){
        next(id, payload);
      });

      var container = {thread: child, childId: id, active: false};
      _children[id] = container;
    }

    _options = userOptions;
    _initialized = true;

  },

  add: function(functionObject) {

    if (_addsPending > 0) throw new Error('Cannot add another function until previous add has completed.');
    if (_priorities[functionObject.name]) throw new Error('Already added a function with that name.');
    if (functionObject.priority < 1) throw new Error('Priority less than 1 reserved for module use.');
    if (!functionObject.func || !functionObject.name) throw new Error('Function object must contain both function and name.');

    _.defaults(functionObject, _functionDefaults);

    functionObject.func = stringifyFunction(functionObject.func); //dehydrate the function

    functionObject.action = 'ADDFUNC';

    _priorities[functionObject.name] = functionObject.priority;

    _.each(_children, function(child){
      _addsPending++;
      setId(functionObject);
      new Promise(function(resolve, reject) {
        functionObject.resolver = resolve;
        functionObject.rejecter = reject;
        _inProcess[functionObject.id] = functionObject;
        child.thread.send(functionObject);
        // console.log('IP', _inProcess[functionObject.id]);
      });

    });

    return new Promise(function(resolve, reject){
      _addResolver = resolve;
      //TODO: reject if timeout elapses
      });
  },

  run: function(name, args){
    return new Promise(function(resolve, reject){
      request({action: name,
                priority: _priorities[name],
                args: args,
                resolver: resolve,
                rejecter: reject
              });
    });

  },

  remove: function(num){
    num = num || 1;
    _toRemove++;
  },

  size: function(){
    return Object.keys(_children).length;
  }

};

module.exports = Tadpole;

//Utility functions.
function stringifyFunction (func) {
  return '(' + func + ')';
}

/*
This function is used by the module methods to process the result of each child thread action,
and then get the next action from the queue.
 */
function next (childId, payload) {
  //use the IDs to retrieve the payload from our storage in this thread
  var sentPayload = _inProcess[payload.id];

  if (payload.error){
    sentPayload.rejecter(payload.error);
    return;
  }

  if (sentPayload.action === 'ADDFUNC') {
    _addsPending--;
    if (_addsPending === 0) _addResolver(true);
  } else {
      sentPayload.resolver(payload.result); //resolve those promises!
    }

  delete _inProcess[payload.id]; //no memory leaks please

  //take this child out of service if we want to decrement running processes
  if (_toRemove) {
    delete children[childId];
    _toRemove--;
    return;
  }

  try{
    var nextPayload = _queue.dequeue();
    _inProcess[nextPayload.id] = nextPayload;
    _children[childId].child.send(nextPayload);
  } catch(err){
    //empty queue throws error
    _children[childId].active = false; //if the queue is empty, this child can rest
  }
}

function setId(payload){
  payload.id = uuid.v4();
}

function request (payload){
  setId(payload);
  if (_queue.length){ // if the queue is full, just enqueue the payload
    _queue.queue(payload);
  } else { //otherwise, let's find this payload a home!
    var slotted = false;
    _.each(_children, function(child, key){
      if (!child.active){
        _inProcess[payload.id] = payload;
        child.active = true;
        child.thread.send(payload);
        slotted = true;
        return false; //if we've found a home, we stop the each loop
      }
    });
    if (!slotted) _queue.queue(payload); //in case the queue was empty, but all the threads were working
  }
}<commit_msg>Can now add, remove, and respawn children.<commit_after>'use strict';

var Promise = require('bluebird');
var PriorityQueue = require('js-priority-queue');
var cp = require('child_process');
var uuid = require('uuid');
var _ = require('lodash');

var _initialized = false, _options, _addsPending = 0, _toRemove = 0, _addResolver;

/*
numChildren: In a single core environment, we'll still use one thread and let the OS manage
resources. In a multicore environment, we'll leave one core for Node and use the rest for child threads.
 */
var _defaultOptions = {
  numChildren: require('os').cpus().length -1 || 1,
  priority: true,
  addTimout: 30000,
  env: process.ENV
};

var _functionDefaults = {
  priority: 100,
};

/*
A container for our child threads.
 */
var _children = {};

/*
This object is mainly for holding the resolver functions, but also keeps a record of each payload.
 */
var _inProcess = {};

var _functions = {};

var _queue = new PriorityQueue({comparator: function(a,b){ return a.priority - b.priority;}});

var Tadpole = {

  spawn: function(userOptions){

    if (_initialized) throw new Error('Cannot re-initialize Tadpole.');

    userOptions = userOptions || {};
    _.defaults(userOptions, _defaultOptions);
    _options = userOptions;

    for (var i = 0; i < userOptions.numChildren; i++){
      addChild();
    }

    _initialized = true;

  },

  addFunction: function(functionObject) {

    if (_addsPending > 0) throw new Error('Cannot add another function until previous add has completed.');
    if (_functions[functionObject.name]) throw new Error('Already added a function with that name.');
    if (functionObject.priority < 1) throw new Error('Priority less than 1 reserved for module use.');
    if (!functionObject.func || !functionObject.name) throw new Error('Function object must contain both function and name.');

    _.defaults(functionObject, _functionDefaults);

    functionObject.func = stringifyFunction(functionObject.func); //dehydrate the function

    functionObject.action = 'ADDFUNC';

    _functions[functionObject.name] = functionObject;

    _.each(_children, function(child){
      injectFunction(functionObject, child);
    });

    return new Promise(function(resolve, reject){
      _addResolver = resolve;
      //TODO: reject if timeout elapses
      });
  },

  run: function(name, args){
    return new Promise(function(resolve, reject){
      request({action: name,
                priority: _functions[name].priority,
                args: args,
                resolver: resolve,
                rejecter: reject
              });
    });

  },

  remove: function(num){
    num = num || 1;
    while(num){
      _toRemove++;
      if(!_queue.length) killChild();
      num--;
    }
  },

  add: function(num){
    while(num){
      restoreChildFunctions(addChild());
      num--;
    }
  },

  size: function(){
    return Object.keys(_children).length;
  }

};

module.exports = Tadpole;

//Utility functions.
function stringifyFunction (func) {
  return '(' + func + ')';
}

/*
This function is used by the module methods to process the result of each child thread action,
and then get the next action from the queue.
 */
function next (childId, payload) {
  //use the IDs to retrieve the payload from our storage in this thread
  var sentPayload = _inProcess[payload.id];

  if (payload.error){
    sentPayload.rejecter(payload.error);
    return;
  }

  if (sentPayload.action === 'ADDFUNC') {
    _addsPending--;
    if (_addsPending === 0) _addResolver(true);
  } else {
      sentPayload.resolver(payload.result); //resolve those promises!
    }

  delete _inProcess[payload.id]; //no memory leaks please

  //take this child out of service if we want to decrement running processes
  if (_toRemove) {
    killChild(childId);
    return;
  }

  try{
    var nextPayload = _queue.dequeue();
    _inProcess[nextPayload.id] = nextPayload;
    _children[childId].child.send(nextPayload);
  } catch(err){
    //empty queue throws error
    _children[childId].active = false; //if the queue is empty, this child can rest
  }
}

function setId(payload){
  payload.id = uuid.v4();
}

function request (payload){
  setId(payload);
  if (_queue.length){ // if the queue is full, just enqueue the payload
    _queue.queue(payload);
  } else { //otherwise, let's find this payload a home!
    var slotted = false;
    _.each(_children, function(child, key){
      if (!child.active){
        _inProcess[payload.id] = payload;
        child.active = true;
        child.thread.send(payload);
        slotted = true;
        return false; //if we've found a home, we stop the each loop
      }
    });
    if (!slotted) _queue.queue(payload); //in case the queue was empty, but all the threads were working
  }
}

function addChild(id){
  id = id || uuid.v4();
  var child = cp.fork(__dirname + '/child.js', {env: _options.env, execArgv: []});

  child.on('error', function(error){
    console.error('Error in child process: ' + error);
  });

  //when we get the result of reach transaction, this function is called to get the next operation in the queue
  child.on('message', function(payload){
    next(id, payload);
  });

  child.on('exit', function(code, signal){
    if (!signal === 'SIGTERM'){
      addChild(id);
      restoreChildFunctions(child);
    }
  });

  var container = {thread: child, id: id, active: false};
  _children[id] = container;
  return id;
}

function injectFunction(functionObject, child){
  _addsPending++;
  setId(functionObject);
  new Promise(function(resolve, reject) {
    functionObject.resolver = resolve;
    functionObject.rejecter = reject;
    _inProcess[functionObject.id] = functionObject;
    child.thread.send(functionObject);
  });
}

function restoreChildFunctions(child){
  if (!_addsPending){
    _.each(_functions, function(functionObject){
      injectFunction(functionObject, child);
    });
  } else setTimeout(function(){
    restoreChildFunctions(child);
  }, 500);
}

function killChild(childId){
  if (childId){
    _children[childId].thread.kill();
    delete _children[childId];
    _toRemove--;
  } else {
      _.each(_children, function(child){
        if (!child.active){
          killChild(child.id);
          _toRemove--;
          return false; //we can stop once we get one
        }
      //we haven't killed the child, but there is still a value in _toRemove, so it should be killed when it finishes what it's working on
      });
    }
}
<|endoftext|>


<commit_before>var EventEmitter = require('events')
var mix = require('mixy')
var MDeps = require('css-module-deps')
var thr = require('through2')
var resolve = require('resolve')
var readonly = require('read-only-stream')
var topoSort = require('deps-topo-sort')
var path = require('path')
var splicer = require('labeled-stream-splicer')
var sink = require('sink-transform')

var inherits = require('util').inherits
inherits(Depsify, EventEmitter)

module.exports = Depsify

function Depsify(entries, opts) {
  if (!(this instanceof Depsify)) {
    return new Depsify(entries, opts)
  }
  if (typeof entries === 'string' || Array.isArray(entries)) {
    opts = mix({}, opts, {
      entries: [].concat(opts.entries || [], entries),
    })
  } else {
    opts = mix({}, entries, opts)
  }
  this._options = opts
  opts.basedir = opts.basedir || process.cwd()

  this.pipeline = this._createPipeline(opts)

  ;[].concat(opts.transform).filter(Boolean).forEach(function (p) {
    this.transform(p)
  }, this)
  ;[].concat(opts.entries).filter(Boolean).forEach(function (file) {
    this.add(file, { basedir: opts.basedir })
  }, this)
  ;[].concat(opts.plugin).filter(Boolean).forEach(function (p) {
    this.plugin(p, { basedir: opts.basedir })
  }, this)
}

Depsify.prototype._createPipeline = function (opts) {
  var self = this
  this._mdeps = this._createDeps(opts)
  this._mdeps.on('file', function (file) {
    pipeline.emit('file', file)
    self.emit('file', file)
  })
  this._mdeps.on('package', function (pkg) {
    pipeline.emit('package', pkg)
    self.emit('package', pkg)
  })
  this._mdeps.on('transform', function (tr, file) {
    pipeline.emit('transform', tr, file)
    self.emit('transform', tr, file)
  })

  // for factor-bundle
  this._bpack = this.pack(opts)

  var pipeline = splicer.obj([
    'record', [ this._recorder() ],
    'deps', [ this._mdeps ],
    'syntax', [],
    'sort', [],
    'dedupe', [],
    'label', [],
    'emit-deps', [ this._emitDeps() ],
    'debug', [],
    'pack', [ this._bpack ],
    'wrap', [],
  ])

  return pipeline
}

Depsify.prototype._createDeps = function(opts) {
  opts = mix.fill({ transform: [] }, opts)
  return MDeps(opts)
}

Depsify.prototype._emitDeps = function() {
  var self = this
  return thr.obj(function (row, enc, next) {
    self.emit('dep', row)
    this.push(row)
    next()
  })
}

Depsify.prototype._recorder = function() {
  var recorded = this._recorded = []
  return thr.obj(function (row, _, next) {
    recorded.push(row)
    next(null, row)
  })
}

Depsify.prototype.pack = function() {
  return [
    sink.obj(function (rows, done) {
      rows.sort(function (a, b) {
        return a.file < b.file ? -1 : 1
      })
      .forEach(function (row) {
        // to use toposort
        row.id = row.file
        this.push(row)
      }, this)
      done()
    }),
    topoSort(),
    thr.obj(function (row, _, next) {
      next(null, row.source)
    }),
  ]
}

Depsify.prototype.add = function(file, opts) {
  opts = opts || {}
  var basedir = opts.basedir || this._options.basedir
  if (Array.isArray(file)) {
    file.forEach(function (f) {
      this.add(f, opts)
    }, this)
  } else if (typeof file === 'string') {
    file = path.resolve(basedir, file)
    this.pipeline.write({ file: file })
  } else {
    this.pipeline.write(mix({ basedir: basedir }, file, opts))
  }
  return this
}

Depsify.prototype.processor = function(processor) {
  this.pipeline.write({ processor: processor })
  return this
}

Depsify.prototype.transform = function(tr) {
  this.pipeline.write({ transform: tr })
  return this
}

Depsify.prototype.reset = function() {
  this.pipeline = this._createPipeline(this._options)
  this._bundled = false
  this.emit('reset')
}

Depsify.prototype.plugin = function(p, opts) {
  if (Array.isArray(p)) {
    opts = p[1]
    p = p[0]
  }
  var basedir = opts && opts.basedir || this._options.basedir
  if (typeof p === 'function') {
    p(this, opts)
  } else {
    var pfile = resolve.sync(p, { basedir: basedir })
    var f = require(pfile)
    f(this, opts)
  }
  return this
}

Depsify.prototype.bundle = function() {
  if (this._bundled) {
    var recorded = this._recorded
    this.reset()
    recorded.forEach(function (x) {
      this.pipeline.write(x)
    }, this)
  }
  var output = readonly(this.pipeline)

  this.emit('bundle', output)
  this.pipeline.end()

  this._bundled = true

  return output
}

<commit_msg>Apply processor in Depsify rather than css-module-deps<commit_after>var EventEmitter = require('events')
var mix = require('mixy')
var MDeps = require('css-module-deps')
var thr = require('through2')
var resolve = require('resolve')
var readonly = require('read-only-stream')
var topoSort = require('deps-topo-sort')
var path = require('path')
var splicer = require('labeled-stream-splicer')
var sink = require('sink-transform')

var inherits = require('util').inherits
inherits(Depsify, EventEmitter)

module.exports = Depsify

function Depsify(entries, opts) {
  if (!(this instanceof Depsify)) {
    return new Depsify(entries, opts)
  }
  if (typeof entries === 'string' || Array.isArray(entries)) {
    opts = mix({}, opts, {
      entries: [].concat(opts.entries || [], entries),
    })
  } else {
    opts = mix({}, entries, opts)
  }
  this._options = opts
  opts.basedir = opts.basedir || process.cwd()

  this.pipeline = this._createPipeline(opts)

  ;[].concat(opts.transform).filter(Boolean).forEach(function (p) {
    this.transform(p)
  }, this)
  ;[].concat(opts.processor).filter(Boolean).forEach(function (p) {
    this.processor(p)
  }, this)
  ;[].concat(opts.entries).filter(Boolean).forEach(function (file) {
    this.add(file, { basedir: opts.basedir })
  }, this)
  ;[].concat(opts.plugin).filter(Boolean).forEach(function (p) {
    this.plugin(p, { basedir: opts.basedir })
  }, this)
}

Depsify.prototype._createPipeline = function (opts) {
  var self = this
  this._mdeps = this._createDeps(opts)
  this._mdeps.on('file', function (file) {
    pipeline.emit('file', file)
    self.emit('file', file)
  })
  this._mdeps.on('package', function (pkg) {
    pipeline.emit('package', pkg)
    self.emit('package', pkg)
  })
  this._mdeps.on('transform', function (tr, file) {
    pipeline.emit('transform', tr, file)
    self.emit('transform', tr, file)
  })

  // for factor-bundle
  this._bpack = this.pack(opts)

  var pipeline = splicer.obj([
    'record', [ this._recorder() ],
    'deps', [ this._mdeps ],
    'syntax', [],
    'sort', [],
    'dedupe', [],
    'label', [],
    'emit-deps', [ this._emitDeps() ],
    'debug', [],
    'pack', [ this._bpack ],
    'wrap', [],
  ])

  return pipeline
}

Depsify.prototype._createDeps = function(opts) {
  opts = mix.fill({ transform: [], processor: [] }, opts)
  return MDeps(opts)
}

Depsify.prototype._emitDeps = function() {
  var self = this
  return thr.obj(function (row, enc, next) {
    self.emit('dep', row)
    this.push(row)
    next()
  })
}

Depsify.prototype._recorder = function() {
  var recorded = this._recorded = []
  return thr.obj(function (row, _, next) {
    recorded.push(row)
    next(null, row)
  })
}

Depsify.prototype.pack = function() {
  return [
    sink.obj(function (rows, done) {
      rows.sort(function (a, b) {
        return a.file < b.file ? -1 : 1
      })
      .forEach(function (row) {
        // to use toposort
        row.id = row.file
        this.push(row)
      }, this)
      done()
    }),
    topoSort(),
    thr.obj(function (row, _, next) {
      next(null, row.source)
    }),
  ]
}

Depsify.prototype.add = function(file, opts) {
  opts = opts || {}
  var basedir = opts.basedir || this._options.basedir
  if (Array.isArray(file)) {
    file.forEach(function (f) {
      this.add(f, opts)
    }, this)
  } else if (typeof file === 'string') {
    file = path.resolve(basedir, file)
    this.pipeline.write({ file: file })
  } else {
    this.pipeline.write(mix({ basedir: basedir }, file, opts))
  }
  return this
}

Depsify.prototype.processor = function(processor) {
  this.pipeline.write({ processor: processor })
  return this
}

Depsify.prototype.transform = function(tr) {
  this.pipeline.write({ transform: tr })
  return this
}

Depsify.prototype.reset = function() {
  this.pipeline = this._createPipeline(this._options)
  this._bundled = false
  this.emit('reset')
}

Depsify.prototype.plugin = function(p, opts) {
  if (Array.isArray(p)) {
    opts = p[1]
    p = p[0]
  }
  var basedir = opts && opts.basedir || this._options.basedir
  if (typeof p === 'function') {
    p(this, opts)
  } else {
    var pfile = resolve.sync(p, { basedir: basedir })
    var f = require(pfile)
    f(this, opts)
  }
  return this
}

Depsify.prototype.bundle = function() {
  if (this._bundled) {
    var recorded = this._recorded
    this.reset()
    recorded.forEach(function (x) {
      this.pipeline.write(x)
    }, this)
  }
  var output = readonly(this.pipeline)

  this.emit('bundle', output)
  this.pipeline.end()

  this._bundled = true

  return output
}

<|endoftext|>


<commit_before>
const depictsWholeNumber = require('depicts-whole-number').onlySafeNumbers

/**
 * Creates a new instance.
 * @class
 */
class ObjectFx {

  /**
   * Expands (unflattens) a flattened object
   * @param {Object} objFlat - flattened object
   * @param {Object} opt - options
   */
  static expand(objFlat, opt) {
    if (Object.prototype.toString.call(objFlat) !== '[object Object]') {
      return null
    }
    const optIsObject = Object.prototype.toString.call(opt) === '[object Object]'
    const DOT = '.'
    const sep = (optIsObject ? opt['KeySeparator'] || DOT : DOT)
    const noArrays = (optIsObject ? opt['NoArrays'] : false)
    const explicitArrays = (optIsObject ? opt['ExplicitArrays'] : false)
    const prefix = 'root' + sep
    const objExp = {}
    const re1 = new RegExp('\\' + sep + '{2,}', 'g')
    const re2 = new RegExp('^\\' + sep + '|\\' + sep + '$', 'g')
    let keys = Object.keys(objFlat)
    for (let i = 0, kln = keys.length; i < kln; i++) {
      const origKey = keys[i]
      let currKey = prefix + origKey
      if (explicitArrays) {
        currKey = currKey.replace(/\[\d+\]/g, (a, b) => {
          return sep + a + sep
        })
        currKey = currKey.replace(re1, sep).replace(re2, '')
      }
      const chunks = currKey.split(sep)
      let obj = objExp
      for (let j = 0, cln = chunks.length; j < cln; j++) {
        let currChunk = chunks[j]
        let nextChunk = chunks[j + 1]
        if (explicitArrays) {
          currChunk = currChunk.replace(/[\[\]]/g, '')
        }
        if (!obj[currChunk]) {
          if (j > cln - 2) {
            obj[currChunk] = objFlat[origKey]
          } else {
            if ((noArrays === false && depictsWholeNumber(nextChunk)) ||
              (explicitArrays && nextChunk.startsWith('[') && nextChunk.endsWith(']') && depictsWholeNumber(nextChunk.replace(/[\[\]]/g, '')))) {
              obj[currChunk] = []
            } else {
              obj[currChunk] = {}
            }
          }
        }
        if (explicitArrays && currChunk.startsWith('[') && currChunk.endsWith(']') && depictsWholeNumber(currChunk.replace(/[\[\]]/g, ''))) {
          currChunk.replace(/[\[\]]/g, '')
        }
        obj = obj[currChunk]
      }
    }
    return objExp.root
  }
}

module.exports = ObjectFx
<commit_msg>complete 'explicit arrays' support, refactor code<commit_after>
const depictsWholeNumber = require('depicts-whole-number').onlySafeNumbers
const c = {
  DOT: '.',
  EMPTY_STRING: '',
  REGEX: {
    SQUARE_BRACKETS: /[\[\]]/g,
    EXPLICIT_ARRAY: /\[(0|[1-9][0-9]*)\]/,
    EXPLICIT_ARRAY_GM: /\[(0|[1-9][0-9]*)\]/g
  }
}

/**
 * ObjectFx static class.
 * @class
 */
class ObjectFx {

  /**
   * Checks for valid array index
   * @param {String} s
   * @return {Boolean}
   */
  static _isValidArrayIndex (s) {
    return depictsWholeNumber(s)
  }

  /**
   * Checks for valid array index within square brackets (named 'explicit array' here)
   * @param {String} s
   * @return {Boolean}
   */
  static _containsValidArrayIndex (s) {
    const result = s.match(c.REGEX.EXPLICIT_ARRAY)
    if (!result) {
      return false
    }
    return depictsWholeNumber(result[1])
  }

  /**
   * Expands (unflattens) a flattened object
   * @param {Object} objFlat - flattened object
   * @param {Object} opt - options
   */
  static expand(objFlat, opt) {
    if (Object.prototype.toString.call(objFlat) !== '[object Object]') {
      return null
    }
    const optIsObject = Object.prototype.toString.call(opt) === '[object Object]'
    const SEP = (optIsObject ? opt['KeySeparator'] || c.DOT : c.DOT)
    const autocreateArrays = (optIsObject ? (opt['AutocreateArrays'] === false ? false : true ) : true)
    const explicitArrays = (optIsObject ? opt['ExplicitArrays'] : false)
    const prefix = 'root' + SEP
    const regexConsecutiveSeparators = new RegExp('\\' + SEP + '{2,}', 'g')
    const regexSurroundingSeparators = new RegExp('^\\' + SEP + '+|\\' + SEP + '+$', 'g')
    const objExp = {}
    let keys = Object.keys(objFlat)
    for (let i = 0, kln = keys.length; i < kln; i++) {
      const origKey = keys[i]
      let currKey = prefix + origKey
      if (explicitArrays) {
        currKey = currKey
                    .replace(c.REGEX.EXPLICIT_ARRAY_GM, SEP + '[$1]' + SEP)
                    .replace(regexConsecutiveSeparators, SEP)
                    .replace(regexSurroundingSeparators, c.EMPTY_STRING)
      }
      const chunks = currKey.split(SEP)
      let obj = objExp
      for (let j = 0, cln = chunks.length; j < cln; j++) {
        let currChunk = chunks[j]
        let nextChunk = chunks[j + 1]
        if (explicitArrays) {
          currChunk = currChunk.replace(c.REGEX.SQUARE_BRACKETS, c.EMPTY_STRING)
        }
        if (!obj[currChunk]) {
          if (j > cln - 2) {
            obj[currChunk] = objFlat[origKey]
          } else {
            if ((autocreateArrays && this._isValidArrayIndex(nextChunk)) ||
                (explicitArrays && this._containsValidArrayIndex(nextChunk))) {
              obj[currChunk] = []
            } else {
              obj[currChunk] = {}
            }
          }
        }
        if (explicitArrays && this._containsValidArrayIndex(currChunk)) {
          currChunk.replace(c.REGEX.SQUARE_BRACKETS, c.EMPTY_STRING)
        }
        obj = obj[currChunk]
      }
    }
    return objExp.root
  }
}

module.exports = ObjectFx
<|endoftext|>


<commit_before>var express = require('express'),
    http = require('http'),
    path = require('path'),
    amqp = require('amqp'),
    bodyParser = require('body-parser')

var status = {
  connection: 'No server connection',
  exchange: 'No exchange established',
  queue: 'No queue established'
}

var app = express()

app.set('port', process.env.PORT || 3000)

app.use(bodyParser.json())

app.post('/start-server', function(req, res){
  app.amqpConnection = amqp.createConnection({ host: 'localhost' })
  // Wait for connection to become established.
  app.amqpConnection.on('ready', function () {
    status.connection = 'Connection is ready';

    var options = {
      type: 'fanout',
      autoDelete: false
    }

    app.amqpConnection.exchange('logs', options, function(exchange){
      app.amqpExchange = exchange;
      status.exchange = 'Exchange is ready';
      res.send(status);
    });
  });
});

app.post('/emit-log', function(req, res){
  var newMessage = req.body;
  console.dir(newMessage);
  console.log('log message level: ' + newMessage.level + ' body: ' + newMessage.message);
  app.amqpExchange.publish('', newMessage.message);
  res.send('message sent');
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('RabbitMQ + Node.js app is running on port ' + app.get('port') + '. To exit press CTRL+C');
});
<commit_msg>update server to automatically connect to the queue<commit_after>var express = require('express'),
    http = require('http'),
    path = require('path'),
    amqp = require('amqp'),
    bodyParser = require('body-parser')

var status = {
  connection: 'No server connection',
  exchange: 'No exchange established',
  queue: 'No queue established'
}

var app = express()

app.set('port', process.env.PORT || 3000)

app.use(bodyParser.json())

app.get('/status', function(req, res){
    res.send(status)
})

app.post('/emit-log', function(req, res){
  var newMessage = req.body;
  console.dir(newMessage);
  console.log('log message level: ' + newMessage.level + ' body: ' + newMessage.message);
  app.amqpExchange.publish('', newMessage.message);
  res.send('message sent');
})

app.amqpConnection = amqp.createConnection({ host: 'localhost' })
// Wait for connection to become established.
app.amqpConnection.on('ready', function () {
  status.connection = 'Connection is ready';

  var options = {
    type: 'fanout',
    autoDelete: false
  }

  // wait for exchange to be created
  app.amqpConnection.exchange('logs', options, function(exchange){
    app.amqpExchange = exchange;
    status.exchange = 'Exchange is ready';
  })
})

http.createServer(app).listen(app.get('port'), function(){
  console.log('RabbitMQ + Node.js app is running on port ' + app.get('port') + '. To exit press CTRL+C');
})
<|endoftext|>


<commit_before>var pull = require('pull-stream')

module.exports = function (map) {
  return function (read) {
    var i = 0, j = 0, last = 0
    var seen = [], started = false, ended = false, _cb, error

    function drain () {
      if(_cb) {
        var cb = _cb
        if(error) {
          _cb = null
          return cb(error)
        }
        if(typeof seen[j] !== 'undefined') {
          _cb = null
          cb(null, seen[j++])
        } else if(j >= last && ended) {
          _cb = null
          cb(true)
        }
      }
    }

    function start () {
      started = true
      if(ended) return drain()
      read(null, function (end, data) {
        if(end) {
          last = i; ended = end
          drain()
        } else {
          var k = i++
          if(!ended)
            start()

          map(data, function (err, data) {
            seen[k] = data
            if(err) error = err
            drain()
          })
        }
      })
    }

    return function (abort, cb) {
      _cb = cb
      if(!started) start()
      drain()
    }
  }
}
<commit_msg>allow streams with null or undefined items<commit_after>var pull = require('pull-stream')

module.exports = function (map) {
  return function (read) {
    var i = 0, j = 0, last = 0
    var seen = [], started = false, ended = false, _cb, error

    function drain () {
      if(_cb) {
        var cb = _cb
        if(error) {
          _cb = null
          return cb(error)
        }
        if(Object.hasOwnProperty.call(seen, j)) {
          _cb = null
          cb(null, seen[j++])
        } else if(j >= last && ended) {
          _cb = null
          cb(true)
        }
      }
    }

    function start () {
      started = true
      if(ended) return drain()
      read(null, function (end, data) {
        if(end) {
          last = i; ended = end
          drain()
        } else {
          var k = i++
          if(!ended)
            start()

          map(data, function (err, data) {
            seen[k] = data
            if(err) error = err
            drain()
          })
        }
      })
    }

    return function (abort, cb) {
      _cb = cb
      if(!started) start()
      drain()
    }
  }
}
<|endoftext|>


<commit_before>"use strict";

var CachingWriter = require("broccoli-caching-writer");

module.exports = LessCompiler;

LessCompiler.prototype = Object.create(CachingWriter.prototype);
LessCompiler.prototype.constructor = LessCompiler;

function uniq(value, index, arr) {
  return arr.indexOf(value) === index;
}

function LessCompiler(sourceNodes, inputFile, outputFile, _options) {
  if (!(this instanceof LessCompiler)) {
    return new LessCompiler(sourceNodes, inputFile, outputFile, _options);
  }

  CachingWriter.call(
    this,
    Array.isArray(sourceNodes) ? sourceNodes : [sourceNodes],
    _options
  );

  if (!outputFile) {
    outputFile = inputFile.replace(/\.less/i, '.css');
  }

  // clone the _options hash to prevent mutating what was
  // passed into us with fallback values. see issue #29
  var options = require("lodash.merge")({}, _options);

  if (options.sourceMap) {
    if (typeof options.sourceMap !== "object") {
      options.sourceMap = {};
    }

    if (!options.sourceMap.sourceMapURL) {
      options.sourceMap.sourceMapURL = outputFile + ".map";
    }
  }

  this.lessOptions = options;
  this.sourceNodes = sourceNodes;
  this.inputFile = inputFile;
  this.outputFile = outputFile;
}

LessCompiler.prototype.build = function() {
  var fs = require("fs");
  var less = require("less");
  var path = require("path");
  var mkdirp = require("mkdirp");

  var destFile = this.outputPath + "/" + this.outputFile;

  mkdirp.sync(path.dirname(destFile));

  var lessOptions = {
    filename: require("include-path-searcher").findFileSync(
      this.inputFile,
      this.inputPaths
    ),
    paths: []
  };

  require("lodash.merge")(lessOptions, this.lessOptions);

  lessOptions.paths = [path.dirname(lessOptions.filename)]
    .concat(this.inputPaths)
    .concat(lessOptions.paths)
    .filter(uniq);

  var data = fs.readFileSync(lessOptions.filename, "utf8");

  return less
    .render(data, lessOptions)
    .catch(function(err) {
      less.writeError(err, lessOptions);

      throw err;
    })
    .then(
      (function(output) {
        fs.writeFileSync(destFile, output.css, { encoding: "utf8" });

        var sourceMapURL = lessOptions.sourceMap &&
          lessOptions.sourceMap.sourceMapURL;

        if (sourceMapURL) {
          fs.writeFileSync(this.outputPath + "/" + sourceMapURL, output.map, {
            encoding: "utf8"
          });
        }
      }).bind(this)
    );
};
<commit_msg>fix: no use LessError.proto.toString() in place of obsolete writeError()<commit_after>'use strict';

var CachingWriter = require('broccoli-caching-writer');

module.exports = LessCompiler;

LessCompiler.prototype = Object.create(CachingWriter.prototype);
LessCompiler.prototype.constructor = LessCompiler;

function uniq(value, index, arr) {
  return arr.indexOf(value) === index;
}

function LessCompiler(sourceNodes, inputFile, outputFile, _options) {
  if (!(this instanceof LessCompiler)) {
    return new LessCompiler(sourceNodes, inputFile, outputFile, _options);
  }

  CachingWriter.call(this, Array.isArray(sourceNodes) ? sourceNodes : [sourceNodes], _options);

  if (!outputFile) {
    outputFile = inputFile.replace(/\.less/i, '.css');
  }

  // clone the _options hash to prevent mutating what was
  // passed into us with fallback values. see issue #29
  var options = require('lodash.merge')({}, _options);

  if (options.sourceMap) {
    if (typeof options.sourceMap !== 'object') {
      options.sourceMap = {};
    }

    if (!options.sourceMap.sourceMapURL) {
      options.sourceMap.sourceMapURL = outputFile + '.map';
    }
  }

  this.lessOptions = options;
  this.sourceNodes = sourceNodes;
  this.inputFile = inputFile;
  this.outputFile = outputFile;
}

LessCompiler.prototype.build = function() {
  var fs = require('fs');
  var less = require('less');
  var path = require('path');
  var mkdirp = require('mkdirp');

  var destFile = this.outputPath + '/' + this.outputFile;

  mkdirp.sync(path.dirname(destFile));

  var lessOptions = {
    filename: require('include-path-searcher').findFileSync(this.inputFile, this.inputPaths),
    paths: []
  };

  require('lodash.merge')(lessOptions, this.lessOptions);

  lessOptions.paths = [path.dirname(lessOptions.filename)]
    .concat(this.inputPaths)
    .concat(lessOptions.paths)
    .filter(uniq);

  var data = fs.readFileSync(lessOptions.filename, 'utf8');

  return less
    .render(data, lessOptions)
    .catch(function(err) {
      if (!lessOptions || (lessOptions && !lessOptions.silent)) {
        console.error(err.toString(lessOptions));
      }

      throw err;
    })
    .then(
      function(output) {
        fs.writeFileSync(destFile, output.css, { encoding: 'utf8' });

        var sourceMapURL = lessOptions.sourceMap && lessOptions.sourceMap.sourceMapURL;

        if (sourceMapURL) {
          fs.writeFileSync(this.outputPath + '/' + sourceMapURL, output.map, {
            encoding: 'utf8'
          });
        }
      }.bind(this)
    );
};
<|endoftext|>


<commit_before>/**
 * Created by dob on 01.04.14.
 */

var molecuel, elements;
var grid = require('gridfs-uploader');
var multer = require('multer');

/**
 * file module for molecuel CMS
 */
var files = function() {

  var self = this;

  molecuel.on('mlcl::elements::registrations:pre',  function(module) {

    elements = module;

    // get native mongo driver
    var mongo = elements.database.database.mongo;
    var db = elements.database.database.connection.db;
    self.grid = new grid(mongo);
    self.grid.db = db;

    self.fileSchema =  {
      // Definition of the filename
      filename: { type: String},
      // Define the content type
      contentType: { type: String},
      // length data
      length: {type: Number, 'default': 0, form: {readonly: true}},
      chunkSize: {type: Number, 'default': 0, form: {readonly: true}},
      // upload date
      uploadDate: { type: Date, 'default': Date.now, form: {readonly: true}},

      // additional metadata
      metadata: {
        filename: {type: String}
      },
      md5: { type: String, trim: true }
    };

    var schemaDefinition = {
      schemaName: 'file',
      schema: self.fileSchema,
      options: {
        indexable: true,
        avoidTranslate: true,
        noStrict: true,
        collection: 'fs.files',
        subSchema: true
      }
    };
    elements.registerSchemaDefinition(schemaDefinition);
    elements.registerTypeHandler('file', self.download);
  });

  molecuel.on('mlcl::core::middlewareRegister:post', function(module, app) {
    app.use(multer());
  });
};

/* ************************************************************************
 SINGLETON CLASS DEFINITION
 ************************************************************************ */
var instance = null;

/**
 * Singleton getInstance definition
 * @return singleton class
 */
var getInstance = function () {
  if (instance === null) {
    instance = new files();
  }
  return instance;
};

/**
 * Upload handler for files
 * @param req
 * @param res
 * @param next
 */
files.prototype.upload = function(req, res) {

  var files = getInstance();

  /**
   * renderResult
   * renders the result from the db into a json object with delete url etc
   * @param res
   * @param dataset
   */
  var renderResult = function renderResult(res, dataset) {
    var files = [];

    if(dataset) {
      var result = {
        name: dataset.filename,
        size: dataset.length,
        url: dataset.url,
        thumbnailUrl: dataset.url,
        deleteUrl: dataset.url,
        deleteType: 'DELETE',
        result: dataset
      };

      // file path are never relative
      if (result.url && result.url.charAt(0) !== '/') {
        result.url = '/'+result.url;
      }

      files.push(result);
    }

    res.send({files: files});
  };

  if(req.files) {
    files.grid.putUniqueFile(req.files.files.path, req.files.files.originalname, null, function (err, result) {
      var dbResult;
      var fileModel = elements.getModel('file');

      if(err && err.name === 'NotUnique') {
        dbResult = err.result;
        if(dbResult) {
          if(!dbResult.url) {
            fileModel.findById(dbResult._id, function(err, myobj) {
              // save it again to ensure url creation
              myobj.save(function(err, obj) {
                renderResult(res, obj);
              });
            });
          } else {
            renderResult(res, dbResult);
          }
        }

      } else if(err) {
        res.send(500);
      } else {
        dbResult = result;
        fileModel.findById(dbResult._id, function(err, myobj) {
          // save it again to ensure url creation
          myobj.save(function(err, obj) {
            renderResult(res, obj);
          });
        });
      }

    });
  } else {
    res.send(500);
  }
};

/**
 * Download handler for elements file type
 * @param req
 * @param res
 * @param next
 */
files.prototype.download = function download(req, res, next) {
  var files = getInstance();
  var grid = files.grid;
  var id = res.locals.data.main._id;
  if(req.method === 'GET') {
    grid.getFileStream(id, function(err, filestream) {
      if(!filestream) {
        next();
      } else {
        filestream.pipe(res);
      }
    });
  } else if(req.method === 'DELETE') {
    files.deleteFile(req, res, next);
  }
};

/**
 * Delete file middleware function
 * @param req
 * @param res
 * @param next
 */
files.prototype.deleteFile = function deleteFile(req, res) {
  var files = getInstance();
  var grid = files.grid;
  var id = res.locals.data.main._id;
  grid.deleteFile(id, null, function(err) {
    if(err) {
      res.send(500);
    } else {
      if(elements && elements.elastic) {
        elements.elastic.delete('file', id, function() {
          res.send(200);
        });
      }
    }
  });
};

var init = function(mlcl) {
  molecuel = mlcl;
  return getInstance();
};

module.exports = init;<commit_msg>new method saveFile<commit_after>/**
 * Created by dob on 01.04.14.
 */

var molecuel, elements;
var grid = require('gridfs-uploader');
var multer = require('multer');

/**
 * file module for molecuel CMS
 */
var files = function() {

  var self = this;

  molecuel.on('mlcl::elements::registrations:pre',  function(module) {

    elements = module;

    // get native mongo driver
    var mongo = elements.database.database.mongo;
    var db = elements.database.database.connection.db;
    self.grid = new grid(mongo);
    self.grid.db = db;

    self.fileSchema =  {
      // Definition of the filename
      filename: { type: String},
      // Define the content type
      contentType: { type: String},
      // length data
      length: {type: Number, 'default': 0, form: {readonly: true}},
      chunkSize: {type: Number, 'default': 0, form: {readonly: true}},
      // upload date
      uploadDate: { type: Date, 'default': Date.now, form: {readonly: true}},

      // additional metadata
      metadata: {
        filename: {type: String}
      },
      md5: { type: String, trim: true }
    };

    var schemaDefinition = {
      schemaName: 'file',
      schema: self.fileSchema,
      options: {
        indexable: true,
        avoidTranslate: true,
        noStrict: true,
        collection: 'fs.files',
        subSchema: true
      }
    };
    elements.registerSchemaDefinition(schemaDefinition);
    elements.registerTypeHandler('file', self.download);
  });

  molecuel.on('mlcl::core::middlewareRegister:post', function(module, app) {
    app.use(multer());
    // send init event
    molecuel.emit('mlcl::files::init:post', self);
  });

};

/* ************************************************************************
 SINGLETON CLASS DEFINITION
 ************************************************************************ */
var instance = null;

/**
 * Singleton getInstance definition
 * @return singleton class
 */
var getInstance = function () {
  if (instance === null) {
    instance = new files();
  }
  return instance;
};

/**
 * Upload handler for files
 * @param req
 * @param res
 * @param next
 */
files.prototype.upload = function(req, res) {
  if(req.files) {
    this.saveFile(req.files.files.path, req.files.files.originalname, null, function(err, result) {
      if(err) {
        return res.send(500, err);
      }
      res.send({files: [result]});
    });
  } else {
    res.send(500);
  }
};

/**
 * Download handler for elements file type
 * @param req
 * @param res
 * @param next
 */
files.prototype.download = function download(req, res, next) {
  var files = getInstance();
  var grid = files.grid;
  var id = res.locals.data.main._id;
  if(req.method === 'GET') {
    grid.getFileStream(id, function(err, filestream) {
      if(!filestream) {
        next();
      } else {
        filestream.pipe(res);
      }
    });
  } else if(req.method === 'DELETE') {
    files.deleteFile(req, res, next);
  }
};

/**
 * Delete file middleware function
 * @param req
 * @param res
 * @param next
 */
files.prototype.deleteFile = function deleteFile(req, res) {
  var files = getInstance();
  var grid = files.grid;
  var id = res.locals.data.main._id;
  grid.deleteFile(id, null, function(err) {
    if(err) {
      res.send(500);
    } else {
      if(elements && elements.elastic) {
        elements.elastic.delete('file', id, function() {
          res.send(200);
        });
      }
    }
  });
};

files.prototype.saveFile = function saveFile(path, name, options, callback) {
  var files = getInstance();

  /**
   * renderResult
   * renders the result from the db into a json object with delete url etc
   * @param res
   * @param dataset
   */
  var renderResult = function renderResult(dataset) {
    var result;
    if(dataset) {
      result = {
        name: dataset.filename,
        size: dataset.length,
        url: dataset.url,
        thumbnailUrl: dataset.url,
        deleteUrl: dataset.url,
        deleteType: 'DELETE',
        result: dataset
      };

      // file path are never relative
      if (result.url && result.url.charAt(0) !== '/') {
        result.url = '/'+result.url;
      }
    }
    return result;
  };

  files.grid.putUniqueFile(path, name, null, function(err, result) {
    var dbResult;
    var fileModel = elements.getModel('file');

    if(err && err.name === 'NotUnique') {
      dbResult = err.result;
      if(dbResult) {
        if(!dbResult.url) {
          fileModel.findById(dbResult._id, function(err, myobj) {
            // save it again to ensure url creation
            myobj.save(function(err, obj) {
              if(err) {
                return callback(err);
              }
              return callback(null, renderResult(obj));
            });
          });
        } else {
          return callback(null, renderResult(dbResult));
        }
      }
    } else if(err) {
      return callback(err);
    } else {
      dbResult = result;
      fileModel.findById(dbResult._id, function(err, myobj) {
        if(options && options.url) {
          myobj.url = options.url;
        }
        // save it again to ensure url creation
        myobj.save(function(err, obj) {
          if(err) {
            return callback(err);
          }
          return callback(null, renderResult(obj));
        });
      });
    }
  });
};

var init = function(mlcl) {
  molecuel = mlcl;
  return getInstance();
};

module.exports = init;<|endoftext|>


<commit_before>"use strict"
var path = require('path')
  , sprintf = require('sprintf-js')

var i18n = function() {
  if(!(this instanceof i18n)) {
    return new i18n()
  }

  /**
   * This is an object which will contain any config set by the user.
   * @type {Object}
   */
  this._config = {}

  /**
   * An object which will hold all the required contexts.
   * @type {Object}
   */
  this._context = {}

  /**
   * The current locale
   * @type {String}
   */
  this._currentLocale = undefined

  /**
   * Object consisting of rules of available configs.
   * @type {Object}
   */
  this._configDetails = {
    defaultLocale: {
      required: true
    },
    dir: {
      required: true
    },
    fallback: {
      value: true
    }
  }
}

/**
 * Method used to configure the i18n module
 * @param  {Object} opts  Configuration object.
 * @return {i18n}         The object itself.
 */
i18n.prototype.configure = function(opts) {
  for(var key in this._configDetails) {
    if((opts[key] !== undefined)
        && ((typeof opts[key] !== 'string') || (opts[key].length > 0))) {
      this._config[key] = opts[key]
    } else if(this._configDetails[key].required) {
      throw new Error('Please provide a ' + key + ' for your application.')
    } else if(this._configDetails[key].value !== undefined) {
      this._config[key] = this._configDetails[key].value
    }
  }

  this._currentLocale = this._config.defaultLocale
  this._getContext()
  return this
}

/**
 * Method used to integrate this object with the middleware.
 */
i18n.prototype.init = function() {
  var self = this
  return function(req, res, next) {
    res.i18n = self
    res.locals.i18n = self
    next()
  }
}

/**
 * Method used to retrieve the context from the specified directory.
 * @return {i18n} The instance itself
 */
i18n.prototype._getContext = function() {
  if(this._context[this._currentLocale] === undefined)
    this._context[this._currentLocale]
        = require(path.join(this._config.dir, (this._currentLocale + '.js')))
  return this
}

/**
 * Method used to get the current locale.
 * @return {String} The current locale
 */
i18n.prototype.getLocale = function() {
  return this._currentLocale
}

/**
 * Method used to change the locale.
 * @param {String} locale The new locale
 */
i18n.prototype.setLocale = function(locale) {
  this._currentLocale = locale
  this._getContext()
}

/**
 * Translation method.
 * @param  {String}   path An object path
 * @param  {Boolean}  def  True if the default locale will be used false other wise
 * @return {String}      The phrase
 */
i18n.prototype.__ = function(path){
  var args = Array.prototype.slice.call(arguments, 1)
  return sprintf.vsprintf(this._getPhrase(path), args)
}

i18n.prototype.__n = function(path) {
  var args = Array.prototype.slice.call(arguments, 1)
  var count = arguments[arguments.length - 1]

  if(count === 0) {
    path += '.zero'
  } else if(count === 1)  {
    path += '.one'
  } else {
    path += '.many'

  }

  return sprintf.vsprintf(this._getPhrase(path), args)
}

i18n.prototype._getPhrase = function(path, def) {
  var obj = (def) ? (this._context[this._config.defaultLocale])
      : (this._context[this._currentLocale])
    , str = path

  path.split('.').forEach(function(data) {
    if(obj[data] !== undefined) {
      if(typeof obj[data] === 'string') {
        str = obj[data]
      } else {
        obj = obj[data]
      }
    } else if(this._currentLocale !== this._config.defaultLocale
        && !(def) && (this._config.fallback)) {
      str = this._getPhrase(path, true)
    }
  }.bind(this))

  return str
}

module.exports = i18n()<commit_msg>rename _getPhrase to _translate and add validation logic inside __n.<commit_after>"use strict"
var path = require('path')
  , sprintf = require('sprintf-js')

var i18n = function() {
  if(!(this instanceof i18n)) {
    return new i18n()
  }

  /**
   * This is an object which will contain any config set by the user.
   * @type {Object}
   */
  this._config = {}

  /**
   * An object which will hold all the required contexts.
   * @type {Object}
   */
  this._context = {}

  /**
   * The current locale
   * @type {String}
   */
  this._currentLocale = undefined

  /**
   * Object consisting of rules of available configs.
   * @type {Object}
   */
  this._configDetails = {
    defaultLocale: {
      required: true
    },
    dir: {
      required: true
    },
    fallback: {
      value: true
    }
  }
}

/**
 * Method used to configure the i18n module
 * @param  {Object} opts  Configuration object.
 * @return {i18n}         The object itself.
 */
i18n.prototype.configure = function(opts) {
  for(var key in this._configDetails) {
    if((opts[key] !== undefined)
        && ((typeof opts[key] !== 'string') || (opts[key].length > 0))) {
      this._config[key] = opts[key]
    } else if(this._configDetails[key].required) {
      throw new Error('Please provide a ' + key + ' for your application.')
    } else if(this._configDetails[key].value !== undefined) {
      this._config[key] = this._configDetails[key].value
    }
  }

  this._currentLocale = this._config.defaultLocale
  this._getContext()
  return this
}

/**
 * Method used to integrate this object with the middleware.
 */
i18n.prototype.init = function() {
  var self = this
  return function(req, res, next) {
    res.i18n = self
    res.locals.i18n = self
    next()
  }
}

/**
 * Method used to retrieve the context from the specified directory.
 * @return {i18n} The instance itself
 */
i18n.prototype._getContext = function() {
  if(this._context[this._currentLocale] === undefined)
    this._context[this._currentLocale]
        = require(path.join(this._config.dir, (this._currentLocale + '.js')))
  return this
}

/**
 * Method used to get the current locale.
 * @return {String} The current locale
 */
i18n.prototype.getLocale = function() {
  return this._currentLocale
}

/**
 * Method used to change the locale.
 * @param {String} locale The new locale
 */
i18n.prototype.setLocale = function(locale) {
  this._currentLocale = locale
  this._getContext()
}

/**
 * Translation method.
 * @param  {String}   path An object path
 * @param  {Boolean}  def  True if the default locale will be used false other wise
 * @return {String}      The phrase
 */
i18n.prototype.__ = function(path){
  var args = Array.prototype.slice.call(arguments, 1)
  return sprintf.vsprintf(this._translate(path), args)
}

i18n.prototype.__n = function(path) {
  if(arguments.length === 1) {
    return this.__(path)
  } else if(typeof arguments[arguments.length - 1] === 'number') {
    var args = Array.prototype.slice.call(arguments, 1)
    var count = arguments[arguments.length - 1]

    if(count === 0) {
      path += '.zero'
    } else if(count === 1)  {
      path += '.one'
    } else {
      path += '.many'

    }
    return sprintf.vsprintf(this._translate(path), args)
  } else {
    throw new Error('Last parameter should be a number.')
  }
}

i18n.prototype._translate = function(path, def) {
  var obj = (def) ? (this._context[this._config.defaultLocale])
      : (this._context[this._currentLocale])
    , str = path

  path.split('.').forEach(function(data) {
    if(obj[data] !== undefined) {
      if(typeof obj[data] === 'string') {
        str = obj[data]
      } else {
        obj = obj[data]
      }
    } else if(this._currentLocale !== this._config.defaultLocale
        && !(def) && (this._config.fallback)) {
      str = this._translate(path, true)
    }
  }.bind(this))

  return str
}

module.exports = i18n()<|endoftext|>


<commit_before>var _ = require("lodash");
var fs = require('fs');
var md5 = require('MD5');

module.exports = function cantrip(options) {

	options = options || {};

	/**
	 * This dataStore object is the interface through which you can access the raw data of cantrip.
	 * It is exposed on the returned middleware, so you can access its methods through it using cantrip.get() etc.
	 * @type {Object}
	 */
	var dataStore = require('./lib/dataStore')(options);

	/**
	 * This method is the one that handles the request
	 */
	var handle = function(req, res, next) {
		if (req.method === "GET") {
			get(req, res, next);
		} else if (req.method === "POST") {
			post(req, res, next);
		} else if (req.method === "PUT") {
			put(req, res, next);
		} else if (req.method === "PATCH") {
			put(req, res, next, true);
		} else if (req.method === "DELETE") {
			del(req, res, next);
		}
	};

	var get = function(req, res, next) {
		var targetNode = dataStore.get(req.path);
		if (_.isNull(targetNode)) {
			return next({
				status: 404,
				error: "Requested node doesn't exist."
			})
		}
		if (_.isObject(targetNode) || _.isArray(targetNode)) {
			res.body = _.cloneDeep(targetNode);
			next();
		} else {
			res.body = {
				value: targetNode
			};
			next();
		}
	};
	var post = function(req, res, next) {
		var targetNode = dataStore.get(req.path);
		if (_.isNull(targetNode)) {
			return next({
				status: 404,
				error: "Requested node doesn't exist."
			})
		}
		//If it's an array, post the new entry to that array
		if (_.isArray(targetNode)) {
			//Add ids to all objects within arrays in the sent object
			addMetadataToModels(req.body);
			//If the posted body is an object itself, add an id to it
			if (_.isObject(req.body) && !_.isArray(req.body)) {
				//Extend the whole object with an _id property, but only if it doesn't already have one
				req.body = _.extend({
					_id: md5(JSON.stringify(req.body) + (new Date()).getTime() + Math.random()),
					_createdDate: (new Date()).getTime(),
					_modifiedDate: (new Date()).getTime()
				}, req.body);
			}
			//Check if the given ID already exists in the collection
			for (var i = 0; i < targetNode.length; i++) {
				if (targetNode[i]._id === req.body._id) {
					return next({
						status: 400,
						error: "An object with the same _id already exists in this collection."
					});
				}
			}
			//Push it to the target array
			dataStore.set(req.path, req.body, false);
			//Set response
			res.body = _.cloneDeep(req.body);
			return next();

		} else {
			return next({
				status: 400,
				error: "Can't POST to a resource. Use PUT instead."
			});
		}
	};
	var put = function(req, res, next, patch) {
		var targetNode = dataStore.get(req.path);
		if (_.isNull(targetNode)) {
			return next({
				status: 404,
				error: "Requested node doesn't exist."
			})
		}
		if (_.isObject(targetNode) && !_.isArray(targetNode)) {
			addMetadataToModels(req.body);
			//If the target had previously had a _modifiedDate property, set it to the current time
			if (targetNode._modifiedDate) req.body._modifiedDate = (new Date()).getTime();

			var save = function() {
				dataStore.set(req.path, req.body, patch);
				//Set response
				res.body = {
					"success": true
				};
				return next();
			};
			//If it's an element inside a collection, make sure the overwritten _id is not present in the collection
			if (req.body._id && targetNode._id && req.body._id !== targetNode._id) {
				var parent = dataStore.parent(req.path)
				for (var i = 0; i < parent.length; i++) {
					if (parent[i]._id === req.body._id) {
						return next({
							status: 400,
							error: "An object with the same _id already exists in this collection."
						});
					}
				}
				//If there was no such problem
				save();
			} else {
				save();
			}
		} else {
			return next({
				status: 400,
				error: "Can't PUT a collection. Use POST instead."
			});
		}
	};
	var del = function(req, res, next) {
		//Get the parent node so we can unset the target
		var parent = dataStore.parent(req.path)
		//Last identifier in the path
		var index = _.last(req.path.split("/"));
		//If it's an object (not an array), then we just unset the key with the keyword delete
		if (_.isObject(parent) && !_.isArray(parent)) {
			//We're not letting users delete the _id
			if ((index + "")[0] === "_") {
				return next({
					status: 400,
					error: "You can't delete an object's metadata."
				});
			} else {
				dataStore.delete(req.path)
				//Set response
				res.body = {
					"success": true
				};
				return next();
			}
			//If it's an array, we must remove it by id with the splice method
		} else if (_.isArray(parent)) {
			dataStore.delete(req.path)
			//Set response
			res.body = {
				"success": true
			};
			return next();
		}

	};
	//Recursively add _ids to all objects within an array (but not arrays) within the specified object.
	var addMetadataToModels = function(obj) {
		//Loop through the objects keys
		for (var key in obj) {
			//If the value of the key is an array (means it's a collection), go through all of its contents
			if (_.isArray(obj[key])) {
				for (var i = 0; i < obj[key].length; i++) {
					//Assign an id to all objects
					if (_.isObject(obj[key][i]) && !_.isArray(obj[key][i])) {
						obj[key][i] = _.extend({
							_id: md5(JSON.stringify(obj[key][i]) + (new Date()).getTime() + Math.random()),
							_createdDate: (new Date()).getTime(),
							_modifiedDate: (new Date()).getTime()
						}, obj[key][i]);
						//Modify the _modifiedDate metadata property
						obj[key][i]._modifiedDate = (new Date()).getTime();
					}
				}
				//If it's an object, call the recursive method with that object
			} else if (_.isObject(obj[key])) {
				addMetadataToModels(obj[key]);
			}
		}
	};

	handle.get = dataStore.get;
	handle.post = dataStore.post;
	handle.delete = dataStore.delete;
	handle.put = dataStore.put;

	return handle;

}

<commit_msg>Keys starting with an underscore on the root are not returned by default<commit_after>var _ = require("lodash");
var fs = require('fs');
var md5 = require('MD5');

module.exports = function cantrip(options) {

	options = options || {};

	/**
	 * This dataStore object is the interface through which you can access the raw data of cantrip.
	 * It is exposed on the returned middleware, so you can access its methods through it using cantrip.get() etc.
	 * @type {Object}
	 */
	var dataStore = require('./lib/dataStore')(options);

	/**
	 * This method is the one that handles the request
	 */
	var handle = function(req, res, next) {
		if (req.method === "GET") {
			get(req, res, next);
		} else if (req.method === "POST") {
			post(req, res, next);
		} else if (req.method === "PUT") {
			put(req, res, next);
		} else if (req.method === "PATCH") {
			put(req, res, next, true);
		} else if (req.method === "DELETE") {
			del(req, res, next);
		}
	};

	var get = function(req, res, next) {
		var targetNode = dataStore.get(req.path);
		if (_.isNull(targetNode)) {
			return next({
				status: 404,
				error: "Requested node doesn't exist."
			})
		}
		if (_.isObject(targetNode) || _.isArray(targetNode)) {
			res.body = _.cloneDeep(targetNode);
			if (req.path === "/") {
				for (var key in res.body) {
					if (key[0] === "_") delete res.body[key];
				}
			}
			next();
		} else {
			res.body = {
				value: targetNode
			};
			next();
		}
	};
	var post = function(req, res, next) {
		var targetNode = dataStore.get(req.path);
		if (_.isNull(targetNode)) {
			return next({
				status: 404,
				error: "Requested node doesn't exist."
			})
		}
		//If it's an array, post the new entry to that array
		if (_.isArray(targetNode)) {
			//Add ids to all objects within arrays in the sent object
			addMetadataToModels(req.body);
			//If the posted body is an object itself, add an id to it
			if (_.isObject(req.body) && !_.isArray(req.body)) {
				//Extend the whole object with an _id property, but only if it doesn't already have one
				req.body = _.extend({
					_id: md5(JSON.stringify(req.body) + (new Date()).getTime() + Math.random()),
					_createdDate: (new Date()).getTime(),
					_modifiedDate: (new Date()).getTime()
				}, req.body);
			}
			//Check if the given ID already exists in the collection
			for (var i = 0; i < targetNode.length; i++) {
				if (targetNode[i]._id === req.body._id) {
					return next({
						status: 400,
						error: "An object with the same _id already exists in this collection."
					});
				}
			}
			//Push it to the target array
			dataStore.set(req.path, req.body, false);
			//Set response
			res.body = _.cloneDeep(req.body);
			return next();

		} else {
			return next({
				status: 400,
				error: "Can't POST to a resource. Use PUT instead."
			});
		}
	};
	var put = function(req, res, next, patch) {
		var targetNode = dataStore.get(req.path);
		if (_.isNull(targetNode)) {
			return next({
				status: 404,
				error: "Requested node doesn't exist."
			})
		}
		if (_.isObject(targetNode) && !_.isArray(targetNode)) {
			addMetadataToModels(req.body);
			//If the target had previously had a _modifiedDate property, set it to the current time
			if (targetNode._modifiedDate) req.body._modifiedDate = (new Date()).getTime();

			var save = function() {
				dataStore.set(req.path, req.body, patch);
				//Set response
				res.body = {
					"success": true
				};
				return next();
			};
			//If it's an element inside a collection, make sure the overwritten _id is not present in the collection
			if (req.body._id && targetNode._id && req.body._id !== targetNode._id) {
				var parent = dataStore.parent(req.path)
				for (var i = 0; i < parent.length; i++) {
					if (parent[i]._id === req.body._id) {
						return next({
							status: 400,
							error: "An object with the same _id already exists in this collection."
						});
					}
				}
				//If there was no such problem
				save();
			} else {
				save();
			}
		} else {
			return next({
				status: 400,
				error: "Can't PUT a collection. Use POST instead."
			});
		}
	};
	var del = function(req, res, next) {
		//Get the parent node so we can unset the target
		var parent = dataStore.parent(req.path)
		//Last identifier in the path
		var index = _.last(req.path.split("/"));
		//If it's an object (not an array), then we just unset the key with the keyword delete
		if (_.isObject(parent) && !_.isArray(parent)) {
			//We're not letting users delete the _id
			if ((index + "")[0] === "_") {
				return next({
					status: 400,
					error: "You can't delete an object's metadata."
				});
			} else {
				dataStore.delete(req.path)
				//Set response
				res.body = {
					"success": true
				};
				return next();
			}
			//If it's an array, we must remove it by id with the splice method
		} else if (_.isArray(parent)) {
			dataStore.delete(req.path)
			//Set response
			res.body = {
				"success": true
			};
			return next();
		}

	};
	//Recursively add _ids to all objects within an array (but not arrays) within the specified object.
	var addMetadataToModels = function(obj) {
		//Loop through the objects keys
		for (var key in obj) {
			//If the value of the key is an array (means it's a collection), go through all of its contents
			if (_.isArray(obj[key])) {
				for (var i = 0; i < obj[key].length; i++) {
					//Assign an id to all objects
					if (_.isObject(obj[key][i]) && !_.isArray(obj[key][i])) {
						obj[key][i] = _.extend({
							_id: md5(JSON.stringify(obj[key][i]) + (new Date()).getTime() + Math.random()),
							_createdDate: (new Date()).getTime(),
							_modifiedDate: (new Date()).getTime()
						}, obj[key][i]);
						//Modify the _modifiedDate metadata property
						obj[key][i]._modifiedDate = (new Date()).getTime();
					}
				}
				//If it's an object, call the recursive method with that object
			} else if (_.isObject(obj[key])) {
				addMetadataToModels(obj[key]);
			}
		}
	};

	handle.get = dataStore.get;
	handle.post = dataStore.post;
	handle.delete = dataStore.delete;
	handle.put = dataStore.put;

	return handle;

}

<|endoftext|>