<commit_before>'use strict';

var _ = require('lodash');
var path = require('path');
var utils = require('./utils');
var OPTIONAL_REGEX = /^optional!/;

/**
 * Create an API method from the declared spec.
 *
 * @param [spec.method='GET'] Request Method (POST, GET, DELETE, PUT)
 * @param [spec.path=''] Path to be appended to the API BASE_PATH, joined with
 *  the instance's path (e.g. 'charges' or 'customers')
 * @param [spec.required=[]] Array of required arguments in the order that they
 *  must be passed by the consumer of the API. Subsequent optional arguments are
 *  optionally passed through a hash (Object) as the penultimate argument
 *  (preceeding the also-optional callback argument
 */
function stripeMethod(spec) {
  var commandPath = typeof spec.path == 'function' ? spec.path
                  : utils.makeURLInterpolator(spec.path || '');
  var requestMethod = (spec.method || 'GET').toUpperCase();
  var urlParams = spec.urlParams || [];

  return function() {
    var self = this;
    var args = [].slice.call(arguments);

    var callback = typeof args[args.length - 1] == 'function' && args.pop();
    var deferred = this.createDeferred(callback);
    var urlData = this.createUrlData();

    for (var i = 0, l = urlParams.length; i < l; ++i) {
      var arg = args[0];
      var param = urlParams[i];

      var isOptional = OPTIONAL_REGEX.test(param);
      param = param.replace(OPTIONAL_REGEX, '');

      if (!arg) {
        if (isOptional) {
          urlData[param] = '';
          continue;
        }
        throw new Error('Stripe: I require argument "' + urlParams[i] + '", but I got: ' + arg);
      }

      urlData[param] = args.shift();
    }

    var data = utils.getDataFromArgs(args);
    var opts = utils.getOptionsFromArgs(args);

    if (args.length) {
      throw new Error(
        'Stripe: Unknown arguments (' + args + '). Did you mean to pass an options object? ' +
        'See https://github.com/stripe/stripe-node/wiki/Passing-Options.'
      );
    }

    var requestPath = this.createFullPath(commandPath, urlData);
    function requestCallback(err, response) {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve(
          spec.transformResponseData ?
            spec.transformResponseData(response) :
            response
        );
      }
    };

    var options = {headers: _.extend(opts.headers, spec.headers)};
    self._request(requestMethod, requestPath, data, opts.auth, options, requestCallback);

    return deferred.promise;
  };
};

module.exports = stripeMethod;
<commit_msg>Add a comment to clarify how this function works<commit_after>'use strict';

var _ = require('lodash');
var path = require('path');
var utils = require('./utils');
var OPTIONAL_REGEX = /^optional!/;

/**
 * Create an API method from the declared spec.
 *
 * @param [spec.method='GET'] Request Method (POST, GET, DELETE, PUT)
 * @param [spec.path=''] Path to be appended to the API BASE_PATH, joined with
 *  the instance's path (e.g. 'charges' or 'customers')
 * @param [spec.required=[]] Array of required arguments in the order that they
 *  must be passed by the consumer of the API. Subsequent optional arguments are
 *  optionally passed through a hash (Object) as the penultimate argument
 *  (preceeding the also-optional callback argument
 */
function stripeMethod(spec) {
  var commandPath = typeof spec.path == 'function' ? spec.path
                  : utils.makeURLInterpolator(spec.path || '');
  var requestMethod = (spec.method || 'GET').toUpperCase();
  var urlParams = spec.urlParams || [];

  return function() {
    var self = this;
    var args = [].slice.call(arguments);

    var callback = typeof args[args.length - 1] == 'function' && args.pop();
    var deferred = this.createDeferred(callback);
    var urlData = this.createUrlData();

    for (var i = 0, l = urlParams.length; i < l; ++i) {
      // Note that we shift the args array after every iteration so this just
      // grabs the "next" argument for use as a URL parameter.
      var arg = args[0];

      var param = urlParams[i];

      var isOptional = OPTIONAL_REGEX.test(param);
      param = param.replace(OPTIONAL_REGEX, '');

      if (!arg) {
        if (isOptional) {
          urlData[param] = '';
          continue;
        }
        throw new Error('Stripe: I require argument "' + urlParams[i] + '", but I got: ' + arg);
      }

      urlData[param] = args.shift();
    }

    var data = utils.getDataFromArgs(args);
    var opts = utils.getOptionsFromArgs(args);

    if (args.length) {
      throw new Error(
        'Stripe: Unknown arguments (' + args + '). Did you mean to pass an options object? ' +
        'See https://github.com/stripe/stripe-node/wiki/Passing-Options.'
      );
    }

    var requestPath = this.createFullPath(commandPath, urlData);
    function requestCallback(err, response) {
      if (err) {
        deferred.reject(err);
      } else {
        deferred.resolve(
          spec.transformResponseData ?
            spec.transformResponseData(response) :
            response
        );
      }
    };

    var options = {headers: _.extend(opts.headers, spec.headers)};
    self._request(requestMethod, requestPath, data, opts.auth, options, requestCallback);

    return deferred.promise;
  };
};

module.exports = stripeMethod;
<|endoftext|>


<commit_before>"use strict";

var debug = require("debug")("swagger:check");

var match = require("./matchers.js");

/**
 * An error thrown when validating parameters
 * @param message {string} What failed ?
 * @param status {number} HTTP status override
 * @constructor
 */
function ValidationError(message, status) {
  this.name = "ValidationError";
  this.message = message || "";
  this.status = status || 400;
}
ValidationError.prototype = Error.prototype;
exports.ValidationError = ValidationError;

/**
 * Defines if the spec version is supported by the middleware
 * @param def {Swagger} The swagger complete definition
 * @throws {ValidationError} If the version is not supported
 */
exports.swaggerVersion = function(def) {
  if(def.swagger !== "2.0") {
    throw new ValidationError("Swagger " + def.swagger +
      "is not supported by this middleware.");
  }
};

/**
 * Check if the context carries the parameter correctly
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param def {Parameter} The parameter's definition
 * @param context {Context} A koa context
 * @return {*} The cleaned value
 * @throws {ValidationError} A possible validation error
 */
function checkParameter(validator, def, context) {
  var value = match.fromContext(def.name, def.in, context);

  // Check requirement
  if(def.required && !value) {
    throw new ValidationError(def.name + " is required");
  } else if(!value) {
    return def.default;
  }

  // Select the right schema according to the spec
  var schema;
  if(def.in === "body") {
    schema = def.schema;
    // TODO: clean and sanitize recursively
  } else {
    schema = def;
  }

  var err = validator(value, schema);
  if(err.length > 0) {
    throw new ValidationError(def.name + " has an invalid format: " +
      JSON.stringify(err));
  }

  return value;
}
exports.parameter = checkParameter;

/**
 * Check if the context carries the parameters correctly
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param defs {[Parameter]} The list of parameters definitions
 * @param context {Context} A koa context
 * @return {object} The checked parameters in a dict
 * @throws {ValidationError}
 */
exports.parameters = function(validator, defs, context) {
  var errorMessages = [];
  var parameterDict = {};
  defs.forEach(function eachDef(def) {
    try {
      parameterDict[def.name] = checkParameter(validator, def, context);
    } catch(e) {
      if(e.name !== "ValidationError") {
        throw e;
      }
      errorMessages.push(e.message);
    }
  });
  if(errorMessages.length > 0) {
    throw new ValidationError(errorMessages.join(", "));
  }
  return parameterDict;
};

/**
 * Checks the response's body and logs the exact violation in swagger:check
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param schema {Schema} The JSON-schema to test the body against
 * @param body {*} The body to send back
 * @throws {ValidationError} When the body does not respect the schema
 */
exports.body = function(validator, schema, body) {
  var err = validator(body, schema);
  if(err.length > 0) {
    debug("Implementation Spec Violation: Unmatching response format");
    debug(err);
    throw new ValidationError("Unmatching response format", 500);
  }
};

/**
 * Checks a sent header and logs the exact violation in swagger:check
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param def {Header} The header definition
 * @param name {string} The header's name
 * @param value {*} The header value
 * @throws {ValidationError} When the header does not respect the schema
 */
function checkSentHeader(validator, def, name, value) {
  if(!value && def.default) {
    return def.def.default;
  }
  var err = validator(value, def.schema);
  if(err) {
    debug("Implementation Spec Violation: Unmatching sent header format: " +
      name);
    debug(err);
    throw new ValidationError("Unmatching response format", 500);
  }
}
exports.sentHeader = checkSentHeader;

/**
 *
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param defs {Headers} The header's definitions
 * @param sentHeaders {object} The sent headers
 * @throws {ValidationError} When the headers does not respect the schema
 */
exports.sentHeaders = function(validator, defs, sentHeaders) {
  var errored = false;
  Object.keys(defs).forEach(function eachSentHeader(name) {
    try {
      checkSentHeader(validator, defs[name], name, sentHeaders[name]);
    } catch(e) {
      errored = true;
    }
  });
  if(errored) {
    throw new ValidationError("Unmatching response format", 500);
  }
};
<commit_msg>Add todos<commit_after>"use strict";

var debug = require("debug")("swagger:check");

var match = require("./matchers.js");

/**
 * An error thrown when validating parameters
 * @param message {string} What failed ?
 * @param status {number} HTTP status override
 * @constructor
 */
function ValidationError(message, status) {
  this.name = "ValidationError";
  this.message = message || "";
  this.status = status || 400;
}
ValidationError.prototype = Error.prototype;
exports.ValidationError = ValidationError;

/**
 * Defines if the spec version is supported by the middleware
 * @param def {Swagger} The swagger complete definition
 * @throws {ValidationError} If the version is not supported
 */
exports.swaggerVersion = function(def) {
  if(def.swagger !== "2.0") {
    throw new ValidationError("Swagger " + def.swagger +
      "is not supported by this middleware.");
  }
};

/**
 * Check if the context carries the parameter correctly
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param def {Parameter} The parameter's definition
 * @param context {Context} A koa context
 * @return {*} The cleaned value
 * @throws {ValidationError} A possible validation error
 */
