<commit_before>/*
  Parser
  Parses clean source. Generates clean AST.
*/

// Utility functions

const mayBe = (value, func) => value === null ? null : func(...value)

// Predifined regexes

const idRegex = /^([a-zA-Z]+[a-zA-Z0-9_]*)((.|\n)*)$/
const numRegex = /^([\-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[e][+\-]?\d+)?)((.|\n)*)$/
// const nullRegEx       = /^null\b/i
const boolRegex = /^(true|false)((.|\n)*)$/i
const stringRegex = /^(\'[^\\']*(?:\\.[^\\']*)*\')((.|\n)*)$/

/*
  Parser factory. Used to create parsers
  parser.regex - creates parser for given regex
  parser.bind - applies monadic bind on parsers
  parser.all - All parsers supplied MUST match
  parser.any - ANY ONE of the parsers need match
*/

const parser = {}

parser.regex = regex => src => mayBe(
  regex.exec(src),
  (m, val, rest) => [val, rest]
)

parser.bind = (mv, mf) => str => mayBe(
  mv(str),
  (val, rest) => mf(val)(rest)
)

parser.all = (...parsers) => src => {
  var rest = src
  var values = []
  for (let parser of parsers) {
    let result = parser(rest)
    if (result === null) return null
    values.push(result[0])
    rest = result[1]
  }
  return [values, rest]
}

parser.any = (...parsers) => src => {
  for (let parser of parsers) {
    let result = parser(src)
    if (result !== null) return result
  }
  return null
}

/*
  All required parsers are created below
*/

const spaceParser = parser.regex(/^(\s+)((.|\n)*)$/)

const equalSignParser = parser.regex(/^(\s+=\s+)((.|\n)*)$/)

const numberParser = str => mayBe(
  numRegex.exec(str),
  (a, num, rest) => [{type: 'Literal', value: Number(num), raw: num}, rest]
)

const identifierParser = str => mayBe(
  idRegex.exec(str),
  (a, name, rest) => [{type: 'Identifier', name: name}, rest]
)

const stringParser = str => mayBe(
  stringRegex.exec(str),
  (a, string, rest) => [{ type: 'StringLiteral', value: '"' + string + '"' }, rest]
)

const booleanParser = str => mayBe(
  boolRegex.exec(str),
  (a, bool, rest) => [{type: 'Literal', value: bool}, rest]
)

const paramParser = parser.all(spaceParser, parser.any(numberParser, identifierParser))

const paramsParser = (str, paramArray = []) => {
  const param = paramParser(str)
  if (param !== null) return paramsParser(param[1], paramArray.concat(param[0][1]))
  return [paramArray, str]
}

const parenParser = str => {

}

const fnDeclParser = parser.bind(
  parser.bind(
    parser.bind(
      identifierParser,
      val => str => mayBe(
        paramsParser(str),
        (params, rest) => [{
          type: 'VariableDeclaration',
          declarations: [
            {
              type: 'VariableDeclarator',
              id: val,
              init: {
                type: 'ArrowFunctionExpression',
                id: null,
                params: params,
                body: {},
                generator: false,
                expression: true
              }
            }
          ],
          kind: 'const'}, rest]
      )
    ),
    val => str => mayBe(
      equalSignParser(str),
      (v, rest) => [val, rest]
    )
  ),
  val => str => mayBe(
    expressionParser(str),
    (v, rest) => {
      val.declarations[0].init.body = v
      return [val, rest]
    }
  )
)

const expressionParser = parser.any(numberParser)

const declParser = parser.bind(
  parser.bind(
    identifierParser,
    val => str => mayBe(
      equalSignParser(str),
      (v, rest) => [val, rest]
    )
  ),
  val => str => mayBe(
    expressionParser(str),
    (v, rest) => [{
      type: 'VariableDeclaration',
      declarations: [
        {
          type: 'VariableDeclarator',
          id: val,
          init: v
        }
      ],
      kind: 'const'
    }]
  )
)

module.exports = function (src) {
  const ast = {type: 'Program', body: [], sourceType: 'script'}
  const result = fnDeclParser(src)
  if (result !== null) ast.body.push(result[0])
  return ast
}
<commit_msg>Modify AST<commit_after>/*
  Parser
  Parses clean source. Generates clean AST.
*/

// Utility functions

const mayBe = (value, func) => value === null ? null : func(...value)

// Predifined regexes

const idRegex = /^([a-zA-Z]+[a-zA-Z0-9_]*)((.|\n)*)$/
const numRegex = /^([\-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[e][+\-]?\d+)?)((.|\n)*)$/
// const nullRegEx       = /^null\b/i
const boolRegex = /^(true|false)((.|\n)*)$/i
const stringRegex = /^(\'[^\\']*(?:\\.[^\\']*)*\')((.|\n)*)$/
const parenRegex = /^({[^{]*[^}]})((.|\n)*)$/

/*
  Parser factory. Used to create parsers
  parser.regex - creates parser for given regex
  parser.bind - applies monadic bind on parsers
  parser.all - All parsers supplied MUST match
  parser.any - ANY ONE of the parsers need match
*/

const parser = {}

parser.regex = regex => src => mayBe(
  regex.exec(src),
  (m, val, rest) => [val, rest]
)

parser.bind = (mv, mf) => str => mayBe(
  mv(str),
  (val, rest) => mf(val)(rest)
)

parser.all = (...parsers) => src => {
  var rest = src
  var values = []
  for (let parser of parsers) {
    let result = parser(rest)
    if (result === null) return null
    values.push(result[0])
    rest = result[1]
  }
  return [values, rest]
}

parser.any = (...parsers) => src => {
  for (let parser of parsers) {
    let result = parser(src)
    if (result !== null) return result
  }
  return null
}

/*
  All required parsers are created below
*/

const spaceParser = parser.regex(/^(\s+)((.|\n)*)$/)

const equalSignParser = parser.regex(/^(\s+=\s+)((.|\n)*)$/)

const numberParser = str => mayBe(
  numRegex.exec(str),
  (a, num, rest) => [{type: 'Literal', value: Number(num), raw: num}, rest]
)

const identifierParser = str => mayBe(
  idRegex.exec(str),
  (a, name, rest) => [{type: 'Identifier', name: name}, rest]
)

const stringParser = str => mayBe(
  stringRegex.exec(str),
  (a, string, rest) => [{ type: 'StringLiteral', value: '"' + string + '"' }, rest]
)

const booleanParser = str => mayBe(
  boolRegex.exec(str),
  (a, bool, rest) => [{type: 'Literal', value: bool}, rest]
)

const paramParser = parser.all(spaceParser, parser.any(numberParser, identifierParser))

const paramsParser = (str, paramArray = []) => {
  const param = paramParser(str)
  if (param !== null) return paramsParser(param[1], paramArray.concat(param[0][1]))
  return [paramArray, str]
}

const parenParser = str => {

}

const fnDeclParser = parser.bind(
  parser.bind(
    parser.bind(
      identifierParser,
      val => str => mayBe(
        paramsParser(str),
        (params, rest) => [{
          type: 'VariableDeclaration',
          declarations: [
            {
              type: 'VariableDeclarator',
              id: val,
              init: {
                type: 'ArrowFunctionExpression',
                id: null,
                params: params,
                body: {},
                generator: false,
                expression: true
              }
            }
          ],
          kind: 'const'}, rest]
      )
    ),
    val => str => mayBe(
      equalSignParser(str),
      (v, rest) => [val, rest]
    )
  ),
  val => str => mayBe(
    expressionParser(str),
    (v, rest) => {
      val.declarations[0].init.body = v
      return [val, rest]
    }
  )
)

const expressionParser = parser.any(numberParser)

const declParser = parser.bind(
  parser.bind(
    identifierParser,
    val => str => mayBe(
      equalSignParser(str),
      (v, rest) => [val, rest]
    )
  ),
  val => str => mayBe(
    expressionParser(str),
    (v, rest) => [{
      type: 'VariableDeclaration',
      declarations: [
        {
          type: 'VariableDeclarator',
          id: val,
          init: v
        }
      ],
      kind: 'const'
    }]
  )
)

module.exports = function (src) {
  const ast = {type: 'Program', body: [], sourceType: 'script'}
  const result = parser.any(declParser, fnDeclParser)(src)
  if (result !== null) ast.body.push(result[0])
  return ast
}
<|endoftext|>


<commit_before>var mongoose = require("mongoose")
  , _ = require("underscore")
  , async = require("async")
  , Status = require("./status")

/**
 * The schema for an embedded friendship.  (Treated as an object for compat
 * with embedding schemas)
 */
var Friendship = {
  // the status of this friendship, see the Status module for details
  status: {type: String, enum: _.values(Status)},

  // when this friendship was added
  added: Date
}

/**
 * Default option values
 */
var optionDefaults = {
  /**
   * The name of the document array "friends" path (default: "friends")
   */
  pathName: "friends",

  /**
   * Should an index be added ot the friends doc array? (default: true)
   */
  index: true
};

/**
 * expose the plugin function as exports
 *
 * Options
 * ---
 * - {String} pathName the pathname for the friends document array (default "friends")
 * - {Boolean} index whether the plugin should automatically index the friends array (default: true)
 *
 * @param {Object} options
 */
module.exports = plugin;

/**
 * expose Status as a property of the plugin
 */
module.exports.Status = Status;

function plugin (options) {
  options = _.defaults(options || {}, optionDefaults);

  // the pathName for the friends array
  var pathName = options.pathName;

  // Fields to add to the extending model
  var fields = {};
  fields[pathName] = { type: [Friendship], select: false };

  /**
   * The work function which pushes or updates embedded friend objects
   * for two documents, returns a function
   *
   * @api private
   */
  var _update = function (query, update, fship) {
    var options = { new: false };

    return function (done) {
      this.findOneAndUpdate(query, update, options, function (err, res) {
        done(err, fship);
      });
    }
  }

  /**
   * Return a function to update a friendship between two parties
   *
   * @api private
   */
  var updateFriendship = function (m1, m2, fship) {
    var query = {_id: m1};
    query[pathName] = {$elemMatch: {_id: m2}};

    var updater = {$set: {}};
    updater.$set[pathName+".$.status"] = fship.status;

    return _update(query, updater, fship);
  }

  /**
   * Return a function to create a new friendship between two parties
   *
   * @api private
   */
  var pushFriendship = function (m1, m2, fship) {
    var query = {_id: m1};

    fship.added = new Date();

    var updater = {$push: {}};
    updater.$push[pathName] = fship;

    return _update(query, updater, fship);
  }

  /**
   * Generate a function to return one side of a friendship between
   * two models
   *
   * @param Model the extending model
   * @param m1 the model or model _id being queried
   * @param m2 the model or model _id whose friendship is queried for
   *
   * @returns a function which will call back with an friendship for m2 on m1
   * @api private
   */
  var friendshipBetween = function (m1, m2) {
    var proj = {};
    proj[pathName] = {$elemMatch: {_id: m2}};

    return function (done) {
      this.findById(m1, proj, function (err, doc) {
        if (err) return done(err);
        if (!doc) return done("friendship not found");
        done(null, doc[pathName][0]);
      });
    }
  }

  return function friends (schema) {
    // add the embedded friends
    schema.add(fields);

    // Index the friends array with a multikey index on _id.  Further indexing
    // on status is probably unnecessary, as all queries will hit _id and
    // this will already limit them to the friends on an individual user.
    if (options.index) {
      var index = {};
      index[pathName+"._id"] = 1;
      schema.index(index, {name: "friendsplugin"});
    }

    /**
     * Send friend request from "friender" to "friend".  Calls back with
     * an object containing the two resulting friend objects
     *
     * On first request, will result in a "requested" friendship for the
     * first party and a "pending" friendship for the second.
     *
     * Reciprocating the request (friendee back to friender) will accept it.
     *
     * @param {Model} m1 the "friender" model doc or _id doing the reqesting
     * @param {Model} m2 the "friendee" model doc or _id being requested
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.requestFriend = function (m1, m2, cb) {
      var steps = {}
        , Model = this

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      async.auto({
        m1: friendshipBetween(m1, m2).bind(this),
        m2: friendshipBetween(m2, m1).bind(this)
      }, function (err, o) {
        if (err) return cb(err);

        var hasfship = !!o.m1
          , fship = o.m1 || {_id: m2}
          , oid = fship._id
          , ostatus = fship.status

        // m2 has no friendship, add a new pending friendship, mark
        // m1's status as requested
        if (!o.m2) {
          fship.status = Status.Requested;
          steps.friend = pushFriendship(m2, m1, {
            _id: m1,
            status: Status.Pending
          }).bind(Model);
        } else {
          switch (o.m2.status) {

          // m2 status is still pending, no update
          case Status.Pending:
            fship.status = Status.Requested;
            break;

          // m2 status is accepted already, no update
          case Status.Accepted:
            fship.status = Status.Accepted;
            break;

          // m2 already requested m1, mark BOTH friendships as accepted
          case Status.Requested:
            fship.status = Status.Accepted;
            steps.friend = updateFriendship(m2, m1, {
              status: Status.Accepted
            }).bind(Model);
            break;
          }
        }

        steps.friend || (steps.friend = function (done) {
          // If no update was necessary, send the friendship back directly
          done(null, o.m2)
        });

        if (hasfship && ostatus == fship.status && oid.equals(fship._id)) {
          steps.friender = function (done) {
            done(null, fship)
          }
        } else if (hasfship) {
          steps.friender = updateFriendship(m1, m2, fship, cb).bind(Model)
        } else {
          steps.friender = pushFriendship(m1, m2, fship, cb).bind(Model)
        }

        async.parallel(steps, cb);
      });
    };

    /**
     * Create a friend request
     *
     * @param {Model} friend The potential friend being requested
     * @param {Function} cb Callback once friendship is created
     */
    schema.methods.requestFriend = function (friend, cb) {
      return this.constructor.requestFriend(this, friend, cb);
    };

    /**
     * Get all friends of a model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Model} model a model doc or _id of a model doc to find friends for
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.getFriends = function (model) {
      var Model = this
        , op = 'find'

      resolveArgs(arguments, function (conditions, fields, options, cb) {
        // assume model is either a Model, or an _id
        model = model._id || model;

        conditions[pathName+"._id"] = model;

        // "accepted" status is mirrored on both sides, but if querying
        // for "pending" or "requested" friends, the query must be reversed
        if (conditions[pathName+".status"]) {
          switch (conditions[pathName+".status"]) {
          case Status.Pending:
            conditions[pathName+".status"] = Status.Requested;
            break;
          case Status.Requested:
            conditions[pathName+".status"] = Status.Pending;
            break;
          }
        }

        async.parallel({
          // query remote friends based on the arguments
          friends: function (done) {
            Model[op](conditions, fields, options, done);
          },

          // Reduce local friend docs to map which will be populated and
          // then combined with the queried remote friends to generate the
          // resulting array
          locals: function (done) {
            var select = {};
            select[pathName] = 1;

            Model.findOne({_id: model}, select, function (err, doc) {
              if (err) return done(err);
              if (!doc) return done(null, []);

              done(null, _.reduce(doc[pathName], function (o, friend) {
                o[friend._id] = friend.toObject();
                return o;
              }, {}));
            });
          }
        }, function (err, res) {
          if (err) return cb(err);

          // wrap the results with the status
          for (var i=0, friendship; i<res.friends.length; i++) {
            friendship = res.locals[res.friends[i]._id];
            friendship.friend = res.friends[i];
            res.friends[i] = friendship;
          }

          cb(null, res.friends);
        });
      });
    };

    /**
     * Get all friends of this model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.getFriends = function (conditions, fields, options, cb) {
      return this.constructor.getFriends(this, conditions, fields, options, cb);
    };

    var getByStatus = function (status) {
      return function (model) {
        resolveArgs(arguments, function (conditions, fields, options, cb) {
          conditions[pathName + ".status"] = status;
          this.getFriends(model, conditions, fields, options, cb);
        }.bind(this));
      }
    }

    /**
     *
     */
    schema.statics.getPendingFriends = getByStatus(Status.Pending);

    /**
     *
     */
    schema.methods.getPendingFriends = function (conditions, fields, options, cb) {
      return this.constructor.getPendingFriends(this, conditions, fields, options, cb);
    }

    /**
     *
     */
    schema.statics.getAcceptedFriends = getByStatus(Status.Accepted);

    /**
     *
     */
    schema.methods.getAcceptedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getAcceptedFriends(this, conditions, fields, options, cb);
    }

    /**
     *
     */
    schema.statics.getRequestedFriends = getByStatus(Status.Requested);

    /**
     *
     */
    schema.methods.getRequestedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getRequestedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Remove a friendship between two friends
     *
     * @param {Model} m1 the first model doc
     * @param {Model} m2 the second model doc
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.removeFriend = function (m1, m2, cb) {
      var collection = this.collection

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      var pull = function (m1, m2) {
        var update = {$pull: {}}
        update.$pull[pathName] = {_id: m2};
        return function (done) {
          collection.update({_id: m1}, update, done);
        }
      }

      async.parallel([pull(m1, m2), pull(m2, m1)], cb);
    };

    /**
     * Remove a friend of this model
     *
     * @param {Model} friend doc to remove
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.removeFriend = function (model, cb) {
      return this.constructor.removeFriend(this, model, cb);
    };
  };
}

/**
 * Expects a signature like Mongoose's `Model.find`, but for the first
 * arg.  Goes through the argument same disambigation that `Model.find`
 * does then calls a callback with the results.  This keeps all this
 * business in one place and lets other functions hook into the options
 * of `getFriends` without copy/pasting this Mongoose logic.
 *
 * @api private
 */
function resolveArgs (args, next) {
  var conditions = args[1]
    , fields = args[2]
    , options = args[3]
    , callback = args[4]

  if ('function' == typeof conditions) {
    callback = conditions;
    conditions = {};
    fields = null;
    options = null;
  } else if ('function' == typeof fields) {
    callback = fields;
    fields = null;
    options = null;
  } else if ('function' == typeof options) {
    callback = options;
    options = null;
  }
  next(conditions, fields, options, callback);
}

/**
 * simple no op function for clarity
 * @api private
 */
function noop() {}
<commit_msg>add more comments<commit_after>var mongoose = require("mongoose")
  , _ = require("underscore")
  , async = require("async")
  , Status = require("./status")

/**
 * The schema for an embedded friendship.  (Treated as an object for compat
 * with embedding schemas)
 */
var Friendship = {
  // the status of this friendship, see the Status module for details
  status: {type: String, enum: _.values(Status)},

  // when this friendship was added
  added: Date
}

/**
 * Default option values
 */
var optionDefaults = {
  /**
   * The name of the document array "friends" path (default: "friends")
   */
  pathName: "friends",

  /**
   * Should an index be added ot the friends doc array? (default: true)
   */
  index: true
};

/**
 * Expose the plugin function as exports
 *
 * Options
 * ---
 * - {String} pathName the pathname for the friends document array (default "friends")
 * - {Boolean} index whether the plugin should automatically index the friends array (default: true)
 *
 * @param {Object} options
 */
module.exports = plugin;

/**
 * Expose Status as a property of the plugin
 */
module.exports.Status = Status;

function plugin (options) {
  options = _.defaults(options || {}, optionDefaults);

  // the pathName for the friends array
  var pathName = options.pathName;

  // Fields to add to the extending model
  var fields = {};
  fields[pathName] = { type: [Friendship], select: false };

  /**
   * The work function which pushes or updates embedded friend objects
   * for two documents, returns a function
   *
   * @api private
   */
  var _update = function (query, update, fship) {
    var options = { new: false };

    return function (done) {
      this.findOneAndUpdate(query, update, options, function (err, res) {
        done(err, fship);
      });
    }
  }

  /**
   * Return a function to update a friendship between two parties
   *
   * @api private
   */
  var updateFriendship = function (m1, m2, fship) {
    var query = {_id: m1};
    query[pathName] = {$elemMatch: {_id: m2}};

    var updater = {$set: {}};
    updater.$set[pathName+".$.status"] = fship.status;

    return _update(query, updater, fship);
  }

  /**
   * Return a function to create a new friendship between two parties
   *
   * @api private
   */
  var pushFriendship = function (m1, m2, fship) {
    var query = {_id: m1};

    fship.added = new Date();

    var updater = {$push: {}};
    updater.$push[pathName] = fship;

    return _update(query, updater, fship);
  }

  /**
   * Generate a function to return one side of a friendship between
   * two models
   *
   * @param Model the extending model
   * @param m1 the model or model _id being queried
   * @param m2 the model or model _id whose friendship is queried for
   *
   * @returns a function which will call back with an friendship for m2 on m1
   * @api private
   */
  var friendshipBetween = function (m1, m2) {
    var proj = {};
    proj[pathName] = {$elemMatch: {_id: m2}};

    return function (done) {
      this.findById(m1, proj, function (err, doc) {
        if (err) return done(err);
        if (!doc) return done("friendship not found");
        done(null, doc[pathName][0]);
      });
    }
  }

  return function friends (schema) {
    // add the embedded friends
    schema.add(fields);

    // Index the friends array with a multikey index on _id.  Further indexing
    // on status is probably unnecessary, as all queries will hit _id and
    // this will already limit them to the friends on an individual user.
    if (options.index) {
      var index = {};
      index[pathName+"._id"] = 1;
      schema.index(index, {name: "friendsplugin"});
    }

    /**
     * Send friend request from "friender" to "friend".  Calls back with
     * an object containing the two resulting friend objects
     *
     * On first request, will result in a "requested" friendship for the
     * first party and a "pending" friendship for the second.
     *
     * Reciprocating the request (friendee back to friender) will accept it.
     *
     * @param {Model} m1 the "friender" model doc or _id doing the reqesting
     * @param {Model} m2 the "friendee" model doc or _id being requested
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.requestFriend = function (m1, m2, cb) {
      var steps = {}
        , Model = this

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      async.auto({
        m1: friendshipBetween(m1, m2).bind(this),
        m2: friendshipBetween(m2, m1).bind(this)
      }, function (err, o) {
        if (err) return cb(err);

        var hasfship = !!o.m1
          , fship = o.m1 || {_id: m2}
          , oid = fship._id
          , ostatus = fship.status

        // m2 has no friendship, add a new pending friendship, mark
        // m1's status as requested
        if (!o.m2) {
          fship.status = Status.Requested;
          steps.friend = pushFriendship(m2, m1, {
            _id: m1,
            status: Status.Pending
          }).bind(Model);
        } else {
          switch (o.m2.status) {

          // m2 status is still pending, no update
          case Status.Pending:
            fship.status = Status.Requested;
            break;

          // m2 status is accepted already, no update
          case Status.Accepted:
            fship.status = Status.Accepted;
            break;

          // m2 already requested m1, mark BOTH friendships as accepted
          case Status.Requested:
            fship.status = Status.Accepted;
            steps.friend = updateFriendship(m2, m1, {
              status: Status.Accepted
            }).bind(Model);
            break;
          }
        }

        steps.friend || (steps.friend = function (done) {
          // If no update was necessary, send the friendship back directly
          done(null, o.m2)
        });

        if (hasfship && ostatus == fship.status && oid.equals(fship._id)) {
          steps.friender = function (done) {
            done(null, fship)
          }
        } else if (hasfship) {
          steps.friender = updateFriendship(m1, m2, fship, cb).bind(Model)
        } else {
          steps.friender = pushFriendship(m1, m2, fship, cb).bind(Model)
        }

        async.parallel(steps, cb);
      });
    };

    /**
     * Create a friend request
     *
     * @param {Model} friend The potential friend being requested
     * @param {Function} cb Callback once friendship is created
     */
    schema.methods.requestFriend = function (friend, cb) {
      return this.constructor.requestFriend(this, friend, cb);
    };

    /**
     * Get all friends of a model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Model} model a model doc or _id of a model doc to find friends for
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.getFriends = function (model) {
      var Model = this
        , op = 'find'

      resolveArgs(arguments, function (conditions, fields, options, cb) {
        // assume model is either a Model, or an _id
        model = model._id || model;

        conditions[pathName+"._id"] = model;

        // "accepted" status is mirrored on both sides, but if querying
        // for "pending" or "requested" friends, the query must be reversed
        if (conditions[pathName+".status"]) {
          switch (conditions[pathName+".status"]) {
          case Status.Pending:
            conditions[pathName+".status"] = Status.Requested;
            break;
          case Status.Requested:
            conditions[pathName+".status"] = Status.Pending;
            break;
          }
        }

        async.parallel({
          // query remote friends based on the arguments
          friends: function (done) {
            Model[op](conditions, fields, options, done);
          },

          // Reduce local friend docs to map which will be populated and
          // then combined with the queried remote friends to generate the
          // resulting array
          locals: function (done) {
            var select = {};
            select[pathName] = 1;

            Model.findOne({_id: model}, select, function (err, doc) {
              if (err) return done(err);
              if (!doc) return done(null, []);

              done(null, _.reduce(doc[pathName], function (o, friend) {
                o[friend._id] = friend.toObject();
                return o;
              }, {}));
            });
          }
        }, function (err, res) {
          if (err) return cb(err);

          // wrap the results with the status
          for (var i=0, friendship; i<res.friends.length; i++) {
            friendship = res.locals[res.friends[i]._id];
            friendship.friend = res.friends[i];
            res.friends[i] = friendship;
          }

          cb(null, res.friends);
        });
      });
    };

    /**
     * Get all friends of this model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.getFriends = function (conditions, fields, options, cb) {
      return this.constructor.getFriends(this, conditions, fields, options, cb);
    };

    var getByStatus = function (status) {
      return function (model) {
        resolveArgs(arguments, function (conditions, fields, options, cb) {
          conditions[pathName + ".status"] = status;
          this.getFriends(model, conditions, fields, options, cb);
        }.bind(this));
      }
    }

    /**
     * Get all pending friends for a given model
     */
    schema.statics.getPendingFriends = getByStatus(Status.Pending);

    /**
     * Get all pending friends for this model
     */
    schema.methods.getPendingFriends = function (conditions, fields, options, cb) {
      return this.constructor.getPendingFriends(this, conditions, fields, options, cb);
    }

    /**
     * Get all accepted friends for a given model
     */
    schema.statics.getAcceptedFriends = getByStatus(Status.Accepted);

    /**
     * Get all accepted friends for this model
     */
    schema.methods.getAcceptedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getAcceptedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Get all requested friends for a given model
     */
    schema.statics.getRequestedFriends = getByStatus(Status.Requested);

    /**
     * Get all requested friends for this model
     */
    schema.methods.getRequestedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getRequestedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Remove a friendship between two friends
     *
     * @param {Model} m1 the first model doc
     * @param {Model} m2 the second model doc
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.removeFriend = function (m1, m2, cb) {
      var collection = this.collection

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      var pull = function (m1, m2) {
        var update = {$pull: {}}
        update.$pull[pathName] = {_id: m2};
        return function (done) {
          collection.update({_id: m1}, update, done);
        }
      }

      async.parallel([pull(m1, m2), pull(m2, m1)], cb);
    };

    /**
     * Remove a friend of this model
     *
     * @param {Model} friend doc to remove
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.removeFriend = function (model, cb) {
      return this.constructor.removeFriend(this, model, cb);
    };
  };
}

/**
 * Expects a signature like Mongoose's `Model.find`, but for the first
 * arg.  Goes through the argument same disambigation that `Model.find`
 * does then calls a callback with the results.  This keeps all this
 * business in one place and lets other functions hook into the options
 * of `getFriends` without copy/pasting this Mongoose logic.
 *
 * @api private
 */
function resolveArgs (args, next) {
  var conditions = args[1]
    , fields = args[2]
    , options = args[3]
    , callback = args[4]

  if ('function' == typeof conditions) {
    callback = conditions;
    conditions = {};
    fields = null;
    options = null;
  } else if ('function' == typeof fields) {
    callback = fields;
    fields = null;
    options = null;
  } else if ('function' == typeof options) {
    callback = options;
    options = null;
  }
  next(conditions, fields, options, callback);
}

/**
 * simple no op function for clarity
 * @api private
 */
function noop() {}
<|endoftext|>


<commit_before>var _ = require('underscore'),
    path = require('path');
const corePlugins = [
    'styles-output', 'scripts-output', 'static-output',
    'scope', 'router', 'template', 'inline-styles', 'stylus', 'module-map', 'package-config', 'update-externals',
    'inline-styles-resources', 'styles', 'scripts', 'static'];
var fileUtils = require("./fileUtil");

var globalPlugins = {};

exports.plugin = function(name, plugin) {
  globalPlugins[name] = plugin;
  plugin.id = name;
};

exports.plugin('module-map', require('./plugins/module-map'));
exports.plugin('package-config', require('./plugins/package-config'));
exports.plugin('router', require('./plugins/router'));
exports.plugin('scope', require('./plugins/scope'));
exports.plugin('stylus', require('./plugins/stylus'));
exports.plugin('inline-styles', require('./plugins/inline-styles'));
exports.plugin('inline-styles-resources', require('./plugins/inline-styles-resources'));
exports.plugin('update-externals', require('./plugins/update-externals'));
exports.plugin('template', require('./plugins/template'));
exports.plugin('styles', require('./plugins/styles.js'));
exports.plugin('scripts', require('./plugins/scripts.js'));
exports.plugin('static', require('./plugins/static.js'));
exports.plugin('styles-output', require('./plugins/styles-output.js'));
exports.plugin('scripts-output', require('./plugins/scripts-output.js'));
exports.plugin('static-output', require('./plugins/static-output.js'));

exports.create = function(options) {
  var plugins;
  var modes; // all registered modes
  var pluginModes; // map of modes and plugins scoped to the mode
  var modeAll; // plugins that are scoped to all modes

  function runPlugins(context, methodName, complete, failOver) {
    var len = 0;
    return (function next(complete) {
      var plugin = plugins[len];
      len++;
      if (plugin) {
        // if plugin shouldn't work with current mode, go to next
        if ((pluginModes[context.mode].indexOf(plugin) < 0) && modeAll.indexOf(plugin) < 0) {
          return next(complete);
        }

        var method = plugin[methodName];
        if (method) {
          return method.call(plugin, context, next, complete);
        } else {
          return next(complete);
        }
      } else {
        if (complete) {
          // async
          complete(undefined, failOver && failOver());
        } else {
          // sync
          return failOver && failOver();
        }
      }
    })(complete);
  }

  function registerPlugin(plugin) {
    var _plugin = globalPlugins[plugin] || plugin;

    var mode = _plugin.mode;
    if (mode) {
      if (_.isString(mode)) {
        mode = [mode];
      }
      _.each(mode, function(_mode) {
        if (mode === 'all') {
          // allow plugins to contribute new modes and participate in all modes
          modeAll.push(_plugin);
        } else {
          if (modes.indexOf(_mode) < 0) {
            modes.push(_mode);
            pluginModes[_mode] = [];
          }
          pluginModes[_mode].push(_plugin);
        }
      });
    } else {
      modeAll.push(_plugin);
    }
    plugins.push(_plugin);
    plugins.sort(function(a, b) {
      return (a.priority || 50) - (b.priority || 50);
    });
  }

  return {
    get: function(name) {
      return plugins.reduce(function(plugin, left) {
        return plugin.id === name ? plugin : left;
      });
    },
    use: function(plugin) {
      if (plugin.path || (_.isString(plugin) && !globalPlugins[plugin])) {
        var pluginPath = plugin.path || plugin;
        var options = plugin.options;
        try {
          plugin = require(pluginPath);
        } catch (e) {

          plugin = require(path.resolve(process.cwd(), fileUtils.lookupPath()) + '/node_modules/' + pluginPath);
        }
        if ('function' === typeof plugin) {
          plugin = plugin(options);
        }
      }
      registerPlugin(plugin);
    },

    initialize: function(config) {
      // reset
      plugins = [];
      modes = []; // all registered modes
      pluginModes = {}; // map of modes and plugins scoped to the mode
      modeAll = []; // plugins that are scoped to all modes

      // load the core plugins
      if (!options.ignoreCorePlugins) {
        corePlugins.forEach(registerPlugin);
      }

      var self = this;
      function plugin(plugins) {
        if (plugins) {
          plugins.forEach(self.use, self);
        }
      }

      // load command line plugins
      plugin(options.plugins);

      // load lumbar.json plugins
      plugin(config.attributes.plugins);
    },

    outputConfigs: function(context, complete) {
      runPlugins(context, 'outputConfigs', complete, function() {
        // Default to a one to one mapping for a given {platform, package, module, mode} combo
        return [ {} ];
      });
    },
    modeComplete: function(context, complete) {
      runPlugins(context, 'modeComplete', complete);
    },
    fileName: function(context, complete) {
      runPlugins(context, 'fileName', complete);
    },

    fileFilter: function(context) {
      return runPlugins(context, 'fileFilter');
    },
    moduleResources: function(context, complete) {
      runPlugins(context, 'moduleResources', complete, function() {
        var module = context.module;
        return (module[context.mode] || []).slice();
      });
    },
    resourceList: function(context, complete) {
      runPlugins(context, 'resourceList', complete, function() { return [context.resource]; });
    },

    file: function(context, complete) {
      runPlugins(context, 'file', complete);
    },
    module: function(context, complete) {
      runPlugins(context, 'module', complete);
    },
    resource: function(context, complete) {
      runPlugins(context, 'resource', complete, function() { return context.resource; });
    },
    modes: function() {
      return modes;
    }
  };
};
<commit_msg>include coffee script plugin<commit_after>var _ = require('underscore'),
    path = require('path');
const corePlugins = [
    'styles-output', 'scripts-output', 'static-output',
    'scope', 'router', 'template', 'inline-styles', 'stylus', 'module-map', 'package-config', 'update-externals',
    'inline-styles-resources', 'styles', 'scripts', 'static'];
var fileUtils = require("./fileUtil");

var globalPlugins = {};

exports.plugin = function(name, plugin) {
  globalPlugins[name] = plugin;
  plugin.id = name;
};

exports.plugin('module-map', require('./plugins/module-map'));
exports.plugin('package-config', require('./plugins/package-config'));
exports.plugin('router', require('./plugins/router'));
exports.plugin('scope', require('./plugins/scope'));
exports.plugin('stylus', require('./plugins/stylus'));
exports.plugin('coffee-script', require('./plugins/coffee-script'));
exports.plugin('inline-styles', require('./plugins/inline-styles'));
exports.plugin('inline-styles-resources', require('./plugins/inline-styles-resources'));
exports.plugin('update-externals', require('./plugins/update-externals'));
exports.plugin('template', require('./plugins/template'));
exports.plugin('styles', require('./plugins/styles.js'));
exports.plugin('scripts', require('./plugins/scripts.js'));
exports.plugin('static', require('./plugins/static.js'));
exports.plugin('styles-output', require('./plugins/styles-output.js'));
exports.plugin('scripts-output', require('./plugins/scripts-output.js'));
exports.plugin('static-output', require('./plugins/static-output.js'));

exports.create = function(options) {
  var plugins;
  var modes; // all registered modes
  var pluginModes; // map of modes and plugins scoped to the mode
  var modeAll; // plugins that are scoped to all modes

  function runPlugins(context, methodName, complete, failOver) {
    var len = 0;
    return (function next(complete) {
      var plugin = plugins[len];
      len++;
      if (plugin) {
        // if plugin shouldn't work with current mode, go to next
        if ((pluginModes[context.mode].indexOf(plugin) < 0) && modeAll.indexOf(plugin) < 0) {
          return next(complete);
        }

        var method = plugin[methodName];
        if (method) {
          return method.call(plugin, context, next, complete);
        } else {
          return next(complete);
        }
      } else {
        if (complete) {
          // async
          complete(undefined, failOver && failOver());
        } else {
          // sync
          return failOver && failOver();
        }
      }
    })(complete);
  }

  function registerPlugin(plugin) {
    var _plugin = globalPlugins[plugin] || plugin;

    var mode = _plugin.mode;
    if (mode) {
      if (_.isString(mode)) {
        mode = [mode];
      }
      _.each(mode, function(_mode) {
        if (mode === 'all') {
          // allow plugins to contribute new modes and participate in all modes
          modeAll.push(_plugin);
        } else {
          if (modes.indexOf(_mode) < 0) {
            modes.push(_mode);
            pluginModes[_mode] = [];
          }
          pluginModes[_mode].push(_plugin);
        }
      });
    } else {
      modeAll.push(_plugin);
    }
    plugins.push(_plugin);
    plugins.sort(function(a, b) {
      return (a.priority || 50) - (b.priority || 50);
    });
  }

  return {
    get: function(name) {
      return plugins.reduce(function(plugin, left) {
        return plugin.id === name ? plugin : left;
      });
    },
    use: function(plugin) {
      if (plugin.path || (_.isString(plugin) && !globalPlugins[plugin])) {
        var pluginPath = plugin.path || plugin;
        var options = plugin.options;
        try {
          plugin = require(pluginPath);
        } catch (e) {

          plugin = require(path.resolve(process.cwd(), fileUtils.lookupPath()) + '/node_modules/' + pluginPath);
        }
        if ('function' === typeof plugin) {
          plugin = plugin(options);
        }
      }
      registerPlugin(plugin);
    },

    initialize: function(config) {
      // reset
      plugins = [];
      modes = []; // all registered modes
      pluginModes = {}; // map of modes and plugins scoped to the mode
      modeAll = []; // plugins that are scoped to all modes

      // load the core plugins
      if (!options.ignoreCorePlugins) {
        corePlugins.forEach(registerPlugin);
      }

      var self = this;
      function plugin(plugins) {
        if (plugins) {
          plugins.forEach(self.use, self);
        }
      }

      // load command line plugins
      plugin(options.plugins);

      // load lumbar.json plugins
      plugin(config.attributes.plugins);
    },

    outputConfigs: function(context, complete) {
      runPlugins(context, 'outputConfigs', complete, function() {
        // Default to a one to one mapping for a given {platform, package, module, mode} combo
        return [ {} ];
      });
    },
    modeComplete: function(context, complete) {
      runPlugins(context, 'modeComplete', complete);
    },
    fileName: function(context, complete) {
      runPlugins(context, 'fileName', complete);
    },

    fileFilter: function(context) {
      return runPlugins(context, 'fileFilter');
    },
    moduleResources: function(context, complete) {
      runPlugins(context, 'moduleResources', complete, function() {
        var module = context.module;
        return (module[context.mode] || []).slice();
      });
    },
    resourceList: function(context, complete) {
      runPlugins(context, 'resourceList', complete, function() { return [context.resource]; });
    },

    file: function(context, complete) {
      runPlugins(context, 'file', complete);
    },
    module: function(context, complete) {
      runPlugins(context, 'module', complete);
    },
    resource: function(context, complete) {
      runPlugins(context, 'resource', complete, function() { return context.resource; });
    },
    modes: function() {
      return modes;
    }
  };
};
<|endoftext|>


<commit_before>'use strict';
var cp = require('child_process');
var fs = require('fs');
var path = require('path');

var _ = require('underscore');
var request = require('request');

var h = require('./helper');
var file = require('./file');
var cache = require('./cache');
var config = require('./config');
var log = require('./log');
var Queue = require('./queue');

function Plugin(id, name, ver, desc, deps) {
  this.id = id;
  this.name = name;
  this.ver = ver || 'default';
  this.desc = desc || '';

  this.enabled = true;
  this.deleted = false;
  this.missing = (ver === 'missing');

  // only need deps for current platform
  this.deps = _.chain(deps || [])
    .filter(x => ! x.includes(':') || x.includes(':' + process.platform))
    .map(x => x.split(':')[0])
    .value();
}

Plugin.prototype.init = function() {
  this.config = config.plugins[this.name] || {};
  this.next = null;
};

Plugin.prototype.setNext = function(next) {
  Object.setPrototypeOf(this, next);
  this.next = next;
};

Plugin.prototype.delete = function() {
  if (!this.missing) {
    try {
      const fullpath = file.pluginFile(this.file);
      file.rm(fullpath);
    } catch(e) {
      return log.error(e.message);
    }
  }
  this.deleted = true;
};

Plugin.prototype.save = function() {
  const stats = cache.get(h.KEYS.plugins) || {};

  if (this.deleted) delete stats[this.name];
  else if (this.missing) return;
  else stats[this.name] = this.enabled;

  cache.set(h.KEYS.plugins, stats);
};

Plugin.prototype.install = function(cb) {
  if (this.deps.length === 0) return cb();

  const cmd = 'npm install --save ' + this.deps.join(' ');
  log.debug(cmd);
  const spin = h.spin(cmd);
  cp.exec(cmd, {cwd: file.codeDir()}, function(e) {
    spin.stop();
    return cb(e);
  });
};

Plugin.prototype.help = function() {};

Plugin.plugins = [];

Plugin.init = function(head) {
  log.trace('initializing all plugins');
  head = head || require('./core');

  const stats = cache.get(h.KEYS.plugins) || {};

  // 1. find installed plugins
  let installed = [];
  for (let f of file.listCodeDir('lib/plugins')) {
    const p = f.data;
    if (!p) continue;
    log.trace('found plugin: ' + p.name + '=' + p.ver);

    p.file = f.file;
    p.enabled = stats[p.name];
    // not saved before? enable it by default
    if (!(p.name in stats)) {
      log.trace('new plugin, enable by default');
      p.enabled = true;
    }
    installed.push(p);
  }
  // the one with bigger `id` comes first
  installed = _.sortBy(installed, x => -x.id);

  // 2. init all in reversed order
  for (let i = installed.length - 1; i >= 0; --i) {
    const p = installed[i];
    if (p.enabled) {
      p.init();
      log.trace('inited plugin: ' + p.name);
    } else {
      log.trace('skipped plugin: ' + p.name);
    }
  }

  // 3. chain together
  const plugins = installed.filter(x => x.enabled);
  let last = head;
  for (let p of plugins) {
    last.setNext(p);
    last = p;
  }

  // 4. check missing plugins
  const missings = [];
  for (let k of _.keys(stats)) {
    if (installed.find(x => x.name === k)) continue;
    const p = new Plugin(-1, k, 'missing');
    p.enabled = stats[k];
    missings.push(p);
    log.trace('missing plugin:' + p.name);
  }

  Plugin.plugins = installed.concat(missings);
  return missings.length === 0;
};

Plugin.copy = function(src, cb) {
  // FIXME: remove local file support?
  if (path.extname(src) !== '.js') {
    src = config.sys.urls.plugin.replace('$name', src);
  }
  const dst = file.pluginFile(src);

  const srcstream = src.startsWith('https://') ? request(src) : fs.createReadStream(src);
  srcstream.on('response', function(resp) {
    if (resp.statusCode !== 200)
      srcstream.emit('error', 'HTTP Error: ' + resp.statusCode);
  });
  srcstream.on('error', function(e) {
    spin.stop();
    file.rm(dst);
    return cb(e);
  });

  const dststream = fs.createWriteStream(dst);
  dststream.on('close', function() {
    spin.stop();
    return cb(null, dst);
  });

  log.debug('copying from ' + src);
  const spin = h.spin('Downloading ' + src);
  srcstream.pipe(dststream);
};

Plugin.install = function(name, cb) {
  Plugin.copy(name, function(e, fullpath) {
    if (e) return cb(e);
    log.debug('copied to ' + fullpath);

    const p = require(fullpath);
    p.file = path.basename(fullpath);
    p.install(function() {
      return cb(null, p);
    });
  });
};

Plugin.installMissings = function(cb) {
  function doTask(plugin, queue, cb) {
    Plugin.install(plugin.name, function(e, p) {
      if (!e) {
        p.enabled = plugin.enabled;
        p.save();
        p.help();
      }
      return cb(e, p);
    });
  }

  const missings = Plugin.plugins.filter(x => x.missing);
  if (missings.length === 0) return cb();

  log.warn('Installing missing plugins, might take a while ...');
  const q = new Queue(missings, {}, doTask);
  q.run(1, cb);
};

Plugin.save = function() {
  for (let p of this.plugins) p.save();
};

module.exports = Plugin;
<commit_msg>[plugin] always enable builtin plugin by default.<commit_after>'use strict';
var cp = require('child_process');
var fs = require('fs');
var path = require('path');

var _ = require('underscore');
var request = require('request');

var h = require('./helper');
var file = require('./file');
var cache = require('./cache');
var config = require('./config');
var log = require('./log');
var Queue = require('./queue');

function Plugin(id, name, ver, desc, deps) {
  this.id = id;
  this.name = name;
  this.ver = ver || 'default';
  this.desc = desc || '';

  this.enabled = true;
  this.deleted = false;
  this.missing = (this.ver === 'missing');
  this.builtin = (this.ver === 'default');

  // only need deps for current platform
  this.deps = _.chain(deps || [])
    .filter(x => ! x.includes(':') || x.includes(':' + process.platform))
    .map(x => x.split(':')[0])
    .value();
}

Plugin.prototype.init = function() {
  this.config = config.plugins[this.name] || {};
  this.next = null;
};

Plugin.prototype.setNext = function(next) {
  Object.setPrototypeOf(this, next);
  this.next = next;
};

Plugin.prototype.delete = function() {
  if (!this.missing) {
    try {
      const fullpath = file.pluginFile(this.file);
      file.rm(fullpath);
    } catch(e) {
      return log.error(e.message);
    }
  }
  this.deleted = true;
};

Plugin.prototype.save = function() {
  const stats = cache.get(h.KEYS.plugins) || {};

  if (this.deleted) delete stats[this.name];
  else if (this.missing) return;
  else stats[this.name] = this.enabled;

  cache.set(h.KEYS.plugins, stats);
};

Plugin.prototype.install = function(cb) {
  if (this.deps.length === 0) return cb();

  const cmd = 'npm install --save ' + this.deps.join(' ');
  log.debug(cmd);
  const spin = h.spin(cmd);
  cp.exec(cmd, {cwd: file.codeDir()}, function(e) {
    spin.stop();
    return cb(e);
  });
};

Plugin.prototype.help = function() {};

Plugin.plugins = [];

Plugin.init = function(head) {
  log.trace('initializing all plugins');
  head = head || require('./core');

  const stats = cache.get(h.KEYS.plugins) || {};

  // 1. find installed plugins
  let installed = [];
  for (let f of file.listCodeDir('lib/plugins')) {
    const p = f.data;
    if (!p) continue;
    log.trace('found plugin: ' + p.name + '=' + p.ver);

    p.file = f.file;
    p.enabled = stats[p.name];

    if (!(p.name in stats)) {
      if (p.builtin) {
        log.trace('new builtin plugin, enable by default');
        p.enabled = true;
      } else {
        log.trace('new 3rd party plugin, disable by default');
        p.enabled = false;
      }
    }
    installed.push(p);
  }
  // the one with bigger `id` comes first
  installed = _.sortBy(installed, x => -x.id);

  // 2. init all in reversed order
  for (let i = installed.length - 1; i >= 0; --i) {
    const p = installed[i];
    if (p.enabled) {
      p.init();
      log.trace('inited plugin: ' + p.name);
    } else {
      log.trace('skipped plugin: ' + p.name);
    }
  }

  // 3. chain together
  const plugins = installed.filter(x => x.enabled);
  let last = head;
  for (let p of plugins) {
    last.setNext(p);
    last = p;
  }

  // 4. check missing plugins
  const missings = [];
  for (let k of _.keys(stats)) {
    if (installed.find(x => x.name === k)) continue;
    const p = new Plugin(-1, k, 'missing');
    p.enabled = stats[k];
    missings.push(p);
    log.trace('missing plugin:' + p.name);
  }

  Plugin.plugins = installed.concat(missings);
  return missings.length === 0;
};

Plugin.copy = function(src, cb) {
  // FIXME: remove local file support?
  if (path.extname(src) !== '.js') {
    src = config.sys.urls.plugin.replace('$name', src);
  }
  const dst = file.pluginFile(src);

  const srcstream = src.startsWith('https://') ? request(src) : fs.createReadStream(src);
  srcstream.on('response', function(resp) {
    if (resp.statusCode !== 200)
      srcstream.emit('error', 'HTTP Error: ' + resp.statusCode);
  });
  srcstream.on('error', function(e) {
    spin.stop();
    file.rm(dst);
    return cb(e);
  });

  const dststream = fs.createWriteStream(dst);
  dststream.on('close', function() {
    spin.stop();
    return cb(null, dst);
  });

  log.debug('copying from ' + src);
  const spin = h.spin('Downloading ' + src);
  srcstream.pipe(dststream);
};

Plugin.install = function(name, cb) {
  Plugin.copy(name, function(e, fullpath) {
    if (e) return cb(e);
    log.debug('copied to ' + fullpath);

    const p = require(fullpath);
    p.file = path.basename(fullpath);
    p.install(function() {
      return cb(null, p);
    });
  });
};

Plugin.installMissings = function(cb) {
  function doTask(plugin, queue, cb) {
    Plugin.install(plugin.name, function(e, p) {
      if (!e) {
        p.enabled = plugin.enabled;
        p.save();
        p.help();
      }
      return cb(e, p);
    });
  }

  const missings = Plugin.plugins.filter(x => x.missing);
  if (missings.length === 0) return cb();

  log.warn('Installing missing plugins, might take a while ...');
  const q = new Queue(missings, {}, doTask);
  q.run(1, cb);
};

Plugin.save = function() {
  for (let p of this.plugins) p.save();
};

module.exports = Plugin;
<|endoftext|>


<commit_before>/**
 * Auth cantina plugin.
 */

var connect = require('connect')
  , utils = require('cantina-utils')

// Expose this service's package info.
utils.pkginfo(module);

// The plugin is being attached to an app.
exports.attach = function(options) {
  options = options || {};

  this.utils.defaults(options, {
    secret: 'keyboard cat',
    key: 'connect.sid'
  });

  this.session = {secret: options.secret, key: options.key};

  if (options.redis) {
    var RedisStore = require('connect-redis')(connect);
    this.session.store = new RedisStore(typeof options.redis == 'object' ? options.redis : {});
  }
  else {
    this.session.store = new connect.session.MemoryStore();
  }

  this.middleware([
    connect.cookieParser(this.session.secret),
    connect.session(this.session)
  ]);

  this.utils.defaults(this.session, require('./socket')(this.session));
};
<commit_msg>more fumbling<commit_after>/**
 * Auth cantina plugin.
 */

var connect = require('connect')
  , utils = require('cantina-utils')

// Expose this service's package info.
utils.pkginfo(module);

// The plugin is being attached to an app.
exports.attach = function(options) {
  var app = this;
  options = options || {};

  this.utils.defaults(options, {
    secret: 'keyboard cat',
    key: 'connect.sid'
  });

  this.session = {secret: options.secret, key: options.key};

  if (options.redis) {
    var RedisStore = require('connect-redis')(connect);
    this.session.store = new RedisStore(typeof options.redis == 'object' ? options.redis : {});
  }
  else {
    this.session.store = new connect.session.MemoryStore();
  }

  this.middleware([
    connect.cookieParser(this.session.secret),
    connect.session(this.session)
  ]);

  var socketLib = require('./socket');
  Object.keys(socketLib).forEach(function(method) {
    app.session[method] = socketLib[method](app.session);
  });
};
<|endoftext|>


<commit_before>var fs = require('fs');
var gettext = require('node-gettext');
var IntlMessageFormat = require('intl-messageformat');
var plurals = require('node-gettext/lib/plurals');

var gt = new gettext();

module.exports = {
    /*
        Given a po string (msgid or msgstr) that contains python-formatted
        variables, this will convert the string into an ICU-formatted string.
    */
    pythonToICU: function (msg) {
        msg = msg.replace(/%\([a-zA-Z0-9_.|]+\)s/g, function (variable_name) {
            var name = variable_name.substring(2, (variable_name.length - 2));
            return '{' + name + '}';
        });
        msg = msg.replace(/%\([a-zA-Z0-9_.|]+\)d/g, function (variable_name) {
            var name = variable_name.substring(2, (variable_name.length - 2));
            return '{' + name + ', number}';
        });
        msg = msg.replace(/%(d|i)/g, '{digit, number}');
        msg = msg.replace(/%s/g, '{string}');
        return msg;
    },

    /*
        Converts a python po-formatted plural msg into an icu-formatted plural.
        Takes as args:
            msgs: list of strings for each plural form
            pluralForms: list of icu plural forms used by this language
                choices are: ['zero', 'one', 'two', 'few', 'many', 'other']
    */
    pythonPluralToICU: function (msgs, pluralForms) {
        var msgKey = 'digit';
        var hasVariableName = false;
        for (var i=0; i<msgs.length; i++) {
            if (msgs[i].match(/%\([a-zA-Z0-9_.|]+\)d/g) !== null) {
                var match = msgs[i].match(/%\([a-zA-Z0-9_.|]+\)d/g)[0];
                msgKey = match.substring(2, (match.length - 2));
                hasVariableName = true;
                break;
            }
        }

        var icuMsg = '{' + msgKey + ', plural,\n';
        pluralForms.forEach(function (plural) {
            var msg = msgs[plural.poPlural];
            if (hasVariableName) {
                msg = msg.replace(/%\([a-zA-Z0-9_.|]+\)d/g, '{' + msgKey + '}');
            } else {
                msg = msg.replace(/%(d|i)/g, '{' + msgKey + '}');
            }
            icuMsg = icuMsg + '    ' + plural.icuPlural + ' {' + msg + '}\n';
        });
        icuMsg = icuMsg + '}';
        return icuMsg;
    },

    /*
        Takes as input a msg object, as returned by the gettext-parser module.
        It contains a msgid, msgstr, and comments.

        Input language format is searched for first within the flag comment of the
        msg chunk, then, in the absence of a flag, within any options passed to
        the method.
    */
    msgToICU: function (msg, options) {
        var format = options.poFormat || '';
        if (msg.comments && msg.comments.flag) {
            format = msg.comments.flag;
        }
        var msgid = msg.msgid;
        var msgstr = msg.msgstr[0];

        var icuId = '';
        var icuStr = '';

        if (format.match(/python-format/g) !== null) {
            icuId = this.pythonToICU(msgid);
            icuStr = this.pythonToICU(msgstr);
        }
        else if (format.match(/c-format/g) !== null) {
            icuId = this.pythonToICU(msgid);
            icuStr = this.pythonToICU(msgstr);
        } else {
            icuId = msgid;
            icuStr = msgstr;
        }

        var icuObject = {'icuId': icuId, 'icuStr': icuStr};
        return icuObject;
    },

    /*
        For the given language, get the plurals used in gettext, then find their
        equivalent label in ICU.
    */
    getPluralsForDomain: function (domain) {
        var pluralForms = [];
        var normalizedDomain = gt._normalizeDomain(domain, true);
        var poPluralsInfo = plurals[normalizedDomain];
        var icuPluralsFunction = IntlMessageFormat.prototype._findPluralRuleFunction(normalizedDomain);

        poPluralsInfo.examples.forEach(function (plural) {
            pluralForms.push({
                icuPlural: icuPluralsFunction(plural.sample),
                poPlural: plural.plural});
        });
        return pluralForms;
    },

    /*
        Handles conversion of plural forms. Only difference between this and
        `pythonToICU` is that this also takes a domain argument, and an optional
        source domain argument (i.e. source language code).
    */
    pluralMsgToICU: function (msg, domain, options) {
        var format = options.poFormat || '';
        var source = options.sourceDomain || 'en';
        if (msg.comments && msg.comments.flag) {
            format = msg.comments.flag;
        }
        var msgids = [msg.msgid, msg.msgid_plural];
        var msgstr = msg.msgstr;

        var icuPluralForms = this.getPluralsForDomain(domain);
        var icuSourcePluralForms = this.getPluralsForDomain(source);
        var icuId = '';
        var icuStr = '';

        if (format.match(/python-format/g) !== null) {
            icuId = this.pythonPluralToICU(msgids, icuSourcePluralForms);
            icuStr = this.pythonPluralToICU(msgstr, icuPluralForms);
        }
        else if (format.match(/c-format/g) !== null) {
            icuId = this.pythonPluralToICU(msgids, icuSourcePluralForms);
            icuStr = this.pythonPluralToICU(msgstr, icuPluralForms);
        }

        var icuObject = {'icuId': icuId, 'icuStr': icuStr};
        return icuObject;
    },

    /*
        Parses a string representation of a po file into a JSON object asynchronously.
        The JSON object has the following structure:
            {
                [msgid in ICU format]: [msgstr in ICU format],
                .
                .
                .
            }
        returns the parsed JSON object.
    */
    poStringToICUSync: function (domain, poString) {
        gt.addTextdomain(domain, poString);
        var poDomain = gt.domains[domain];
        var language = poDomain.headers.language;
        var options = {};
        if (language && domain != language) {
            throw new Error('domain specified does not match language specified in po file.');
        }

        var icuObject = {};
        var self = this;
        Object.keys(poDomain['translations']['']).forEach(function (key) {
            var msg = poDomain['translations'][''][key];
            if (msg.msgid.length > 0) {
                var icuMsg = null;
                options.poFormat = msg.comments.flag;
                if (msg.msgid_plural !== undefined) {
                    icuMsg = self.pluralMsgToICU(msg, domain, options);
                } else {
                    icuMsg = self.msgToICU(msg, options);
                }
                icuObject[icuMsg['icuId']] = icuMsg['icuStr'];
            }
        });
        return icuObject;
    },

    /*
        Parses a string representation of a po file into a JSON object asynchronously.
        The JSON object has the following structure:
            {
                [msgid in ICU format]: [msgstr in ICU format],
                .
                .
                .
            }
        Callback takes two arguments – error and the json object
    */
    poStringToICUAsync: function (domain, poString, callback) {
        var icuObject = this.poStringToICUSync(domain, poString);
        callback(null, icuObject);
    },

    /*
        Reads in a file to a string, and parses it into an icu-formatted
        JSON object.
    */
    poFileToICUAsync: function (domain, path, callback) {
        var pofile = fs.readFileSync(path, 'utf8');
        var icuObject = this.poStringToICUSync(domain, pofile);
        callback(null, icuObject);
    },

    /*
        Reads in a file to a string, and parses it into an icu-formatted
        JSON object.
    */
    poFileToICUSync: function (domain, path) {
        var pofile = fs.readFileSync(path, 'utf8');
        return this.poStringToICUSync(domain, pofile);
    }
};
<commit_msg>Clean extra whitespace in po text<commit_after>var fs = require('fs');
var gettext = require('node-gettext');
var IntlMessageFormat = require('intl-messageformat');
var plurals = require('node-gettext/lib/plurals');

var gt = new gettext();

module.exports = {
    /**
     * @param  {A po msg string}
     * @return {the po msg string with all arbitrary whitespace turned into one space}
     */
    cleanWhiteSpace: function (msg) {
        return msg.replace(/\s+/g, ' ');
    },

    /*
        Given a po string (msgid or msgstr) that contains python-formatted
        variables, this will convert the string into an ICU-formatted string.
    */
    pythonToICU: function (msg) {
        msg = this.cleanWhiteSpace(msg);
        msg = msg.replace(/%\([a-zA-Z0-9_.|]+\)s/g, function (variable_name) {
            var name = variable_name.substring(2, (variable_name.length - 2));
            return '{' + name + '}';
        });
        msg = msg.replace(/%\([a-zA-Z0-9_.|]+\)d/g, function (variable_name) {
            var name = variable_name.substring(2, (variable_name.length - 2));
            return '{' + name + ', number}';
        });
        msg = msg.replace(/%(d|i)/g, '{digit, number}');
        msg = msg.replace(/%s/g, '{string}');
        return msg;
    },

    /*
        Converts a python po-formatted plural msg into an icu-formatted plural.
        Takes as args:
            msgs: list of strings for each plural form
            pluralForms: list of icu plural forms used by this language
                choices are: ['zero', 'one', 'two', 'few', 'many', 'other']
    */
    pythonPluralToICU: function (msgs, pluralForms) {
        var msgKey = 'digit';
        var hasVariableName = false;
        for (var i=0; i<msgs.length; i++) {
            if (msgs[i].match(/%\([a-zA-Z0-9_.|]+\)d/g) !== null) {
                var match = msgs[i].match(/%\([a-zA-Z0-9_.|]+\)d/g)[0];
                msgKey = match.substring(2, (match.length - 2));
                hasVariableName = true;
                break;
            }
        }

        var icuMsg = '{' + msgKey + ', plural,\n';
        var self = this;
        pluralForms.forEach(function (plural) {
            var msg = msgs[plural.poPlural];
            msg = self.cleanWhiteSpace(msg);
            if (hasVariableName) {
                msg = msg.replace(/%\([a-zA-Z0-9_.|]+\)d/g, '{' + msgKey + '}');
            } else {
                msg = msg.replace(/%(d|i)/g, '{' + msgKey + '}');
            }
            icuMsg = icuMsg + '    ' + plural.icuPlural + ' {' + msg + '}\n';
        });
        icuMsg = icuMsg + '}';
        return icuMsg;
    },

    /*
        Takes as input a msg object, as returned by the gettext-parser module.
        It contains a msgid, msgstr, and comments.

        Input language format is searched for first within the flag comment of the
        msg chunk, then, in the absence of a flag, within any options passed to
        the method.
    */
    msgToICU: function (msg, options) {
        var format = options.poFormat || '';
        if (msg.comments && msg.comments.flag) {
            format = msg.comments.flag;
        }
        var msgid = msg.msgid;
        var msgstr = msg.msgstr[0];

        var icuId = '';
        var icuStr = '';

        if (format.match(/python-format/g) !== null) {
            icuId = this.pythonToICU(msgid);
            icuStr = this.pythonToICU(msgstr);
        }
        else if (format.match(/c-format/g) !== null) {
            icuId = this.pythonToICU(msgid);
            icuStr = this.pythonToICU(msgstr);
        } else {
            icuId = msgid;
            icuStr = msgstr;
        }

        var icuObject = {'icuId': icuId, 'icuStr': icuStr};
        return icuObject;
    },

    /*
        For the given language, get the plurals used in gettext, then find their
        equivalent label in ICU.
    */
    getPluralsForDomain: function (domain) {
        var pluralForms = [];
        var normalizedDomain = gt._normalizeDomain(domain, true);
        var poPluralsInfo = plurals[normalizedDomain];
        var icuPluralsFunction = IntlMessageFormat.prototype._findPluralRuleFunction(normalizedDomain);

        poPluralsInfo.examples.forEach(function (plural) {
            pluralForms.push({
                icuPlural: icuPluralsFunction(plural.sample),
                poPlural: plural.plural});
        });
        return pluralForms;
    },

    /*
        Handles conversion of plural forms. Only difference between this and
        `pythonToICU` is that this also takes a domain argument, and an optional
        source domain argument (i.e. source language code).
    */
    pluralMsgToICU: function (msg, domain, options) {
        var format = options.poFormat || '';
        var source = options.sourceDomain || 'en';
        if (msg.comments && msg.comments.flag) {
            format = msg.comments.flag;
        }
        var msgids = [msg.msgid, msg.msgid_plural];
        var msgstr = msg.msgstr;

        var icuPluralForms = this.getPluralsForDomain(domain);
        var icuSourcePluralForms = this.getPluralsForDomain(source);
        var icuId = '';
        var icuStr = '';

        if (format.match(/python-format/g) !== null) {
            icuId = this.pythonPluralToICU(msgids, icuSourcePluralForms);
            icuStr = this.pythonPluralToICU(msgstr, icuPluralForms);
        }
        else if (format.match(/c-format/g) !== null) {
            icuId = this.pythonPluralToICU(msgids, icuSourcePluralForms);
            icuStr = this.pythonPluralToICU(msgstr, icuPluralForms);
        }

        var icuObject = {'icuId': icuId, 'icuStr': icuStr};
        return icuObject;
    },

    /*
        Parses a string representation of a po file into a JSON object asynchronously.
        The JSON object has the following structure:
            {
                [msgid in ICU format]: [msgstr in ICU format],
                .
                .
                .
            }
        returns the parsed JSON object.
    */
    poStringToICUSync: function (domain, poString) {
        gt.addTextdomain(domain, poString);
        var poDomain = gt.domains[domain];
        var language = poDomain.headers.language;
        var options = {};
        if (language && domain != language) {
            throw new Error('domain specified does not match language specified in po file.');
        }

        var icuObject = {};
        var self = this;
        Object.keys(poDomain['translations']['']).forEach(function (key) {
            var msg = poDomain['translations'][''][key];
            if (msg.msgid.length > 0) {
                var icuMsg = null;
                options.poFormat = msg.comments.flag;
                if (msg.msgid_plural !== undefined) {
                    icuMsg = self.pluralMsgToICU(msg, domain, options);
                } else {
                    icuMsg = self.msgToICU(msg, options);
                }
                icuObject[icuMsg['icuId']] = icuMsg['icuStr'];
            }
        });
        return icuObject;
    },

    /*
        Parses a string representation of a po file into a JSON object asynchronously.
        The JSON object has the following structure:
            {
                [msgid in ICU format]: [msgstr in ICU format],
                .
                .
                .
            }
        Callback takes two arguments – error and the json object
    */
    poStringToICUAsync: function (domain, poString, callback) {
        var icuObject = this.poStringToICUSync(domain, poString);
        callback(null, icuObject);
    },

    /*
        Reads in a file to a string, and parses it into an icu-formatted
        JSON object.
    */
    poFileToICUAsync: function (domain, path, callback) {
        var pofile = fs.readFileSync(path, 'utf8');
        var icuObject = this.poStringToICUSync(domain, pofile);
        callback(null, icuObject);
    },

    /*
        Reads in a file to a string, and parses it into an icu-formatted
        JSON object.
    */
    poFileToICUSync: function (domain, path) {
        var pofile = fs.readFileSync(path, 'utf8');
        return this.poStringToICUSync(domain, pofile);
    }
};
<|endoftext|>


<commit_before>(function($) {

    var minHeight = 20;

    function addDragOntoDiv(height, top, extraClass, containerLeft, container) {
        var arrow = $('
').css({
            left: containerLeft
        });
        $('
').css({
            height: height + 'px',
            top: top + 'px'
        }).appendTo('body').on('dragenter.kem', allowDrop).on('dragover.kem', allowDrop).on('dragleave.kem', dragLeave).on('drop.kem', function(event) {
            var newDropTarget;
            var oldRank;
            console.log($(event.delegateTarget));
            if($(event.delegateTarget).hasClass('kem-drag-onto-top')) {
                console.log('Drop target top!');
                newDropTarget = $('
').prependTo(container);
                oldRank = $('.kem-dragging').closest('.kem-rank');
                $('.kem-dragging').detach().appendTo(newDropTarget).removeClass('kem-dragging');
                if (oldRank.find('.kem-item').length === 0) {
                    oldRank.remove();
                }
            }
        }).append(arrow);
    }

    function allowDrop(event) {
        if (event.originalEvent.dataTransfer && event.originalEvent.dataTransfer.types.indexOf("kem") > -1) {
            event.preventDefault();
            $(event.delegateTarget).addClass('kem-dragging-over');
        }
    }

    function dragLeave(event) {
        $(event.delegateTarget).removeClass('kem-dragging-over');
    }

    /**
     * Drag onto divs should be 3/5s dragging onto a rank and 2/5s dragging off of a rank
     */
    function createDragOntoDivs(container, containerLeft) {
        var i, heightBetween, topBetween = 0, top, height, fifthOfSection, cur, next, curMid, nextMid = null, arrow, elems;
        elems = container.find('.kem-rank');
        if(elems.length > 0) {
            heightBetween = Math.max($(elems[0]).offset().top, minHeight)
            addDragOntoDiv(heightBetween, topBetween, 'kem-drag-onto-top', containerLeft, container);
        }
        for(i = 0; i < elems.length - 1; i++) {
            top = topBetween + heightBetween;
            cur = $(elems[i]);
            next = $(elems[i+1]);
            curMid = nextMid || (cur.offset().top + cur.outerHeight()/2);
            nextMid = next.offset().top + next.outerHeight()/2;
            fifthOfSection = (nextMid - curMid)/5;
            topBetween = curMid + 1.5 * fifthOfSection;
            height = topBetween - top;
            heightBetween = 2 * fifthOfSection;
            addDragOntoDiv(height, top, '', containerLeft, container);
            addDragOntoDiv(heightBetween, topBetween, 'kem-drag-onto-between', containerLeft, container);
        }
        if(elems.length > 0) {
            top = topBetween + heightBetween;
            cur = $(elems[elems.length - 1])
            height = cur.outerHeight() - (top - cur.offset().top);
            heightBetween = Math.max($(window).height() - top - height, minHeight);
            topBetween = $(window).height() - heightBetween;
            addDragOntoDiv(height, top, '', containerLeft, container);
            arrow = $('
').css({
                left: containerLeft
            });
            $('
').css({
                top: topBetween + 'px',
                bottom: '0px'
            }).appendTo('body').on('dragenter.kem', allowDrop).on('dragover.kem', allowDrop).on('dragleave.kem', dragLeave).on('drop.kem', function(event) {
                var newDropTarget;
                var oldRank;
                newDropTarget = $('
').appendTo(elems.first().parent());
                oldRank = $('.kem-dragging').closest('.kem-rank');
                $('.kem-dragging').detach().appendTo(newDropTarget).removeClass('kem-dragging');
                if (oldRank.find('.kem-item').length === 0) {
                    oldRank.remove();
                }
            }).append(arrow);
        }
    }

    /**
     * An overarching goal of this library is for all state to be expressed in the DOM, so that it can both be used for
     * CSS and so the library itself can remain stateless
     */
    $.fn.rankem = function(options) {
        this.addClass('kem-container');
        if(options.ties === true) {
            this.addClass('kem-ties');
        }
        this.each(function () {
            var $elem = $(this),
                rank = $('
').appendTo(this);
            $elem.find('.kem-item').each(function() {
                $item = $(this);
                $item.appendTo(rank);
            }).attr('draggable', true).on('dragstart.kem', function(event){
                var dragging = $(event.currentTarget),
                    link = dragging.find('a'),
                    dt = event.originalEvent.dataTransfer;
                dt.effectAllowed = 'move';
                dt.setData("kem", ".kem-dragging");
                dt.setData('text/html', event.currentTarget.outerHTML);
                if(link.length > 0 && link.attr('href')) {
                    dt.setData("text/uri-list", link.attr('href'));
                }
                // Chrome does not allow you to change the DOM during a dragstart
                setTimeout(function() {
                    dragging.addClass('kem-dragging');
                    createDragOntoDivs($elem, $elem.offset().left);
                }, 1);
            }).on('dragend.kem', function(event) {
                $(event.currentTarget).removeClass('.kem-dragging');
                $('.kem-drag-onto').remove();
            });
        });
        //$('.kem-drag-onto').hide();
    }
}(jQuery))
<commit_msg>Fixed a tiny issue.<commit_after>(function($) {

    var minHeight = 20;

    function addDragOntoDiv(height, top, extraClass, containerLeft, container) {
        var arrow = $('
').css({
            left: containerLeft
        });
        $('
').css({
            height: height + 'px',
            top: top + 'px'
        }).appendTo('body').on('dragenter.kem', allowDrop).on('dragover.kem', allowDrop).on('dragleave.kem', dragLeave).on('drop.kem', function(event) {
            var newDropTarget;
            var oldRank;
            console.log($(event.delegateTarget));
            if($(event.delegateTarget).hasClass('kem-drag-onto-top')) {
                console.log('Drop target top!');
                newDropTarget = $('
').prependTo(container);
                oldRank = $('.kem-dragging').closest('.kem-rank');
                $('.kem-dragging').detach().appendTo(newDropTarget).removeClass('kem-dragging');
                if (oldRank.find('.kem-item').length === 0) {
                    oldRank.remove();
                }
            }
        }).append(arrow);
    }

    function allowDrop(event) {
        if (event.originalEvent.dataTransfer && event.originalEvent.dataTransfer.types.indexOf("kem") > -1) {
            event.preventDefault();
            $(event.delegateTarget).addClass('kem-dragging-over');
        }
    }

    function dragLeave(event) {
        $(event.delegateTarget).removeClass('kem-dragging-over');
    }

    /**
     * Drag onto divs should be 3/5s dragging onto a rank and 2/5s dragging off of a rank
     */
    function createDragOntoDivs(container, containerLeft) {
        var i, heightBetween, topBetween = 0, top, height, fifthOfSection, cur, next, curMid, nextMid = null, arrow, elems;
        elems = container.find('.kem-rank');
        if(elems.length > 0) {
            heightBetween = Math.max($(elems[0]).offset().top, minHeight)
            addDragOntoDiv(heightBetween, topBetween, 'kem-drag-onto-top', containerLeft, container);
        }
        for(i = 0; i < elems.length - 1; i++) {
            top = topBetween + heightBetween;
            cur = $(elems[i]);
            next = $(elems[i+1]);
            curMid = nextMid || (cur.offset().top + cur.outerHeight()/2);
            nextMid = next.offset().top + next.outerHeight()/2;
            fifthOfSection = (nextMid - curMid)/5;
            topBetween = curMid + 1.5 * fifthOfSection;
            height = topBetween - top;
            heightBetween = 2 * fifthOfSection;
            addDragOntoDiv(height, top, '', containerLeft, container);
            addDragOntoDiv(heightBetween, topBetween, 'kem-drag-onto-between', containerLeft, container);
        }
        if(elems.length > 0) {
            top = topBetween + heightBetween;
            cur = $(elems[elems.length - 1])
            height = cur.outerHeight() - (top - cur.offset().top);
            heightBetween = Math.max($(window).height() - top - height, minHeight);
            topBetween = $(window).height() - heightBetween;
            addDragOntoDiv(height, top, '', containerLeft, container);
            arrow = $('
').css({
                left: containerLeft
            });
            $('
').css({
                top: topBetween + 'px',
                bottom: '0px'
            }).appendTo('body').on('dragenter.kem', allowDrop).on('dragover.kem', allowDrop).on('dragleave.kem', dragLeave).on('drop.kem', function(event) {
                var newDropTarget;
                var oldRank;
                newDropTarget = $('
').appendTo(container);
                oldRank = $('.kem-dragging').closest('.kem-rank');
                $('.kem-dragging').detach().appendTo(newDropTarget).removeClass('kem-dragging');
                if (oldRank.find('.kem-item').length === 0) {
                    oldRank.remove();
                }
            }).append(arrow);
        }
    }

    /**
     * An overarching goal of this library is for all state to be expressed in the DOM, so that it can both be used for
     * CSS and so the library itself can remain stateless
     */
    $.fn.rankem = function(options) {
        this.addClass('kem-container');
        if(options.ties === true) {
            this.addClass('kem-ties');
        }
        this.each(function () {
            var $elem = $(this),
                rank = $('
').appendTo(this);
            $elem.find('.kem-item').each(function() {
                $item = $(this);
                $item.appendTo(rank);
            }).attr('draggable', true).on('dragstart.kem', function(event){
                var dragging = $(event.currentTarget),
                    link = dragging.find('a'),
                    dt = event.originalEvent.dataTransfer;
                dt.effectAllowed = 'move';
                dt.setData("kem", ".kem-dragging");
                dt.setData('text/html', event.currentTarget.outerHTML);
                if(link.length > 0 && link.attr('href')) {
                    dt.setData("text/uri-list", link.attr('href'));
                }
                // Chrome does not allow you to change the DOM during a dragstart
                setTimeout(function() {
                    dragging.addClass('kem-dragging');
                    createDragOntoDivs($elem, $elem.offset().left);
                }, 1);
            }).on('dragend.kem', function(event) {
                $(event.currentTarget).removeClass('.kem-dragging');
                $('.kem-drag-onto').remove();
            });
        });
        //$('.kem-drag-onto').hide();
    }
}(jQuery))
<|endoftext|>


<commit_before>'use strict';

const ERROR = 'Too deep traverse. Consider reduce scripts deepness.';

let currify = require('currify');

let getBody = require('./get-body');
let cliParse = require('./cli-parse');
let replace = require('./replace');
let regexp = require('./regexp');

let RegExpEnter = regexp.enter;

module.exports  = (name, options, scripts) => {
    if (!scripts) {
        scripts = options;
        options = {};
    }

    check(name, options, scripts);

    return parse(name, options, scripts);
};

function check(name, options, scripts) {
    if (typeof name !== 'string')
        throw Error('name should be string!');

    if (typeof scripts !== 'object')
        throw Error('json should be object!');
}

function parse(name, options, scripts) {
    let i = 1000;
    let body = getBody(name, options, scripts);
    let expandFn = currify(expand, scripts);

    while (RegExpEnter.test(body)) {
        if (i)
            --i;
        else
            throw Error(ERROR);

        body = replace(body, expandFn);
    }

    return body;
}

function expand(scripts, type, str) {
    let result;

    switch(type) {
    case 'npm':
        result = getBody(str, scripts);
        break;
    case 'redrun':
        let parsed = cliParse(str.split(' '), scripts);
        result = parsed.cmd || `echo ${parsed.output}`;
        break;
    default:
        result = str;
        break;
    }

    return result;
}

<commit_msg>refactor(redrun) add redrunParse<commit_after>'use strict';

const ERROR = 'Too deep traverse. Consider reduce scripts deepness.';

let currify = require('currify');

let getBody = require('./get-body');
let cliParse = require('./cli-parse');
let replace = require('./replace');
let regexp = require('./regexp');

let RegExpEnter = regexp.enter;

module.exports  = (name, options, scripts) => {
    if (!scripts) {
        scripts = options;
        options = {};
    }

    check(name, options, scripts);

    return parse(name, options, scripts);
};

function check(name, options, scripts) {
    if (typeof name !== 'string')
        throw Error('name should be string!');

    if (typeof scripts !== 'object')
        throw Error('json should be object!');
}

function parse(name, options, scripts) {
    let i = 1000;
    let body = getBody(name, options, scripts);
    let expandFn = currify(expand, scripts);

    while (RegExpEnter.test(body)) {
        if (i)
            --i;
        else
            throw Error(ERROR);

        body = replace(body, expandFn);
    }

    return body;
}

function expand(scripts, type, str) {
    let result;

    switch(type) {
    case 'npm':
        result = getBody(str, scripts);
        break;
    case 'redrun':
        result = redrunParse(str, scripts);
        break;
    default:
        result = str;
        break;
    }

    return result;
}

function redrunParse(str, scripts) {
    let args = str.split(' ');
    let parsed = cliParse(args, scripts);
    let result = parsed.cmd || `echo ${parsed.output}`;

    return result;
}

<|endoftext|>


<commit_before>/**
 * Created by Newton on 2015/5/11.
 */

'use strict';

var is = require('is');
var path = require('path');
var util = require('./util');
var debug = util.debug;
var colors = util.colors;
var dirname = path.dirname;
var basename = path.basename;
var extname = path.extname;

// Variable declaration
var defaults = { prefix: '', suffix: '' };

/**
 * rename file
 * @param path
 * @param transform
 * @returns {*}
 */

function rename(path, transform){
  var meta = parse(path);
  var basename = meta.base;

  transform = is.fn(transform) ? transform(util.extend({}, meta)) : transform;

  // rename it when transformer is string as a filepath
  if (is.string(transform)) {
    path = transform.trim() || path;
  } else {
    transform = util.extend({}, defaults, transform);

    // rename
    meta.basename = transform.prefix + meta.basename + transform.suffix;

    path = format(meta);
  }

  // debug
  debug('rename: %s', colors.magenta(basename));
  debug('to: %s', colors.magenta(meta.base));

  return path;
}

/**
 * parse path
 * @param path
 * @returns {{extname, dirname, basename}}
 */

function parse(path){
  var ext = extname(path);

  return {
    origin: path,
    extname: ext,
    dirname: dirname(path),
    basename: basename(path, ext)
  }
}

/**
 * format path form meta
 * @param meta
 * @returns {string}
 */

function format(meta){
  meta.dirname = meta.dirname || '.';
  meta.extname = meta.extname || '';
  meta.basename = meta.basename || '';

  return meta.dirname + '/' + meta.basename + meta.extname;
}

/**
 * Exports module.
 */

module.exports = rename;
<commit_msg>bug fixed<commit_after>/**
 * Created by Newton on 2015/5/11.
 */

'use strict';

var is = require('is');
var path = require('path');
var util = require('./util');
var debug = util.debug;
var colors = util.colors;
var dirname = path.dirname;
var basename = path.basename;
var extname = path.extname;

// Variable declaration
var defaults = { prefix: '', suffix: '' };

/**
 * rename file
 * @param path
 * @param transform
 * @returns {*}
 */

function rename(path, transform){
  var meta = parse(path);
  var basename = meta.basename + meta.extname;

  transform = is.fn(transform) ? transform(util.extend({}, meta)) : transform;

  // rename it when transformer is string as a filepath
  if (is.string(transform)) {
    path = transform.trim() || path;
  } else {
    transform = util.extend({}, defaults, transform);

    // rename
    meta.basename = transform.prefix + meta.basename + transform.suffix;

    path = format(meta);
  }

  // debug
  debug('rename: %s', colors.magenta(basename));
  debug('to: %s', colors.magenta(meta.basename + meta.extname));

  return path;
}

/**
 * parse path
 * @param path
 * @returns {{extname, dirname, basename}}
 */

function parse(path){
  var ext = extname(path);

  return {
    origin: path,
    extname: ext,
    dirname: dirname(path),
    basename: basename(path, ext)
  }
}

/**
 * format path form meta
 * @param meta
 * @returns {string}
 */

function format(meta){
  meta.dirname = meta.dirname || '.';
  meta.extname = meta.extname || '';
  meta.basename = meta.basename || '';

  return meta.dirname + '/' + meta.basename + meta.extname;
}

/**
 * Exports module.
 */

module.exports = rename;
<|endoftext|>