function checkParameter(validator, def, context) {
  var value = match.fromContext(def.name, def.in, context);

  // Check requirement
  if(def.required && !value) {
    throw new ValidationError(def.name + " is required");
  } else if(!value) {
    return def.default;
  }

  // Select the right schema according to the spec
  var schema;
  if(def.in === "body") {
    schema = def.schema;
    // TODO: clean and sanitize recursively
  } else {
    // TODO: coerce type
    schema = def;
  }

  var err = validator(value, schema);
  if(err.length > 0) {
    throw new ValidationError(def.name + " has an invalid format: " +
      JSON.stringify(err));
  }

  return value;
}
exports.parameter = checkParameter;

/**
 * Check if the context carries the parameters correctly
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param defs {[Parameter]} The list of parameters definitions
 * @param context {Context} A koa context
 * @return {object} The checked parameters in a dict
 * @throws {ValidationError}
 */
exports.parameters = function(validator, defs, context) {
  var errorMessages = [];
  var parameterDict = {};
  defs.forEach(function eachDef(def) {
    try {
      parameterDict[def.name] = checkParameter(validator, def, context);
    } catch(e) {
      if(e.name !== "ValidationError") {
        throw e;
      }
      errorMessages.push(e.message);
    }
  });
  if(errorMessages.length > 0) {
    throw new ValidationError(errorMessages.join(", "));
  }
  return parameterDict;
};

/**
 * Checks the response's body and logs the exact violation in swagger:check
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param schema {Schema} The JSON-schema to test the body against
 * @param body {*} The body to send back
 * @throws {ValidationError} When the body does not respect the schema
 */
exports.body = function(validator, schema, body) {
  // TODO: clean and sanitize recursively
  var err = validator(body, schema);
  if(err.length > 0) {
    debug("Implementation Spec Violation: Unmatching response format");
    debug(err);
    throw new ValidationError("Unmatching response format", 500);
  }
};

/**
 * Checks a sent header and logs the exact violation in swagger:check
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param def {Header} The header definition
 * @param name {string} The header's name
 * @param value {*} The header value
 * @throws {ValidationError} When the header does not respect the schema
 */
function checkSentHeader(validator, def, name, value) {
  if(!value && def.default) {
    return def.def.default;
  }
  var err = validator(value, def.schema);
  if(err) {
    debug("Implementation Spec Violation: Unmatching sent header format: " +
      name);
    debug(err);
    throw new ValidationError("Unmatching response format", 500);
  }
}
exports.sentHeader = checkSentHeader;

/**
 *
 * @param validator {function(object, Schema)} JSON-Schema validator function
 * @param defs {Headers} The header's definitions
 * @param sentHeaders {object} The sent headers
 * @throws {ValidationError} When the headers does not respect the schema
 */
exports.sentHeaders = function(validator, defs, sentHeaders) {
  var errored = false;
  Object.keys(defs).forEach(function eachSentHeader(name) {
    try {
      checkSentHeader(validator, defs[name], name, sentHeaders[name]);
    } catch(e) {
      errored = true;
    }
  });
  if(errored) {
    throw new ValidationError("Unmatching response format", 500);
  }
};
<|endoftext|>


<commit_before>var _ = require('underscore'),
    Backbone = require('backbone-dirty'),
    settings = require('settings'),
    fs = require('fs'),
    Step = require('step'),
    path = require('path'),
    models = require('models');

// Implement custom sync method for Project model.
// Writes projects to individual directories and splits out Stylesheets from
// the main project MML JSON file.
models.ProjectList.prototype.sync =
models.Project.prototype.sync = function(method, model, success, error) {
    switch (method) {
    case 'read':
        if (model.id) {
            loadProject(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        } else {
            loadProjectAll(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        }
        break;
    case 'create':
    case 'update':
        saveProject(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    case 'delete':
        destroyProject(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    }
};

// Implement custom sync method for Export model.
// Removes any files associated with the export model.
models.Export.prototype.sync = function(method, model, success, error) {
    switch (method) {
    case 'delete':
        var filepath;
        Step(
            function() {
                Backbone.sync('read', model, this, this);
            },
            function(data) {
                if (data && data.filename) {
                    filepath = path.join(settings.export_dir, data.filename);
                    path.exists(filepath, this);
                } else {
                    this(false);
                }
            },
            function(remove) {
                if (remove) {
                    fs.unlink(filepath, this);
                } else {
                    this();
                }
            },
            function() {
                Backbone.sync(method, model, success, error);
            }
        );
        break;
    default:
        Backbone.sync(method, model, success, error);
        break;
    }
};

/**
 * Load a single model. Requires that model.id be populated.
 */
function loadProject(model, callback) {
    var modelPath = path.join(settings.files, model.type, model.id);
    fs.readFile(path.join(modelPath, model.id) + '.mml', 'utf-8',
    function(err, data) {
        if (err || !data) {
            return callback('Error reading model file.');
        }
        var object = JSON.parse(data);
        // Set the object ID explicitly for multiple-load scenarios where
        // model parse()/set() is bypassed.
        object.id = model.id;
        if (object.Stylesheet && object.Stylesheet.length > 0) {
            Step(
                function() {
                    var group = this.group();
                    _.each(object.Stylesheet, function(filename, index) {
                        fs.readFile(
                            path.join(modelPath, filename),
                            'utf-8',
                            group()
                        );
                    });
                },
                function(err, files) {
                    object.Stylesheet = _.reduce(
                        object.Stylesheet,
                        function(memo, filename, index) {
                            if (typeof files[index] !== 'undefined') {
                                memo.push({
                                    id: filename,
                                    data: files[index]
                                });
                            }
                            return memo;
                        },
                        []
                    );
                    return callback(null, object);
                }
            );
        } else {
            return callback(null, object);
        }
    });
};

/**
 * Load an array of all models.
 */
function loadProjectAll(model, callback) {
    var basepath = path.join(settings.files, model.type);
    Step(
        function() {
            path.exists(basepath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basepath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(basepath, this);
        },
        function(err, files) {
            if (err) {
                return this('Error reading model directory.');
            }
            else if (files.length === 0) {
                return this();
            }
            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                var id = files[i];
                loadProject({ id: id, type: model.type }, group());
            }
        },
        function(err, models) {
            // Ignore errors from loading individual models (e.g.
            // don't let one bad apple spoil the collection).
            models = _.select(models, function(model) {
                return (typeof model === 'object');
            });
            return callback(null, models);
        }
    );
};

/**
 * Destroy a project.
 * rm rf the project directory.
 */
function destroyProject(model, callback) {
    var rm = function(basePath, callback) {
        var killswitch = false;
        Step(
            function() {
                fs.stat(basePath, this);
            },
            function(err, stat) {
                if (stat.isDirectory()) {
                    this();
                } else if (stat.isFile()) {
                    killswitch = true;
                    fs.unlink(basePath, this);
                } else {
                    killswitch = true;
                    this();
                }
            },
            // The next steps apply only when basePath refers to a directory.
            function(err) {
                if (killswitch) return this();
                fs.readdir(basePath, this);
            },
            function(err, files) {
                if (killswitch) return this();
                if (files.length === 0) {
                    this();
                } else {
                    var group = this.group();
                    for (var i = 0; i < files.length; i++) {
                        rm(path.join(basePath, files[i]), group());
                    }
                }
            },
            function(err) {
                if (killswitch) return callback();
                fs.rmdir(basePath, callback);
            }
        );
    };
    var modelPath = path.join(settings.files, model.type, model.id);
    rm(modelPath, callback);
}

/**
 * Save a Project. Called by create/update.
 *
 * Special case for projects creates a subdirectory per project and does
 * additional splitting out of subproperties (stylesheets) into separate files.
 */
function saveProject(model, callback) {
    var basePath = path.join(settings.files, model.type);
    var modelPath = path.join(settings.files, model.type, model.id);
    Step(
        function() {
            path.exists(basePath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basePath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            path.exists(modelPath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(modelPath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(modelPath, this);
        },
        function(err, files) {
            // Remove any stale files in the project directory.
            var group = this.group();
            var stylesheets = model.get('Stylesheet') || [];
            var stale = _.select(files, function(filename) {
                if (filename === (model.id + '.mml')) {
                    return false;
                } else if (_.pluck(stylesheets, 'id').indexOf(filename) !== -1) {
                    return false;
                }
                return true;
            });
            if (stale.length) {
                for (var i = 0; i < stale.length; i++) {
                    fs.unlink(path.join(modelPath, stale[i]), group());
                }
            }
            else {
                group()();
            }
        },
        function() {
            // Hard clone the model JSON before doing adjustments to the data
            // based on writing separate stylesheets.
            var data = JSON.parse(JSON.stringify(model.toJSON()));
            var files = [];
            if (data.id) {
                delete data.id;
            }
            if (data.Stylesheet) {
                _.each(data.Stylesheet, function(stylesheet, key) {
                    if (stylesheet.id) {
                        files.push({
                            filename: stylesheet.id,
                            data: stylesheet.data
                        });
                        data.Stylesheet[key] = stylesheet.id;
                    }
                });
            }
            files.push({
                filename: model.id + '.mml',
                data: JSON.stringify(data)
            });

            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                fs.writeFile(
                    path.join(modelPath, files[i].filename),
                    files[i].data,
                    group()
                );
            }
        },
        function() {
            callback(null, model);
        }
    );
}

// A model instance cache.
var Cache = function() {
    this.cache = {};
};
Cache.prototype.get = function(type, id) {
    if (this.cache[type] && this.cache[type][id]) {
        return this.cache[type][id];
    }
    this.cache[type] = this.cache[type] || {}
    this.set(type, id, new models[type]({id: id}));
    return this.cache[type][id];
};
Cache.prototype.set = function(type, id, model) {
    this.cache[type] = this.cache[type] || {}
    this.cache[type][id] = model;
    return this.cache[type][id];
};
Cache.prototype.del = function(type, id) {
    if (this.cache[type][id]) {
        delete this.cache[type][id];
    }
};

module.exports = _.extend({ cache: new Cache() }, models);

<commit_msg>Hardcode project path back in for now.<commit_after>var _ = require('underscore'),
    Backbone = require('backbone-dirty'),
    settings = require('settings'),
    fs = require('fs'),
    Step = require('step'),
    path = require('path'),
    models = require('models');

// Implement custom sync method for Project model.
// Writes projects to individual directories and splits out Stylesheets from
// the main project MML JSON file.
models.ProjectList.prototype.sync =
models.Project.prototype.sync = function(method, model, success, error) {
    switch (method) {
    case 'read':
        if (model.id) {
            loadProject(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        } else {
            loadProjectAll(model, function(err, model) {
                return err ? error(err) : success(model);
            });
        }
        break;
    case 'create':
    case 'update':
        saveProject(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    case 'delete':
        destroyProject(model, function(err, model) {
            return err ? error(err) : success(model);
        });
        break;
    }
};

// Implement custom sync method for Export model.
// Removes any files associated with the export model.
models.Export.prototype.sync = function(method, model, success, error) {
    switch (method) {
    case 'delete':
        var filepath;
        Step(
            function() {
                Backbone.sync('read', model, this, this);
            },
            function(data) {
                if (data && data.filename) {
                    filepath = path.join(settings.export_dir, data.filename);
                    path.exists(filepath, this);
                } else {
                    this(false);
                }
            },
            function(remove) {
                if (remove) {
                    fs.unlink(filepath, this);
                } else {
                    this();
                }
            },
            function() {
                Backbone.sync(method, model, success, error);
            }
        );
        break;
    default:
        Backbone.sync(method, model, success, error);
        break;
    }
};

/**
 * Load a single model. Requires that model.id be populated.
 */
function loadProject(model, callback) {
    var modelPath = path.join(settings.files, 'project', model.id);
    fs.readFile(path.join(modelPath, model.id) + '.mml', 'utf-8',
    function(err, data) {
        if (err || !data) {
            return callback('Error reading model file.');
        }
        var object = JSON.parse(data);
        // Set the object ID explicitly for multiple-load scenarios where
        // model parse()/set() is bypassed.
        object.id = model.id;
        if (object.Stylesheet && object.Stylesheet.length > 0) {
            Step(
                function() {
                    var group = this.group();
                    _.each(object.Stylesheet, function(filename, index) {
                        fs.readFile(
                            path.join(modelPath, filename),
                            'utf-8',
                            group()
                        );
                    });
                },
                function(err, files) {
                    object.Stylesheet = _.reduce(
                        object.Stylesheet,
                        function(memo, filename, index) {
                            if (typeof files[index] !== 'undefined') {
                                memo.push({
                                    id: filename,
                                    data: files[index]
                                });
                            }
                            return memo;
                        },
                        []
                    );
                    return callback(null, object);
                }
            );
        } else {
            return callback(null, object);
        }
    });
};

/**
 * Load an array of all models.
 */
function loadProjectAll(model, callback) {
    var basepath = path.join(settings.files, 'project');
    Step(
        function() {
            path.exists(basepath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basepath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(basepath, this);
        },
        function(err, files) {
            if (err) {
                return this('Error reading model directory.');
            }
            else if (files.length === 0) {
                return this();
            }
            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                var id = files[i];
                loadProject({ id: id }, group());
            }
        },
        function(err, models) {
            // Ignore errors from loading individual models (e.g.
            // don't let one bad apple spoil the collection).
            models = _.select(models, function(model) {
                return (typeof model === 'object');
            });
            return callback(null, models);
        }
    );
};

/**
 * Destroy a project.
 * rm rf the project directory.
 */
function destroyProject(model, callback) {
    var rm = function(basePath, callback) {
        var killswitch = false;
        Step(
            function() {
                fs.stat(basePath, this);
            },
            function(err, stat) {
                if (stat.isDirectory()) {
                    this();
                } else if (stat.isFile()) {
                    killswitch = true;
                    fs.unlink(basePath, this);
                } else {
                    killswitch = true;
                    this();
                }
            },
            // The next steps apply only when basePath refers to a directory.
            function(err) {
                if (killswitch) return this();
                fs.readdir(basePath, this);
            },
            function(err, files) {
                if (killswitch) return this();
                if (files.length === 0) {
                    this();
                } else {
                    var group = this.group();
                    for (var i = 0; i < files.length; i++) {
                        rm(path.join(basePath, files[i]), group());
                    }
                }
            },
            function(err) {
                if (killswitch) return callback();
                fs.rmdir(basePath, callback);
            }
        );
    };
    var modelPath = path.join(settings.files, 'project', model.id);
    rm(modelPath, callback);
}

/**
 * Save a Project. Called by create/update.
 *
 * Special case for projects creates a subdirectory per project and does
 * additional splitting out of subproperties (stylesheets) into separate files.
 */
function saveProject(model, callback) {
    var basePath = path.join(settings.files, 'project');
    var modelPath = path.join(settings.files, 'project', model.id);
    Step(
        function() {
            path.exists(basePath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(basePath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            path.exists(modelPath, this);
        },
        function(exists) {
            if (!exists) {
                fs.mkdir(modelPath, 0777, this);
            } else {
                this();
            }
        },
        function() {
            fs.readdir(modelPath, this);
        },
        function(err, files) {
            // Remove any stale files in the project directory.
            var group = this.group();
            var stylesheets = model.get('Stylesheet') || [];
            var stale = _.select(files, function(filename) {
                if (filename === (model.id + '.mml')) {
                    return false;
                } else if (_.pluck(stylesheets, 'id').indexOf(filename) !== -1) {
                    return false;
                }
                return true;
            });
            if (stale.length) {
                for (var i = 0; i < stale.length; i++) {
                    fs.unlink(path.join(modelPath, stale[i]), group());
                }
            }
            else {
                group()();
            }
        },
        function() {
            // Hard clone the model JSON before doing adjustments to the data
            // based on writing separate stylesheets.
            var data = JSON.parse(JSON.stringify(model.toJSON()));
            var files = [];
            if (data.id) {
                delete data.id;
            }
            if (data.Stylesheet) {
                _.each(data.Stylesheet, function(stylesheet, key) {
                    if (stylesheet.id) {
                        files.push({
                            filename: stylesheet.id,
                            data: stylesheet.data
                        });
                        data.Stylesheet[key] = stylesheet.id;
                    }
                });
            }
            files.push({
                filename: model.id + '.mml',
                data: JSON.stringify(data)
            });

            var group = this.group();
            for (var i = 0; i < files.length; i++) {
                fs.writeFile(
                    path.join(modelPath, files[i].filename),
                    files[i].data,
                    group()
                );
            }
        },
        function() {
            callback(null, model);
        }
    );
}

// A model instance cache.
var Cache = function() {
    this.cache = {};
};
Cache.prototype.get = function(type, id) {
    if (this.cache[type] && this.cache[type][id]) {
        return this.cache[type][id];
    }
    this.cache[type] = this.cache[type] || {}
    this.set(type, id, new models[type]({id: id}));
    return this.cache[type][id];
};
Cache.prototype.set = function(type, id, model) {
    this.cache[type] = this.cache[type] || {}
    this.cache[type][id] = model;
    return this.cache[type][id];
};
Cache.prototype.del = function(type, id) {
    if (this.cache[type][id]) {
        delete this.cache[type][id];
    }
};

module.exports = _.extend({ cache: new Cache() }, models);

<|endoftext|>


<commit_before>// ------------------------
//  Launch a backbone powered entry box when someone clicks the new-person button
// ------------------------

require(   [ 'jquery', 'backbone-forms', 'Backbone', 'underscore', 'utils/slugify', 'templates/person/new', 'templates/person/compact_list' ],
  function (  $,        BackboneForms,    Backbone,   _          ,  slugify,         personTemplate,         compactListTemplate            ) {

    // handle the API wrapping the responses in result(s): {...}
    $.ajaxSetup({
      converters: {
        "text json": function (json) {
          var data = $.parseJSON(json);
          return data.results || data.result || data;
        }
      }
    });

    var PersonModel = Backbone.Model.extend({
      urlRoot: '/api/v1/person',
      schema: {
        name: { dataType: 'Text', validators: ['required'] },
        slug: { dataType: 'Text', validators: ['required'] },
      }
    });

    var PossibleMatchesCollection = Backbone.Collection.extend({
      url: '/api/v1/person'
    });

    var PossibleMatchesView = Backbone.View.extend({
      tagName:   'ul',
      className: 'potential-matches',

      initialize: function() {
        var self = this;
        this.collection.bind("reset", function() { self.render() } );
      },

      render: function () {
        var content = compactListTemplate({ persons: this.collection.toJSON() });
        this.$el.html( content );
        return this;
      }
    });

    var NewPersonView = Backbone.View.extend({

      initialize: function () {
        this.form = new BackboneForms({ model: this.model });
        this.possibleMatches     = new PossibleMatchesCollection();
        this.possibleMatchesView = new PossibleMatchesView({collection: this.possibleMatches});
      },

      render: function () {

        // render the template and form
        var $content = $( personTemplate({}) );
        var $form    = $( this.form.render().el );

        // add the contents of the form to the template content
        $content.find('form').prepend( $form.children() );

        // update our element
        this.$el.html( $content );

        // give the list to the PossibleMatches view for rendering
        this.$('ul.potential-matches').html( this.possibleMatchesView.render().el );

        return this;
      },

      events: {
        'submit form ':             'submitForm',
        'keyup input[name=name]':   'nameEdit',
      },

      submitForm: function (e) {
        e.preventDefault();

        var self   = this;
        var form   = self.form;
        var errors = form.commit();

        if (_.isEmpty(errors)) {
          this.model.save(
            {},
            {
              success: function (model, response) {
                document.location = response.meta.edit_url;
              },
              error: function (model, response) {
                var errors = $.parseJSON( response.responseText ).errors || {};
                _.each(errors, function(val, key) {
                  form.fields[key] && form.fields[key].setError(val);
                });
              }
            }
          );
        }

      },

      nameEdit: function (e) {
        // When the name is being entered we should fill in the slug. This will
        // let the user edit the slug, or see that it can't be generated from the
        // name. Also means that we don't need to explain why that field is there.
        var $name = this.$(':input[name=name]');
        var $slug = this.$(':input[name=slug]');
        $slug.val( slugify( $name.val() ) );

        // Try to load matching people from the server and display them in the
        // 'possible matches' list.
        var self = this;

        if ($name.val()) {
          self.possibleMatches.fetch({
            data: { name: $name.val() },
            success: function () {
              self.possibleMatchesView.render();
            }
          });
        } else {
          self.possibleMatches.reset();
        }

        return true;
      },

    });

    $(function() {

    	$('#new-person').click(function(event) {

        event.preventDefault();

        var person = new PersonModel({});
        var view   = new NewPersonView({model: person});

        view.render();

        var $replacement = view.$el;
        $replacement.hide();
        $(this).hide().replaceWith( $replacement );
        $replacement.slideDown();

        view.$(':input:first').focus();
    	});

    });
  }
);
<commit_msg>Change code layout so that the refactoring is easier to see<commit_after>// ------------------------
//  Launch a backbone powered entry box when someone clicks the new-person button
// ------------------------

require(
  [
    'jquery',
    'backbone-forms',
    'Backbone',
    'underscore',
    'utils/slugify',
    'templates/person/new',
    'templates/person/compact_list',
    'popit/models/person'
  ],
  function (
    $,
    BackboneForms,
    Backbone,
    _,
    slugify,
    personTemplate,
    compactListTemplate,
    PersonModel
  ) {

    // handle the API wrapping the responses in result(s): {...}
    $.ajaxSetup({
      converters: {
        "text json": function (json) {
          var data = $.parseJSON(json);
          return data.results || data.result || data;
        }
      }
    });

    var PersonModel = Backbone.Model.extend({
      urlRoot: '/api/v1/person',
      schema: {
        name: { dataType: 'Text', validators: ['required'] },
        slug: { dataType: 'Text', validators: ['required'] },
      }
    });

    var PossibleMatchesCollection = Backbone.Collection.extend({
      url: '/api/v1/person'
    });

    var PossibleMatchesView = Backbone.View.extend({
      tagName:   'ul',
      className: 'potential-matches',

      initialize: function() {
        var self = this;
        this.collection.bind("reset", function() { self.render() } );
      },

      render: function () {
        var content = compactListTemplate({ persons: this.collection.toJSON() });
        this.$el.html( content );
        return this;
      }
    });

    var NewPersonView = Backbone.View.extend({

      initialize: function () {
        this.form = new BackboneForms({ model: this.model });
        this.possibleMatches     = new PossibleMatchesCollection();
        this.possibleMatchesView = new PossibleMatchesView({collection: this.possibleMatches});
      },

      render: function () {

        // render the template and form
        var $content = $( personTemplate({}) );
        var $form    = $( this.form.render().el );

        // add the contents of the form to the template content
        $content.find('form').prepend( $form.children() );

        // update our element
        this.$el.html( $content );

        // give the list to the PossibleMatches view for rendering
        this.$('ul.potential-matches').html( this.possibleMatchesView.render().el );

        return this;
      },

      events: {
        'submit form ':             'submitForm',
        'keyup input[name=name]':   'nameEdit',
      },

      submitForm: function (e) {
        e.preventDefault();

        var self   = this;
        var form   = self.form;
        var errors = form.commit();

        if (_.isEmpty(errors)) {
          this.model.save(
            {},
            {
              success: function (model, response) {
                document.location = response.meta.edit_url;
              },
              error: function (model, response) {
                var errors = $.parseJSON( response.responseText ).errors || {};
                _.each(errors, function(val, key) {
                  form.fields[key] && form.fields[key].setError(val);
                });
              }
            }
          );
        }

      },

      nameEdit: function (e) {
        // When the name is being entered we should fill in the slug. This will
        // let the user edit the slug, or see that it can't be generated from the
        // name. Also means that we don't need to explain why that field is there.
        var $name = this.$(':input[name=name]');
        var $slug = this.$(':input[name=slug]');
        $slug.val( slugify( $name.val() ) );

        // Try to load matching people from the server and display them in the
        // 'possible matches' list.
        var self = this;

        if ($name.val()) {
          self.possibleMatches.fetch({
            data: { name: $name.val() },
            success: function () {
              self.possibleMatchesView.render();
            }
          });
        } else {
          self.possibleMatches.reset();
        }

        return true;
      },

    });

    $(function() {

    	$('#new-person').click(function(event) {

        event.preventDefault();

        var person = new PersonModel({});
        var view   = new NewPersonView({model: person});

        view.render();

        var $replacement = view.$el;
        $replacement.hide();
        $(this).hide().replaceWith( $replacement );
        $replacement.slideDown();

        view.$(':input:first').focus();
    	});

    });
  }
);
<|endoftext|>


<commit_before>var Promise = require('bluebird'),
    _ = require('lodash'),
    moment =require('moment-range'),
    config = require('./config'),
    Datastore = require('nedb'),
    DateUtil = require('./dateUtil').DateUtil;

Promise.longStackTraces();
Promise.onPossiblyUnhandledRejection(function(e/*, promise*/){ throw e; });

function constructDateCriteria(dateFieldName, value, operator) {
  var dateCriteria = {};
  dateCriteria[dateFieldName] = {};
  dateCriteria[dateFieldName][operator] = DateUtil.getDate(value).toDate();
  return dateCriteria;
}

function constructCriteria(query, dateFieldName) {

  var criteria = [];
  if (!query) {
    return criteria;
  }

  if (query.objectID) {
    criteria.push({objectID: {$in: [].concat(query.objectID)}});
  }
  if (query.owner) {
    criteria.push({owner: query.owner});
  }
  if (query.startDate) {
    criteria.push(constructDateCriteria(dateFieldName, query.startDate, '$gte'));
  }
  if (query.endDate) {
    criteria.push(constructDateCriteria(
      dateFieldName, DateUtil.getDate(query.endDate).add(1, 'days'), '$lt'));
  }

  return criteria;
}

function getData(query, dateFieldName, dbMethod) {
  var criteria = constructCriteria(query, dateFieldName);

  if (criteria.length === 0) {
    return dbMethod({});
  }

  return dbMethod({$and: criteria});
}

function KanbanStorage(dataFileLocation) {
  var dataFileLoc = dataFileLocation || config.dataFileLocation || './data/',
      itemStore = new Datastore({ filename: dataFileLoc + 'kanbanItems.json', autoload: true }),
      snapshotStore = new Datastore({ filename: dataFileLoc + 'snapshot.json', autoload: true });

  var snapshotInsert = Promise.promisify(snapshotStore.insert, snapshotStore),
      snapshotFind = Promise.promisify(snapshotStore.find, snapshotStore),
      snapshotRemove = Promise.promisify(snapshotStore.remove, snapshotStore),
      itemRemove = Promise.promisify(itemStore.remove, itemStore),
      itemInsert = Promise.promisify(itemStore.insert, itemStore),
      itemFind = Promise.promisify(itemStore.find, itemStore),
      itemUpdate = Promise.promisify(itemStore.update, itemStore);

  this.sortItemStatusChangeLog = function(item) {
    function comparator(a, b) {
      if (a.from < b.from) {
        return -1;
      } else if (a.from > b.from) {
        return 1;
      } else {
        return 0;
      }
    }

    return (!item || !item.statusChangeLog) ?
      [] : item.statusChangeLog.sort(comparator);
  };

  this.saveItems = itemInsert;

  this.getItems = function(query) {
    return getData(query, 'kanbanizedOn', itemFind);
  };

  /*
  The expected input is an array containing object with below structure
  {
    objectID:
    name: ,
    type: ,
    owner: ,
    statusChangeLog: [{
      from: ,
      to: ,
      status:
    }],
    blockLog: [{
      blocked: ,
      stage: ,
      time: ,
      reason:
    }],
    kanbanizedOn:
  }
  */
  this.updateItems = function(kanbanItems) {
    var allUpdated = kanbanItems.map(
      function(item) {
        var setUpdate = {};
        if (!_.isEmpty(item.statusChangeLog)) {
          setUpdate['statusChangeLog'] = {$each: item.statusChangeLog};
        }
        if (!_.isEmpty(item.blockLog)) {
          setUpdate['blockLog'] = {$each: item.blockLog};
        }

        return itemUpdate({objectID: item.objectID},
            {$addToSet: setUpdate},
            {$set: {
                owner: item.owner,
                name: item.name
              }
            }
          );
      });

    return Promise.all(allUpdated)
      .then(function(updatedItemCnt) {
        return updatedItemCnt.reduce(function(all, cnt) {
            return all + cnt;
          }, 0);
      });
  };

  this.removeItems = itemRemove;

  this.getSnapshot = function(query) {
    return getData(query, 'date', snapshotFind);
  };

  /**
  The expected input is an array containing object with below structure.
  This data is purely for snapshot construction and so no item specific data like name.
  It should be called daily to capture kanban item status for faster snapshot rendering.

  {
    objectID: ,
    type: '',
    owner: '',
    status: '',
    date:
  }
  */
  this.saveSnapshot = function(snapshotItems) {
    return snapshotInsert(snapshotItems);
  };

  this.removeSnapshot = snapshotRemove;

  this.convertItemDetailToSnapshot = function(kanbanItems, startDate) {
    var start = DateUtil.getDate(startDate),
        end = DateUtil.getDate(),
        nextDay = start.clone().add('days', 1),
        oneDay = moment().range(start, nextDay),
        range = moment().range(start, end),
        snapshotItems = [],
        that = this;

    range.by(oneDay, function(day) {
      var eachDay = day.utc();

      if (config.ignoreWeekend) {
        var weekDay = eachDay.day();

        if (weekDay === 0 || weekDay === 6) {
          return; // Not storing for weekend
        }
      }

      kanbanItems.forEach(function(item) {
        var statusLog = that.sortItemStatusChangeLog(item);
        var status = '';

        for (var i = 0; i < statusLog.length; i++) {
          var log = statusLog[i];
          var from = DateUtil.getDate(log.from);

          if (eachDay.isBefore(from, 'day')) {
            break;
          }

          status = log.status; // Take latest log's status
        }

        if (status) {
          snapshotItems.push({
            objectID: item.objectID,
            type: item.type,
            owner: item.owner,
            status: status,
            date: eachDay.toDate()
          });
        }
      });
    });

    return snapshotItems;
  };

  /**
  The expected input is an array containing object with below structure
  {
    objectID: ,
    type: ,
    name: ,
    owner: ,
    statusChangeLog: [{
      from: , // expected format is '2014-01-27T02:17:13.527Z'
      to: ,
      status:
    }],
    blockLog: [{
      blocked: ,
      stage: ,
      time: ,
      reason:
    }],
    kanbanizedOn:
  }
  */
  this.initHistoricalData = function(kanbanItems, startDate) {

    function extractItemID(item) {
      return item.objectID;
    }

    function getItemIds(items) {
      var ids = [];
      items.forEach(function(item) {
        var id = extractItemID(item);
        if (ids.indexOf(id) === -1) {
          ids.push(id);
        }
      });

      return ids;
    }

    function filterItemsByID(itemIDs, item) {
      return itemIDs.indexOf(extractItemID(item)) > -1;
    }

    var snapshots = this.convertItemDetailToSnapshot(kanbanItems, startDate),
        allItemIDs = getItemIds(kanbanItems),
        that = this,
        criteria = constructCriteria({startDate: startDate}, 'date');

    var snapshotPromise = this.removeSnapshot({$and: criteria}, {multi: true})
      .then(function() {
        var resolver = Promise.defer();

        snapshotStore.findOne({}).sort({date: -1}).exec(
          function(err, latestSnapshot) {
            if (err) {
              resolver.reject(err);
            } else {
              resolver.resolve(latestSnapshot || null);
            }
          });

        return resolver.promise;
      })
      .then(function(latestSnapshot) {
        if (latestSnapshot !== null) {
          return that.getSnapshot({startDate: latestSnapshot.date});
        } else {
          // No any data in DB
          return [];
        }
      })
      .then(function(snapshotToCumulate) {
        var snapshotByDate = snapshots.reduce(function(grouping, snapshot) {
          var date = moment(snapshot.date).format(config.dateFormat);

          if (!grouping[date]) {
            grouping[date] = [];
          }
          grouping[date].push(snapshot);

          return grouping;
        }, {});

        _.keys(snapshotByDate).sort().forEach(function(date) {
          var snapshotPerDay = snapshotByDate[date],
              remainedCumulate = [];

          function isSameObjectID(s1, s2) {
            return s1.objectID == s2.objectID;
          }

          for (var i = snapshotToCumulate.length - 1; i >= 0; i--) {
            var cumulate = snapshotToCumulate[i],
                existed = snapshotPerDay.some(
                  isSameObjectID.bind(this, cumulate));

            if (!existed) {
              cumulate = _.clone(cumulate);
              cumulate.date = DateUtil.getDate(date).toDate();
              delete cumulate._id;

              remainedCumulate.push(cumulate);
              snapshotPerDay.push(cumulate);
            }
          }

          snapshotToCumulate = remainedCumulate;
        });

        var allSnapshots = _.values(snapshotByDate).reduce(
          function(allSnapshots, snapshotPerDay) {
            return allSnapshots.concat(snapshotPerDay);
          }, []);

        return that.saveSnapshot(allSnapshots);
      });

    return itemFind({objectID: {$in: allItemIDs}})
      .then(function(items) {
        var existedItemIDs = getItemIds(items || []);
        var newItemIDs = _.difference(allItemIDs, existedItemIDs);

        var existedItems = _.filter(kanbanItems,
          filterItemsByID.bind(this, existedItemIDs));
        var newItems = _.filter(kanbanItems,
          filterItemsByID.bind(this, newItemIDs));

        return Promise
          .all([
            that.updateItems(existedItems),
            itemInsert(newItems),
            existedItems,
            snapshotPromise
          ])
          .spread(function(updatedItemCnt, newItems, existedItems, snapshots) {
            return {
              updatedItemCnt: updatedItemCnt,
              newItems: newItems,
              updatedItems: existedItems,
              snapshots: snapshots
            };
          });
      });
  };
}

module.exports = KanbanStorage;
<commit_msg>Fix issue that estimate is not updated when item update<commit_after>var Promise = require('bluebird'),
    _ = require('lodash'),
    moment =require('moment-range'),
    config = require('./config'),
    Datastore = require('nedb'),
    DateUtil = require('./dateUtil').DateUtil;

Promise.longStackTraces();
Promise.onPossiblyUnhandledRejection(function(e/*, promise*/){ throw e; });

function constructDateCriteria(dateFieldName, value, operator) {
  var dateCriteria = {};
  dateCriteria[dateFieldName] = {};
  dateCriteria[dateFieldName][operator] = DateUtil.getDate(value).toDate();
  return dateCriteria;
}

function constructCriteria(query, dateFieldName) {

  var criteria = [];
  if (!query) {
    return criteria;
  }

  if (query.objectID) {
    criteria.push({objectID: {$in: [].concat(query.objectID)}});
  }
  if (query.owner) {
    criteria.push({owner: query.owner});
  }
  if (query.startDate) {
    criteria.push(constructDateCriteria(dateFieldName, query.startDate, '$gte'));
  }
  if (query.endDate) {
    criteria.push(constructDateCriteria(
      dateFieldName, DateUtil.getDate(query.endDate).add(1, 'days'), '$lt'));
  }

  return criteria;
}

function getData(query, dateFieldName, dbMethod) {
  var criteria = constructCriteria(query, dateFieldName);

  if (criteria.length === 0) {
    return dbMethod({});
  }

  return dbMethod({$and: criteria});
}

function KanbanStorage(dataFileLocation) {
  var dataFileLoc = dataFileLocation || config.dataFileLocation || './data/',
      itemStore = new Datastore({ filename: dataFileLoc + 'kanbanItems.json', autoload: true }),
      snapshotStore = new Datastore({ filename: dataFileLoc + 'snapshot.json', autoload: true });

  var snapshotInsert = Promise.promisify(snapshotStore.insert, snapshotStore),
      snapshotFind = Promise.promisify(snapshotStore.find, snapshotStore),
      snapshotRemove = Promise.promisify(snapshotStore.remove, snapshotStore),
      itemRemove = Promise.promisify(itemStore.remove, itemStore),
      itemInsert = Promise.promisify(itemStore.insert, itemStore),
      itemFind = Promise.promisify(itemStore.find, itemStore),
      itemUpdate = Promise.promisify(itemStore.update, itemStore);

  this.sortItemStatusChangeLog = function(item) {
    function comparator(a, b) {
      if (a.from < b.from) {
        return -1;
      } else if (a.from > b.from) {
        return 1;
      } else {
        return 0;
      }
    }

    return (!item || !item.statusChangeLog) ?
      [] : item.statusChangeLog.sort(comparator);
  };

  this.saveItems = itemInsert;

  this.getItems = function(query) {
    return getData(query, 'kanbanizedOn', itemFind);
  };

  /*
  The expected input is an array containing object with below structure
  {
    objectID: ,
    name: ,
    estimate: ,
    type: ,
    owner: ,
    statusChangeLog: [{
      from: ,
      to: ,
      status:
    }],
    blockLog: [{
      blocked: ,
      stage: ,
      time: ,
      reason:
    }],
    kanbanizedOn:
  }
  */
  this.updateItems = function(kanbanItems) {
    var allUpdated = kanbanItems.map(
      function(item) {
        var setUpdate = {};
        if (!_.isEmpty(item.statusChangeLog)) {
          setUpdate['statusChangeLog'] = {$each: item.statusChangeLog};
        }
        if (!_.isEmpty(item.blockLog)) {
          setUpdate['blockLog'] = {$each: item.blockLog};
        }

        return itemUpdate({objectID: item.objectID},
            {$addToSet: setUpdate},
            {$set: {
                owner: item.owner,
                name: item.name,
                estimate: item.estimate
              }
            }
          );
      });

    return Promise.all(allUpdated)
      .then(function(updatedItemCnt) {
        return updatedItemCnt.reduce(function(all, cnt) {
            return all + cnt;
          }, 0);
      });
  };

  this.removeItems = itemRemove;

  this.getSnapshot = function(query) {
    return getData(query, 'date', snapshotFind);
  };

  /**
  The expected input is an array containing object with below structure.
  This data is purely for snapshot construction and so no item specific data like name.
  It should be called daily to capture kanban item status for faster snapshot rendering.

  {
    objectID: ,
    type: '',
    owner: '',
    status: '',
    date:
  }
  */
  this.saveSnapshot = function(snapshotItems) {
    return snapshotInsert(snapshotItems);
  };

  this.removeSnapshot = snapshotRemove;

  this.convertItemDetailToSnapshot = function(kanbanItems, startDate) {
    var start = DateUtil.getDate(startDate),
        end = DateUtil.getDate(),
        nextDay = start.clone().add('days', 1),
        oneDay = moment().range(start, nextDay),
        range = moment().range(start, end),
        snapshotItems = [],
        that = this;

    range.by(oneDay, function(day) {
      var eachDay = day.utc();

      if (config.ignoreWeekend) {
        var weekDay = eachDay.day();

        if (weekDay === 0 || weekDay === 6) {
          return; // Not storing for weekend
        }
      }

      kanbanItems.forEach(function(item) {
        var statusLog = that.sortItemStatusChangeLog(item);
        var status = '';

        for (var i = 0; i < statusLog.length; i++) {
          var log = statusLog[i];
          var from = DateUtil.getDate(log.from);

          if (eachDay.isBefore(from, 'day')) {
            break;
          }

          status = log.status; // Take latest log's status
        }

        if (status) {
          snapshotItems.push({
            objectID: item.objectID,
            type: item.type,
            owner: item.owner,
            status: status,
            date: eachDay.toDate()
          });
        }
      });
    });

    return snapshotItems;
  };

  /**
  The expected input is an array containing object with below structure
  {
    objectID: ,
    type: ,
    name: ,
    estiamte: ,
    owner: ,
    statusChangeLog: [{
      from: , // expected format is '2014-01-27T02:17:13.527Z'
      to: ,
      status:
    }],
    blockLog: [{
      blocked: ,
      stage: ,
      time: ,
      reason:
    }],
    kanbanizedOn:
  }
  */
  this.initHistoricalData = function(kanbanItems, startDate) {

    function extractItemID(item) {
      return item.objectID;
    }

    function getItemIds(items) {
      var ids = [];
      items.forEach(function(item) {
        var id = extractItemID(item);
        if (ids.indexOf(id) === -1) {
          ids.push(id);
        }
      });

      return ids;
    }

    function filterItemsByID(itemIDs, item) {
      return itemIDs.indexOf(extractItemID(item)) > -1;
    }

    var snapshots = this.convertItemDetailToSnapshot(kanbanItems, startDate),
        allItemIDs = getItemIds(kanbanItems),
        that = this,
        criteria = constructCriteria({startDate: startDate}, 'date');

    var snapshotPromise = this.removeSnapshot({$and: criteria}, {multi: true})
      .then(function() {
        var resolver = Promise.defer();

        snapshotStore.findOne({}).sort({date: -1}).exec(
          function(err, latestSnapshot) {
            if (err) {
              resolver.reject(err);
            } else {
              resolver.resolve(latestSnapshot || null);
            }
          });

        return resolver.promise;
      })
      .then(function(latestSnapshot) {
        if (latestSnapshot !== null) {
          return that.getSnapshot({startDate: latestSnapshot.date});
        } else {
          // No any data in DB
          return [];
        }
      })
      .then(function(snapshotToCumulate) {
        var snapshotByDate = snapshots.reduce(function(grouping, snapshot) {
          var date = moment(snapshot.date).format(config.dateFormat);

          if (!grouping[date]) {
            grouping[date] = [];
          }
          grouping[date].push(snapshot);

          return grouping;
        }, {});

        _.keys(snapshotByDate).sort().forEach(function(date) {
          var snapshotPerDay = snapshotByDate[date],
              remainedCumulate = [];

          function isSameObjectID(s1, s2) {
            return s1.objectID == s2.objectID;
          }

          for (var i = snapshotToCumulate.length - 1; i >= 0; i--) {
            var cumulate = snapshotToCumulate[i],
                existed = snapshotPerDay.some(
                  isSameObjectID.bind(this, cumulate));

            if (!existed) {
              cumulate = _.clone(cumulate);
              cumulate.date = DateUtil.getDate(date).toDate();
              delete cumulate._id;

              remainedCumulate.push(cumulate);
              snapshotPerDay.push(cumulate);
            }
          }

          snapshotToCumulate = remainedCumulate;
        });

        var allSnapshots = _.values(snapshotByDate).reduce(
          function(allSnapshots, snapshotPerDay) {
            return allSnapshots.concat(snapshotPerDay);
          }, []);

        return that.saveSnapshot(allSnapshots);
      });

    return itemFind({objectID: {$in: allItemIDs}})
      .then(function(items) {
        var existedItemIDs = getItemIds(items || []);
        var newItemIDs = _.difference(allItemIDs, existedItemIDs);

        var existedItems = _.filter(kanbanItems,
          filterItemsByID.bind(this, existedItemIDs));
        var newItems = _.filter(kanbanItems,
          filterItemsByID.bind(this, newItemIDs));

        return Promise
          .all([
            that.updateItems(existedItems),
            itemInsert(newItems),
            existedItems,
            snapshotPromise
          ])
          .spread(function(updatedItemCnt, newItems, existedItems, snapshots) {
            return {
              updatedItemCnt: updatedItemCnt,
              newItems: newItems,
              updatedItems: existedItems,
              snapshots: snapshots
            };
          });
      });
  };
}

module.exports = KanbanStorage;
<|endoftext|>


<commit_before>const exec = require('child_process').spawn;
const fs = require('fs');

const toolbox = require('../toolbox.js');
const otu_manager = require('../otu_manager.js');
const merging = require('./fasta-merging.js');

exports.name = 'swarm';
exports.multicore = true;
exports.category = 'Clustering';

exports.run = function (os, config, callback) {
	let token = os.token;
	let directory = '/app/data/' + token + '/';
	let tmp_merged = toolbox.tmp_filename() + '.fasta';
	let tmp_origins = toolbox.tmp_filename() + '.tsv';

	let otu_file = config.params.outputs.otus_table;

	let config_merging = {params:{
		inputs: config.params.inputs,
		outputs: {
			merged: tmp_merged,
			origins: tmp_origins
		},
		params: {}
	}, log:config.log};

	// Merging
	merging.run (os, config_merging, (_, msg) => {
		if (msg != null) {
			callback(os, msg);
			return;
		}

		// OTU search
		config.params.inputs.merged = tmp_merged;
		config.params.inputs.origins = tmp_origins;
		otu_search(os, config, (os, msg) => {
			// Remove unnecessary files
			fs.unlink(directory+tmp_origins, ()=>{});
			fs.unlink(directory+tmp_merged, ()=>{});

			callback(os, msg);
		});
	});
};

var otu_search = (os, config, callback) => {
	let directory = '/app/data/' + os.token + '/';
	// let tmp_output = toolbox.tmp_filename() + '.txt';
	let tmp_output = toolbox.tmp_filename() + '.uc';

	// Swarm options
	var options = [directory + config.params.inputs.merged,
	'-o', '/dev/null', //directory + tmp_output,
	'-u', directory + tmp_output,
	'-f', '-z',
	'-w', directory + config.params.outputs.centroids,
	'-t', os.cores,
	'-d', config.params.params.max_diff];

	// Execute swarm
	console.log("Running swarm with the command line:");
	console.log('/app/lib/swarm/bin/swarm', options.join(' '));
	var child = exec('/app/lib/swarm/bin/swarm', options);

	child.stdout.on('data', function(data) {
		fs.appendFileSync(directory + config.log, data);
	});
	child.stderr.on('data', function(data) {
		fs.appendFileSync(directory + config.log, data);
	});
	child.on('close', function(code) {
		if (code != 0) {
			fs.unlink(directory+tmp_output, ()=>{});
			console.log (os.token + ': Error durong swarm execution')
			callback(os, 'Error during swarm execution');
		} else {
			config.params.inputs.uc = tmp_output;
			otu_manager.write_from_uc(os, config, (os, msg) => {
				fs.unlink(directory+tmp_output, ()=>{});
				callback(os, msg);
			});
		}
	});
};

<commit_msg>bugfix: -d and -f options of swarm should not be executed simultaneously<commit_after>const exec = require('child_process').spawn;
const fs = require('fs');

const toolbox = require('../toolbox.js');
const otu_manager = require('../otu_manager.js');
const merging = require('./fasta-merging.js');

exports.name = 'swarm';
exports.multicore = true;
exports.category = 'Clustering';

exports.run = function (os, config, callback) {
	let token = os.token;
	let directory = '/app/data/' + token + '/';
	let tmp_merged = toolbox.tmp_filename() + '.fasta';
	let tmp_origins = toolbox.tmp_filename() + '.tsv';

	let otu_file = config.params.outputs.otus_table;

	let config_merging = {params:{
		inputs: config.params.inputs,
		outputs: {
			merged: tmp_merged,
			origins: tmp_origins
		},
		params: {}
	}, log:config.log};

	// Merging
	merging.run (os, config_merging, (_, msg) => {
		if (msg != null) {
			callback(os, msg);
			return;
		}

		// OTU search
		config.params.inputs.merged = tmp_merged;
		config.params.inputs.origins = tmp_origins;
		otu_search(os, config, (os, msg) => {
			// Remove unnecessary files
			fs.unlink(directory+tmp_origins, ()=>{});
			fs.unlink(directory+tmp_merged, ()=>{});

			callback(os, msg);
		});
	});
};

var otu_search = (os, config, callback) => {
	let directory = '/app/data/' + os.token + '/';
	// let tmp_output = toolbox.tmp_filename() + '.txt';
	let tmp_output = toolbox.tmp_filename() + '.uc';

	// Swarm options
	var options = [directory + config.params.inputs.merged,
	'-o', '/dev/null', //directory + tmp_output,
	'-u', directory + tmp_output,
	'-z',
	'-w', directory + config.params.outputs.centroids,
	'-t', os.cores];

	if (config.params.params.max_diff == 1)
		options = options.concat(['-f']);
	else
		options = options.concat(['-d', config.params.params.max_diff]);

	// Execute swarm
	console.log("Running swarm with the command line:");
	console.log('/app/lib/swarm/bin/swarm', options.join(' '));
	var child = exec('/app/lib/swarm/bin/swarm', options);

	child.stdout.on('data', function(data) {
		fs.appendFileSync(directory + config.log, data);
	});
	child.stderr.on('data', function(data) {
		fs.appendFileSync(directory + config.log, data);
	});
	child.on('close', function(code) {
		if (code != 0) {
			fs.unlink(directory+tmp_output, ()=>{});
			console.log (os.token + ': Error durong swarm execution')
			callback(os, 'Error during swarm execution');
		} else {
			config.params.inputs.uc = tmp_output;
			otu_manager.write_from_uc(os, config, (os, msg) => {
				fs.unlink(directory+tmp_output, ()=>{});
				callback(os, msg);
			});
		}
	});
};

<|endoftext|>


<commit_before>// clone object
Object.clone = function(object) {
    var O = function() {};
    O.prototype = object || {};
    return new O();
};

// Object.paht('Object.key.key.value'[, {'key': {'key': {'value': 123}}});
Object.path = function (path, scope) {

    path = path.split('.');
    scope = scope || window;

    // find keys in paths or return null
    for (var i = 0; i < path.length; ++i) {
        if (!(scope = scope[path[i]])) {
            return;
        }
    }

    return scope;
};

// global module loader
var M = (function(miids, modules, wsCache, fn) {

    // check and initialize websockets
    if (typeof WebSocket !== fn) {
        return alert('You have an old browser :(\n\nPlease update to the latest version\nand this site will work.');
    }

    var webSocket = new WebSocket('ws://' + window.location.host + '/');
    var head = document.getElementsByTagName('head')[0];
    var convertToArray = Array.prototype.slice;

    // module class
    var Mono = {
        // remove the module instance cache
        die: function () {
            delete miids[this.miid];
        },
        // listen to events
        on: listen,
        one: function (event, handler) {
            listen.call(this, event, handler, true);
        },
        // remove listeners
        off: function (event, handler) {
            var events = this.mono.events;

            if (events && events[event]) {

                if (handler) {

                    for (var i = 0, l = events[event].length; i < l; ++i) {

                        if (events[event][i][0] === handler) {
                            events[event].splice(i, 1);
                        }
                    }

                } else {
                    delete events[event];
                }
            }
        },
        // emit event
        emit: function(event) {
            var events = this.mono.events;

            // mark miid as ready
            if (event === 'ready') {
                this.mono.ready = true;
            }

            if (events && events[event]) {

                // slice first argument and apply the others to the callback function
                var args = convertToArray.call(arguments).slice(1);

                for (var i = 0, l = events[event].length; i < l; ++i) {

                    if (events[event][i]) {

                        // Fire registred Methods
                        events[event][i].apply(this, args);

                        // remove from event buffer, if once is true
                        if (events[event][i].one) {
                            events[event].splice(i, 1);
                        }
                    }
                }
            }
        },
        // emit an event on a miid
        push: function (miid, event) {
            if (miids[miid]) {

                // slice first argument and apply the others to the callback function
                var args = convertToArray.call(arguments).slice(1);

                miids[miid].emit.apply(miids[miid], args);
            }
        },
        // emit on all miids
        pushAll: function (event) {

            // slice first argument and apply the others to the callback function
            var args = convertToArray.call(arguments);

            for (var miid in miids) {
                miids[miid].emit.apply(miids[miid], args);
            }
        }
    };

    // create core module instance
    var core = Object.clone(Mono);
    core.mono = {miid: 'M', ready: true};

    // load miid
    core.load = function (miid, parentMiid) {

        // don't load the same miid more than once
        if (!miid || miids[miid]) {
            return;
        }

        // wait for websocket
        if (webSocket.readyState !== webSocket.OPEN) {
            return webSocket.onopen = function () {
                miids.M.load(miid, parentMiid);
            };
        }

        // get miid config
        miids.M.emit('load', null, miid, function (err, config) {

            if (err) {
                return;
            }

            // load dependend modules
            if (config.modules) {
                for (var i = 0, l = config.modules.length; i < l; ++i) {
                    M(config.modules[i], miid);
                }
            }

            // add parent miid to waitFor config
            if (parentMiid) {
                config.waitFor = config.waitFor || [];
                config.waitFor.push(parentMiid);
            }

            // create module instance
            miids[miid] = Object.clone(Mono);
            var miidMono = miids[miid].mono = {
                config: config,
                miid: miid,
                name: config.name
            };

            // load scripts and init module
            if (config.scripts && config.scripts.length > 0) {
                loadJS(miid, config.name, config.scripts, function (miid, moduleConstructor) {

                    miidMono.init = miidMono.init || moduleConstructor;

                    // listen to send events
                    if (config.events) {
                        for (var i = 0, l = config.events.length; i < l; ++i) {
                            miids[miid].on(config.events[i], send(config.events[i]));
                        }
                    }

                    initModule(miid);
                });

            } else {
                initModule(miid);
            }
        });
    };

    // wrapping function
    core.load.wrap = function (name, module) {
        modules[name] = module;
        miids.M.emit(name);
    };

    // listen to core load event
    core.on('load', send('load'));

    // miid cache
    miids.M = core;

    // parse websocket messages: ['miid:event:cbId','err','data']
    webSocket.onmessage = function (message) {

        try {
            message = JSON.parse(message.data);
        } catch (error) {
            return;
        }

        message[0] = message[0].split(':');

        var miid = message[0][0];
        var event = message[0][1];
        var cbId = message[0][2];
        var err = message[1];
        var data = message[2];

        if (miid && event && miids[miid]) {

            // call callback
            if (wsCache[cbId]) {
                wsCache[cbId].call(miids[miid], err, data);
                delete wsCache[cbId];
            }

            // emit event
            miids[miid].emit(event, err, data);
        }
    };

    // create websocket message: ['miid:event:cbId','err','data']
    function send (event) {
        return function (err, data, callback) {

            var message = [this.mono.miid + ':' + event, err || 0];
            var cbId;

            if (data) {
                message[2] = data;
            }

            if (callback) {
                cbId = uid(5);
                message[0] += ':' + cbId;
            }

            try {
                message = JSON.stringify(message);
            } catch (parseError) {
                if (callback) {
                    callback(parseError);
                }
                return;
            }

            if (callback) {
                wsCache[cbId] = callback;
            }

            webSocket.send(message);
        };
    }

    // random string generator
    function uid (len) {
        for (var i = 0, l = len || 23, random = ''; i < l; ++i) {
            random += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[0 | Math.random() * 62];
        }
        return random;
    }

    // add listeneres
    function listen (event, handler, once) {
        var events = this.mono.events = this.mono.events || {};

        if (typeof handler === fn) {
            // fire ready event immediately if miid is ready
            if (event === 'ready' && this.mono.ready) {
                return handler.call(this);
            }

            if (!events[event]) {
                events[event] = [];
            }

            handler.one = once;
            events[event].push(handler);
        }
    }

    // load scripts
    function initModule (miid) {

        miid = miids[miid].mono;

        // call module init
        if (typeof miid.init === fn) {

            // handle waitFor
            if (miid.config.waitFor instanceof Array) {
                var count = miid.config.waitFor.length;
                var current = 0;
                var handler = function () {
                    if (current === count) {
                        miid.init.call(miids[miid.miid]);
                    }
                };

                for (;current < count; ++current) {
                    miids[miid.miid].one('ready', miid.config.waitFor[current], handler);
                }
            } else {
                miid.init.call(miids[miid.miid]);
            }
        }
    }

    // commonjs require
    function require (module) {
        return function (name) {
            if (name.indexOf('../') === 0) {

                var namePath = name.split('../');
                var stepBackLenght = namePath.length - 1;
                namePath = namePath.pop();

                name = module.base + (module.path.length === stepBackLenght ? namePath : module.path.slice(0, stepBackLenght).join('/') + '/' + namePath);

            } else if (name.indexOf('./') === 0) {
                var path = module.path.join('/');
                name = module.base + (path ? path + '/' : '') + name.substr(2);
            } else {

                if (name.split('/').length < 5 && name[name.length - 1] !== '/') {
                    name += '/';
                }

                for (var script in modules) {
                    if (script.indexOf(name) === 0) {
                        name = script;
                        break;
                    }
                }
            }

            name += name.slice(-3) !== '.js' ? '.js' : '';
            if (modules[name]) {
                return modules[name].exports;
            }
        };
    }

    // create CommonJS modules in order of the dependencies
    function createCommonJsModulesInOrder (miid, moduleSources, callback) {

        // init modules in order (desc)
        for (var i = (moduleSources.length - 1), l = 0; i >= l; --i) {

            // evaluate module script
            if (typeof modules[moduleSources[i]] === fn && !modules[moduleSources[i]]._eval) {

                    var module = {
                        id: moduleSources[i],
                        exports: {}
                    };

                    module.path = module.id.split('/');
                    module.file = module.path.pop();

                    if (module.id.indexOf('//') === 0) {

                        module.base = '//';
                        module.path = module.path.slice(2);

                    } else if (module.id[0] === '/') {

                        module.base = '/';
                        module.path = module.path.slice(1);

                    } else if (module.id.indexOf('://') > 0) {

                        module.base = module.path.slice(0,3).join('/') + '/';
                        module.path = module.path.slice(3);

                    } else {

                        module.base = module.path.slice(0,4).join('/') + '/';
                        module.path = module.path.slice(4);
                    }

                    // execute CommonJS module
                    modules[moduleSources[i]] = modules[moduleSources[i]].call(module.exports, require(module), module, module.exports);
                    modules[moduleSources[i]]._eval = true;
            }
        }

        // return first module of dependency list
        callback(miid, modules[moduleSources[0]] ? modules[moduleSources[0]].exports : null);
    }

    // load scripts (script tag)
    function loadJS (miid, moduleName, moduleSources, callback) {

        var length = moduleSources.length;

        var extDepLoaded = function (src) {
            return function () {
                modules[src] = 3;
                miids.M.emit(src);
            };
        };
        var modDepLoaded = function () {
            if (--length === 0) {
                createCommonJsModulesInOrder(miid, moduleSources, callback);
            }
        };

        for (var i = moduleSources.length - 1, source, url; i >= 0; --i) {

            // ingore loading for unified code
            if (moduleSources[0] === '#') {
                // remove the control sign
                moduleSources[i] = moduleSources[i].indexOf('./') === 1 ? moduleSources[i].substr(3) : moduleSources[i].substr(1);
                --length;
                continue;
            }

            // load external scripts
            if (moduleSources[i][0] === '/' || moduleSources[i].indexOf('://') > 0) {
                modules[moduleSources[i]] = 1;
                node.onload = extDepLoaded(moduleSources[i]);
                url = source = moduleSources[i];
            } else {
                url = '/@/M/';

                // load module file
                if (moduleSources[i].indexOf('./') === 0) {
                    moduleSources[i] = moduleSources[i].substr(2);
                    url = url + 'mod/' + miid + '/' + moduleSources[i];
                    source = moduleName + moduleSources[i];
                // load dependency
                } else {
                    source = moduleSources[i];
                    url = url + 'dep/' + miid + '/' + source;
                }

                modules[source] = 2;
                moduleSources[i] = source;
            }

            // when script is loaded check if it's evaluated
            miids.M.one(source, modDepLoaded);

            // emit source event for already loaded scripts
            if (typeof modules[source] === fn || modules[source] === 3) {
                miids.M.emit(source);

            // load module scripts
            } else {
                var node = document.createElement('script');
                node.src = url;
                head.appendChild(node);
            }
        }
    }

    return core.load;
})({}, {}, {}, 'function');
<commit_msg>moved clone and path functions into the M namespace<commit_after>// global module loader
var M = (function(miids, modules, wsCache, fn) {

    // check and initialize websockets
    if (typeof WebSocket !== fn) {
        return alert('You have an old browser :(\n\nPlease update to the latest version\nand this site will work.');
    }

    var webSocket = new WebSocket('ws://' + window.location.host + '/');
    var head = document.getElementsByTagName('head')[0];
    var convertToArray = Array.prototype.slice;

    // module class
    var Mono = {
        // remove the module instance cache
        die: function () {
            delete miids[this.miid];
        },
        // listen to events
        on: listen,
        one: function (event, handler) {
            listen.call(this, event, handler, true);
        },
        // remove listeners
        off: function (event, handler) {
            var events = this.mono.events;

            if (events && events[event]) {

                if (handler) {

                    for (var i = 0, l = events[event].length; i < l; ++i) {

                        if (events[event][i][0] === handler) {
                            events[event].splice(i, 1);
                        }
                    }

                } else {
                    delete events[event];
                }
            }
        },
        // emit event
        emit: function(event) {
            var events = this.mono.events;

            // mark miid as ready
            if (event === 'ready') {
                this.mono.ready = true;
            }

            if (events && events[event]) {

                // slice first argument and apply the others to the callback function
                var args = convertToArray.call(arguments).slice(1);

                for (var i = 0, l = events[event].length; i < l; ++i) {

                    if (events[event][i]) {

                        // Fire registred Methods
                        events[event][i].apply(this, args);

                        // remove from event buffer, if once is true
                        if (events[event][i].one) {
                            events[event].splice(i, 1);
                        }
                    }
                }
            }
        },
        // emit an event on a miid
        push: function (miid, event) {
            if (miids[miid]) {

                // slice first argument and apply the others to the callback function
                var args = convertToArray.call(arguments).slice(1);

                miids[miid].emit.apply(miids[miid], args);
            }
        },
        // emit on all miids
        pushAll: function (event) {

            // slice first argument and apply the others to the callback function
            var args = convertToArray.call(arguments);

            for (var miid in miids) {
                miids[miid].emit.apply(miids[miid], args);
            }
        }
    };

    // clone object
    function clone (object) {
        var O = (function() {});
        O.prototype = object || {};
        return new O();
    }

    // load miid
    function load (miid, parentMiid) {

        // don't load the same miid more than once
        if (!miid || miids[miid]) {
            return;
        }

        // wait for websocket
        if (webSocket.readyState !== webSocket.OPEN) {
            return webSocket.onopen = function () {
                load(miid, parentMiid);
            };
        }

        // get miid config
        miids.M.emit('load', null, miid, function (err, config) {

            if (err) {
                return;
            }

            // load dependend modules
            if (config.modules) {
                for (var i = 0, l = config.modules.length; i < l; ++i) {
                    M(config.modules[i], miid);
                }
            }

            // add parent miid to waitFor config
            if (parentMiid) {
                config.waitFor = config.waitFor || [];
                config.waitFor.push(parentMiid);
            }

            // create module instance
            miids[miid] = clone(Mono);
            var miidMono = miids[miid].mono = {
                config: config,
                miid: miid,
                name: config.name
            };

            // load scripts and init module
            if (config.scripts && config.scripts.length > 0) {
                loadJS(miid, config.name, config.scripts, function (miid, moduleConstructor) {

                    miidMono.init = miidMono.init || moduleConstructor;

                    // listen to send eventsip
                    if (config.events) {
                        for (var i = 0, l = config.events.length; i < l; ++i) {
                            miids[miid].on(config.events[i], send(config.events[i]));
                        }
                    }

                    initModule(miid);
                });

            } else {
                initModule(miid);
            }
        });
    }

    // add listeneres
    function listen (event, handler, once) {
        var events = this.mono.events = this.mono.events || {};

        if (typeof handler === fn) {
            // fire ready event immediately if miid is ready
            if (event === 'ready' && this.mono.ready) {
                return handler.call(this);
            }

            if (!events[event]) {
                events[event] = [];
            }

            handler.one = once;
            events[event].push(handler);
        }
    }

    // load scripts
    function initModule (miid) {

        miid = miids[miid].mono;

        // call module init
        if (typeof miid.init === fn) {

            // handle waitFor
            if (miid.config.waitFor instanceof Array) {
                var count = miid.config.waitFor.length;
                var current = 0;
                var handler = function () {
                    if (current === count) {
                        miid.init.call(miids[miid.miid]);
                    }
                };

                for (;current < count; ++current) {
                    miids[miid.miid].one('ready', miid.config.waitFor[current], handler);
                }
            } else {
                miid.init.call(miids[miid.miid]);
            }
        }
    }

    // commonjs require
    function require (module) {
        return function (name) {
            if (name.indexOf('../') === 0) {

                var namePath = name.split('../');
                var stepBackLenght = namePath.length - 1;
                namePath = namePath.pop();

                name = module.base + (module.path.length === stepBackLenght ? namePath : module.path.slice(0, stepBackLenght).join('/') + '/' + namePath);

            } else if (name.indexOf('./') === 0) {
                var path = module.path.join('/');
                name = module.base + (path ? path + '/' : '') + name.substr(2);
            } else {

                if (name.split('/').length < 5 && name[name.length - 1] !== '/') {
                    name += '/';
                }

                for (var script in modules) {
                    if (script.indexOf(name) === 0) {
                        name = script;
                        break;
                    }
                }
            }

            name += name.slice(-3) !== '.js' ? '.js' : '';
            if (modules[name]) {
                return modules[name].exports;
            }
        };
    }

    // create CommonJS modules in order of the dependencies
    function createCommonJsModulesInOrder (miid, moduleSources, callback) {

        // init modules in order (desc)
        for (var i = (moduleSources.length - 1), l = 0; i >= l; --i) {

            // evaluate module script
            if (typeof modules[moduleSources[i]] === fn && !modules[moduleSources[i]]._eval) {

                    var module = {
                        id: moduleSources[i],
                        exports: {}
                    };

                    module.path = module.id.split('/');
                    module.file = module.path.pop();

                    if (module.id.indexOf('//') === 0) {

                        module.base = '//';
                        module.path = module.path.slice(2);

                    } else if (module.id[0] === '/') {

                        module.base = '/';
                        module.path = module.path.slice(1);

                    } else if (module.id.indexOf('://') > 0) {

                        module.base = module.path.slice(0,3).join('/') + '/';
                        module.path = module.path.slice(3);

                    } else {

                        module.base = module.path.slice(0,4).join('/') + '/';
                        module.path = module.path.slice(4);
                    }

                    // execute CommonJS module
                    modules[moduleSources[i]] = modules[moduleSources[i]].call(module.exports, require(module), module, module.exports);
                    modules[moduleSources[i]]._eval = true;
            }
        }

        // return first module of dependency list
        callback(miid, modules[moduleSources[0]] ? modules[moduleSources[0]].exports : null);
    }

    // random string generator
    function uid (len) {
        for (var i = 0, l = len || 23, random = ''; i < l; ++i) {
            random += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[0 | Math.random() * 62];
        }
        return random;
    }

    // load scripts (script tag)
    function loadJS (miid, moduleName, moduleSources, callback) {

        var length = moduleSources.length;

        var extDepLoaded = function (src) {
            return function () {
                modules[src] = 3;
                miids.M.emit(src);
            };
        };
        var modDepLoaded = function () {
            if (--length === 0) {
                createCommonJsModulesInOrder(miid, moduleSources, callback);
            }
        };

        for (var i = moduleSources.length - 1, source, url; i >= 0; --i) {

            // ingore loading for unified code
            if (moduleSources[0] === '#') {
                // remove the control sign
                moduleSources[i] = moduleSources[i].indexOf('./') === 1 ? moduleSources[i].substr(3) : moduleSources[i].substr(1);
                --length;
                continue;
            }

            // load external scripts
            if (moduleSources[i][0] === '/' || moduleSources[i].indexOf('://') > 0) {
                modules[moduleSources[i]] = 1;
                node.onload = extDepLoaded(moduleSources[i]);
                url = source = moduleSources[i];
            } else {
                url = '/@/M/';

                // load module file
                if (moduleSources[i].indexOf('./') === 0) {
                    moduleSources[i] = moduleSources[i].substr(2);
                    url = url + 'mod/' + miid + '/' + moduleSources[i];
                    source = moduleName + moduleSources[i];
                // load dependency
                } else {
                    source = moduleSources[i];
                    url = url + 'dep/' + miid + '/' + source;
                }

                modules[source] = 2;
                moduleSources[i] = source;
            }

            // when script is loaded check if it's evaluated
            miids.M.one(source, modDepLoaded);

            // emit source event for already loaded scripts
            if (typeof modules[source] === fn || modules[source] === 3) {
                miids.M.emit(source);

            // load module scripts
            } else {
                var node = document.createElement('script');
                node.src = url;
                head.appendChild(node);
            }
        }
    }

    // create websocket message: ['miid:event:cbId','err','data']
    function send (event) {
        return function (err, data, callback) {

            var message = [this.mono.miid + ':' + event, err || 0];
            var cbId;

            if (data) {
                message[2] = data;
            }

            if (callback) {
                cbId = uid(5);
                message[0] += ':' + cbId;
            }

            try {
                message = JSON.stringify(message);
            } catch (parseError) {
                if (callback) {
                    callback(parseError);
                }
                return;
            }

            if (callback) {
                wsCache[cbId] = callback;
            }

            webSocket.send(message);
        };
    }

    // parse websocket messages: ['miid:event:cbId','err','data']
    webSocket.onmessage = function (message) {

        try {
            message = JSON.parse(message.data);
        } catch (error) {
            return;
        }

        message[0] = message[0].split(':');

        var miid = message[0][0];
        var event = message[0][1];
        var cbId = message[0][2];
        var err = message[1];
        var data = message[2];

        if (miid && event && miids[miid]) {

            // call callback
            if (wsCache[cbId]) {
                wsCache[cbId].call(miids[miid], err, data);
                delete wsCache[cbId];
            }

            // emit event
            miids[miid].emit(event, err, data);
        }
    };

    // create and init core module instance
    miids.M = clone(Mono);
    miids.M.mono = {miid: 'M', ready: true};
    miids.M.on('load', send('load'));

    // wrapping function
    load.wrap = function (name, module) {
        modules[name] = module;
        miids.M.emit(name);
    };

    // export some handy functions
    load.clone = clone;

    // Object.paht('Object.key.key.value'[, {'key': {'key': {'value': 123}}});
    load.path = function (path, scope) {

        path = path.split('.');
        scope = scope || window;

        // find keys in paths or return null
        for (var i = 0; i < path.length; ++i) {
            if (!(scope = scope[path[i]])) {
                return;
            }
        }

        return scope;
    };

    return load;
})({}, {}, {}, 'function');
<|endoftext|>