<commit_before>/**
 * Module dependencies.
 */

var express = require('express');
var mongoose = require('mongoose');
var api = require('lib/db-api');
var utils = require('lib/utils');
var accepts = require('lib/accepts');
var restrict = utils.restrict;
var pluck = utils.pluck;
var expose = utils.expose;
var log = require('debug')('democracyos:topic');
var config = require('lib/config');
var utils = require('lib/utils');
var notifier = require('lib/notifications').notifier;
var migrateTopic = require('lib/migrations/topic');
var hasAccess = require('lib/is-owner').hasAccess;

var app = module.exports = express();

/**
 * Limit request to json format only
 */

app.use(accepts('application/json'));

var topicListKeys = [
  'id topicId title mediaTitle status open closed public draft deleted forum',
  'tag participants voted createdAt updatedAt closingAt',
  'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
].join(' ');

var topicKeys = topicListKeys
              + ' '
              + 'summary clauses source state upvotes downvotes abstentions';

function exposeTopic(topicDoc, user, keys) {
  if (!keys) keys = topicKeys;

  var topic = topicDoc.toJSON();
  topic.voted = topicDoc.votedBy(user);

  return expose(keys)(topic);
}

function findTopic(req, res, next) {
  if (req.params.id) {
    api.topic.searchOne(req.params.id, function (err, topic) {
      if (err) {
        log('Error fetching topic: %s', err);
        return next()
      }

      req.topic = topic
      next()
    });
  } else {
    next();
  }
}

function findForum(req, res, next) {
  if (!config.multiForum) return next();

//  if (!mongoose.Types.ObjectId.isValid(req.query.forum)) {
//    return _handleError('Must define a valid \'forum\' param.', req, res);
//  }

  var id = req.query.forum || (req.topic && req.topic.forum && req.topic.forum._id.toString())
  if (id) {
    api.forum.findById(id, function(err, forum) {
      if (err) return _handleError(err, req, res);
      if (!forum) return _handleError(new Error('Forum not found.'), req, res);

      req.forum = forum;
      next();
    });
  } else {
    next();
  }
}

function onlyForumAdmins(req, res, next) {
  if (!req.user) _handleUnauthorizedError(new Error('Unauthorized.'), req, res);

  if (config.multiForum) {
    if (req.forum.isAdmin(req.user)) return next();
    return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
  }

  if (req.user.staff) return next();
  return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
}

function canVote(req, res, next) {
  if (!config.multiForum || req.user.staff) {
    return next();
  }

  if (!req.user) {
    return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
  }

  if (req.forum && (req.forum.isAdmin(req.user) || req.forum.hasRole('admin', req.user) || req.forum.hasRole('collaborator', req.user))) {
    return next();
  } else {
    return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
  }
}

app.get('/topic/all',
findForum,
function(req, res, next) {
  if (req.query.draft) {
    onlyForumAdmins(req, res, next);
  } else {
    next();
  }
},
function (req, res) {
  log('Request /topic/all');

  api.topic.all({
    forum: req.forum
  }, function(err, topics) {
    if (err) return _handleError(err, req, res);

    topics = topics.filter(function(topic) {
      if (topic.public) return true;
      if (req.query.draft && topic.draft) return true;
      return false;
    });

    log('Serving topics %j', pluck(topics, 'id'));

    res.json(topics.map(function(topicDoc){
      return exposeTopic(topicDoc, req.user, topicListKeys);
    }));
  });
});

app.get('/topic/:id', function (req, res) {
  log('Request GET /topic/%s', req.params.id);

  api.topic.get(req.params.id, function (err, topic) {
    if (err) return _handleError(err, req, res);
    if (!topic) return res.send(404);
    migrateTopic(topic, function (err, topicMigrated) {
      if (err) {
        return _handleError(err, req, res);
      }

      log('Serving topic %s', topicMigrated.id);
      res.json(exposeTopic(topicMigrated, req.user));
    });
  });
});

// TODO: Change endpoint -- move to comment-api
app.get('/topic/:id/sidecomments', function (req, res) {
  log('Requesting /topic/%s/sidecomments', req.params.id);
  // var paging = { page: 0, limit: 0, sort: 'createdAt', exclude_user: null };
  api.comment.getSideComments(req.params.id, null, function (err, comments) {
    if (err) return _handleError(err, req, res);
    log('Serving topic %s body\'s comments %j', req.params.id, pluck(comments, 'id'));

    var keys = [
      'id text createdAt editedAt context reference',
      'author.id author.fullName author.displayName author.avatar',
      'flags upvotes downvotes votes replies.length'
    ].join(' ');

    res.json(comments.map(expose(keys)));
  });
});

app.get('/topic/:id/comments', function (req, res) {
  log('Request /topic/%s/comments', req.params.id);

  var sort = '';
  if (~['-score', '-createdAt', 'createdAt'].indexOf(req.query.sort)) {
    sort = req.query.sort;
  } else {
    sort = '-score';
  }

  var paging = {
    page: req.query.page || 0,
    limit: req.query.limit || 0,
    sort: sort,
    exclude_user: req.query.exclude_user || null
  };

  api.topic.comments(req.params.id, paging, function (err, comments) {
    if (err) return _handleError(err, req, res);

    if (!req.query.count) {
      log('Serving topic %s comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        "id text createdAt editedAt context reference",
        "author.id author.fullName author.displayName author.avatar",
        "flags upvotes downvotes votes replies.length"
      ].join(' ');

      res.json(comments.map(expose(keys)));
    } else {
      log('Serving topic %s comments count: %d', req.params.id, comments.length);

      res.json(comments.length);
    }
  });
});

app.get('/topic/:id/my-comments', restrict, function (req, res) {
  log('Request /topic/%s/my-comments', req.params.id);

  api.topic.userComments(req.params.id, req.user.id, function (err, comments) {
    if (err) return _handleError(err, req, res);

    log('Serving topic %s comments %j for user %s', req.params.id, pluck(comments, 'id'), req.user.id);

    var keys = [
      "id text createdAt editedAt context reference",
      "author.id author.fullName author.displayName author.avatar",
      "flags upvotes downvotes votes replies.length"
    ].join(' ');

    res.json(comments.map(expose(keys)));
  });
});

app.post('/topic/:id/comment', restrict, function (req, res) {
  log('Request /topic/%s/comment %j', req.params.id, req.body.comment);

  var comment = {
    text: req.body.text,
    context: req.body.context || 'topic',
    reference: req.params.id,
    topicId: req.body.topicId,
    author: req.user.id,
  };

  api.topic.comment(comment, function (err, commentDoc) {
    if (err) return _handleError(err, req, res);

    var keys = [
      'id text',
      'author.id author.fullName author.displayName author.avatar',
      'upvotes downvotes flags',
      'createdAt replies context reference'
    ].join(' ');

    res.json(200, expose(keys)(commentDoc));
  });
});

app.post('/topic/:id/vote', restrict, findTopic, findForum, canVote, function (req, res) {
  log('Request /topic/%s/vote', req.param('id'));

  api.topic
  .vote(
    req.param('id'),
    req.user,
    req.param('value'),
    function (err, topic) {
      if (err) return _handleError(err, req, res);

      var eventName = 'topic-voted';

      notifier.notify(eventName)
        .withData( { topic: topic.id, user: req.user.id, vote: req.param('value') } )
        .send(function (err, data) {
          if (err) {
            log('Error when sending notification for event %s', eventName);
          } else {
            log('Successfully notified voting of topic %s', topic.id);
          }
        });

      res.json(exposeTopic(topic, req.user));
    }
  );
});

app.post('/topic/create', restrict, hasAccess, function (req, res, next) {
  log('Request /topic/create %j', req.body);

  req.body.forum = req.forum;
  api.topic.create(req.body, function (err, topic) {
    if (err) return next(err);
    var keys = [
      'id topicId title mediaTitle body clauses source state',
      'status open closed public draft deleted tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable bodyTruncationText links author authorUrl'
    ].join(' ');
    res.json(exposeTopic(topic, req.user));
  });
});

app.post('/topic/:id', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s', req.params.id);

  api.topic.update(req.params.id, req.body, function (err, topic) {
    if (err) return _handleError(err, req, res);
    log('Serving topic %s', topic.id);
    res.json(exposeTopic(topic, req.user));
  });
});

app.post('/topic/:id/link', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s/link', req.params.id);

  var link = req.body.link;
  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    var linkDoc = link && link.id
      ? topicDoc.links.id(link.id)
      : topicDoc.links.create();

    linkDoc.update(link);
    if (linkDoc.isNew) topicDoc.links.push(linkDoc);

    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200, expose('id text url')(linkDoc));
    });
  });
});

app.delete('/topic/:id/link', restrict, hasAccess, function (req, res) {
  log('Request DELETE /topic/%s/link', req.params.id);

  var link = req.body.link;
  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    topicDoc.links.remove(link);
    log('removed link %s from topic %s', link, topicDoc.id);

    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200);
    });
  });
});

app.post('/topic/:id/publish', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s/publish', req.params.id);

  api.topic.get(req.params.id, function (err, topic) {
    if (err) return _handleError(err, req, res);

    topic.publishedAt = new Date;
    topic.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('publish topic %s at %s', topic.id, topic.publishedAt);

      var eventName = 'topic-published';

      var topicUrl = utils.buildUrl(config, { pathname: '/topic/' + topic.id });

      var data = {
        topic: { mediaTitle: topic.mediaTitle, id: topic.id },
        url: topicUrl
      };

      if (config.deploymentId) {
        data.deploymentId = config.deploymentId;
      }

      if (!config.multiForum) {
        notifier.notify(eventName)
          .withData(data)
          .send(function (err) {
            if (err) {
              log('Error when sending notification for event %s', eventName);
            } else {
              log('Successfully notified publishing of topic %s', topic.id);
            }
          });
      }
    });

    res.json(exposeTopic(topic, req.user));
  });
});

app.post('/topic/:id/unpublish', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s/unpublish', req.params.id);

  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    topicDoc.publishedAt = null;
    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('unpublished topic %s', topicDoc.id);
      res.json(exposeTopic(topicDoc, req.user));
    });
  });
});

app.delete('/topic/:id', restrict, hasAccess, function (req, res) {
  log('Request DEL /topic/%s', req.params.id);

  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    topicDoc.deletedAt = new Date;
    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('deleted topic %s', topicDoc.id);
      res.json(200);
    });
  });
});

function _handleError (err, req, res) {
  log("Error found: %s", err);
  var error = err;
  if (err.errors && err.errors.text) error = err.errors.text;
  if (error.type) error = error.type;

  res.json(400, { error: error });
}

function _handleUnauthorizedError (err, req, res) {
  log("Error found: %s", err);
  var error = err;
  if (err.errors && err.errors.text) error = err.errors.text;
  if (error.type) error = error.type;

  res.json(403, { error: error });
}
<commit_msg>Add restriction on commenting topics #1143<commit_after>/**
 * Module dependencies.
 */

var express = require('express');
var mongoose = require('mongoose');
var api = require('lib/db-api');
var utils = require('lib/utils');
var accepts = require('lib/accepts');
var restrict = utils.restrict;
var pluck = utils.pluck;
var expose = utils.expose;
var log = require('debug')('democracyos:topic');
var config = require('lib/config');
var utils = require('lib/utils');
var notifier = require('lib/notifications').notifier;
var migrateTopic = require('lib/migrations/topic');
var hasAccess = require('lib/is-owner').hasAccess;

var app = module.exports = express();

/**
 * Limit request to json format only
 */

app.use(accepts('application/json'));

var topicListKeys = [
  'id topicId title mediaTitle status open closed public draft deleted forum',
  'tag participants voted createdAt updatedAt closingAt',
  'publishedAt deletedAt votable clauseTruncationText links author authorUrl'
].join(' ');

var topicKeys = topicListKeys
              + ' '
              + 'summary clauses source state upvotes downvotes abstentions';

function exposeTopic(topicDoc, user, keys) {
  if (!keys) keys = topicKeys;

  var topic = topicDoc.toJSON();
  topic.voted = topicDoc.votedBy(user);

  return expose(keys)(topic);
}

function findTopic(req, res, next) {
  if (req.params.id) {
    api.topic.searchOne(req.params.id, function (err, topic) {
      if (err) {
        log('Error fetching topic: %s', err);
        return next()
      }

      req.topic = topic
      next()
    });
  } else {
    next();
  }
}

function findForum(req, res, next) {
  if (!config.multiForum) return next();

//  if (!mongoose.Types.ObjectId.isValid(req.query.forum)) {
//    return _handleError('Must define a valid \'forum\' param.', req, res);
//  }

  var id = req.query.forum || (req.topic && req.topic.forum && req.topic.forum._id.toString())
  if (id) {
    api.forum.findById(id, function(err, forum) {
      if (err) return _handleError(err, req, res);
      if (!forum) return _handleError(new Error('Forum not found.'), req, res);

      req.forum = forum;
      next();
    });
  } else {
    next();
  }
}

function onlyForumAdmins(req, res, next) {
  if (!req.user) _handleUnauthorizedError(new Error('Unauthorized.'), req, res);

  if (config.multiForum) {
    if (req.forum.isAdmin(req.user)) return next();
    return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
  }

  if (req.user.staff) return next();
  return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
}

function canVote(req, res, next) {
  if (!config.multiForum || req.user.staff) {
    return next();
  }

  if (!req.user) {
    return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
  }

  if (req.forum && (req.forum.isAdmin(req.user) || req.forum.hasRole('admin', req.user) || req.forum.hasRole('collaborator', req.user))) {
    return next();
  } else {
    return _handleUnauthorizedError(new Error('Unauthorized.'), req, res);
  }
}

app.get('/topic/all',
findForum,
function(req, res, next) {
  if (req.query.draft) {
    onlyForumAdmins(req, res, next);
  } else {
    next();
  }
},
function (req, res) {
  log('Request /topic/all');

  api.topic.all({
    forum: req.forum
  }, function(err, topics) {
    if (err) return _handleError(err, req, res);

    topics = topics.filter(function(topic) {
      if (topic.public) return true;
      if (req.query.draft && topic.draft) return true;
      return false;
    });

    log('Serving topics %j', pluck(topics, 'id'));

    res.json(topics.map(function(topicDoc){
      return exposeTopic(topicDoc, req.user, topicListKeys);
    }));
  });
});

app.get('/topic/:id', function (req, res) {
  log('Request GET /topic/%s', req.params.id);

  api.topic.get(req.params.id, function (err, topic) {
    if (err) return _handleError(err, req, res);
    if (!topic) return res.send(404);
    migrateTopic(topic, function (err, topicMigrated) {
      if (err) {
        return _handleError(err, req, res);
      }

      log('Serving topic %s', topicMigrated.id);
      res.json(exposeTopic(topicMigrated, req.user));
    });
  });
});

// TODO: Change endpoint -- move to comment-api
app.get('/topic/:id/sidecomments', function (req, res) {
  log('Requesting /topic/%s/sidecomments', req.params.id);
  // var paging = { page: 0, limit: 0, sort: 'createdAt', exclude_user: null };
  api.comment.getSideComments(req.params.id, null, function (err, comments) {
    if (err) return _handleError(err, req, res);
    log('Serving topic %s body\'s comments %j', req.params.id, pluck(comments, 'id'));

    var keys = [
      'id text createdAt editedAt context reference',
      'author.id author.fullName author.displayName author.avatar',
      'flags upvotes downvotes votes replies.length'
    ].join(' ');

    res.json(comments.map(expose(keys)));
  });
});

app.get('/topic/:id/comments', function (req, res) {
  log('Request /topic/%s/comments', req.params.id);

  var sort = '';
  if (~['-score', '-createdAt', 'createdAt'].indexOf(req.query.sort)) {
    sort = req.query.sort;
  } else {
    sort = '-score';
  }

  var paging = {
    page: req.query.page || 0,
    limit: req.query.limit || 0,
    sort: sort,
    exclude_user: req.query.exclude_user || null
  };

  api.topic.comments(req.params.id, paging, function (err, comments) {
    if (err) return _handleError(err, req, res);

    if (!req.query.count) {
      log('Serving topic %s comments %j', req.params.id, pluck(comments, 'id'));

      var keys = [
        "id text createdAt editedAt context reference",
        "author.id author.fullName author.displayName author.avatar",
        "flags upvotes downvotes votes replies.length"
      ].join(' ');

      res.json(comments.map(expose(keys)));
    } else {
      log('Serving topic %s comments count: %d', req.params.id, comments.length);

      res.json(comments.length);
    }
  });
});

app.get('/topic/:id/my-comments', restrict, function (req, res) {
  log('Request /topic/%s/my-comments', req.params.id);

  api.topic.userComments(req.params.id, req.user.id, function (err, comments) {
    if (err) return _handleError(err, req, res);

    log('Serving topic %s comments %j for user %s', req.params.id, pluck(comments, 'id'), req.user.id);

    var keys = [
      "id text createdAt editedAt context reference",
      "author.id author.fullName author.displayName author.avatar",
      "flags upvotes downvotes votes replies.length"
    ].join(' ');

    res.json(comments.map(expose(keys)));
  });
});

app.post('/topic/:id/comment', restrict, findTopic, findForum, canVote, function (req, res) {
  log('Request /topic/%s/comment %j', req.params.id, req.body.comment);

  var comment = {
    text: req.body.text,
    context: req.body.context || 'topic',
    reference: req.params.id,
    topicId: req.body.topicId,
    author: req.user.id,
  };

  api.topic.comment(comment, function (err, commentDoc) {
    if (err) return _handleError(err, req, res);

    var keys = [
      'id text',
      'author.id author.fullName author.displayName author.avatar',
      'upvotes downvotes flags',
      'createdAt replies context reference'
    ].join(' ');

    res.json(200, expose(keys)(commentDoc));
  });
});

app.post('/topic/:id/vote', restrict, findTopic, findForum, canVote, function (req, res) {
  log('Request /topic/%s/vote', req.param('id'));

  api.topic
  .vote(
    req.param('id'),
    req.user,
    req.param('value'),
    function (err, topic) {
      if (err) return _handleError(err, req, res);

      var eventName = 'topic-voted';

      notifier.notify(eventName)
        .withData( { topic: topic.id, user: req.user.id, vote: req.param('value') } )
        .send(function (err, data) {
          if (err) {
            log('Error when sending notification for event %s', eventName);
          } else {
            log('Successfully notified voting of topic %s', topic.id);
          }
        });

      res.json(exposeTopic(topic, req.user));
    }
  );
});

app.post('/topic/create', restrict, hasAccess, function (req, res, next) {
  log('Request /topic/create %j', req.body);

  req.body.forum = req.forum;
  api.topic.create(req.body, function (err, topic) {
    if (err) return next(err);
    var keys = [
      'id topicId title mediaTitle body clauses source state',
      'status open closed public draft deleted tag participants',
      'upvotes downvotes abstentions createdAt updatedAt closingAt',
      'publishedAt deletedAt votable bodyTruncationText links author authorUrl'
    ].join(' ');
    res.json(exposeTopic(topic, req.user));
  });
});

app.post('/topic/:id', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s', req.params.id);

  api.topic.update(req.params.id, req.body, function (err, topic) {
    if (err) return _handleError(err, req, res);
    log('Serving topic %s', topic.id);
    res.json(exposeTopic(topic, req.user));
  });
});

app.post('/topic/:id/link', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s/link', req.params.id);

  var link = req.body.link;
  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    var linkDoc = link && link.id
      ? topicDoc.links.id(link.id)
      : topicDoc.links.create();

    linkDoc.update(link);
    if (linkDoc.isNew) topicDoc.links.push(linkDoc);

    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200, expose('id text url')(linkDoc));
    });
  });
});

app.delete('/topic/:id/link', restrict, hasAccess, function (req, res) {
  log('Request DELETE /topic/%s/link', req.params.id);

  var link = req.body.link;
  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    topicDoc.links.remove(link);
    log('removed link %s from topic %s', link, topicDoc.id);

    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);

      res.json(200);
    });
  });
});

app.post('/topic/:id/publish', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s/publish', req.params.id);

  api.topic.get(req.params.id, function (err, topic) {
    if (err) return _handleError(err, req, res);

    topic.publishedAt = new Date;
    topic.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('publish topic %s at %s', topic.id, topic.publishedAt);

      var eventName = 'topic-published';

      var topicUrl = utils.buildUrl(config, { pathname: '/topic/' + topic.id });

      var data = {
        topic: { mediaTitle: topic.mediaTitle, id: topic.id },
        url: topicUrl
      };

      if (config.deploymentId) {
        data.deploymentId = config.deploymentId;
      }

      if (!config.multiForum) {
        notifier.notify(eventName)
          .withData(data)
          .send(function (err) {
            if (err) {
              log('Error when sending notification for event %s', eventName);
            } else {
              log('Successfully notified publishing of topic %s', topic.id);
            }
          });
      }
    });

    res.json(exposeTopic(topic, req.user));
  });
});

app.post('/topic/:id/unpublish', restrict, hasAccess, function (req, res) {
  log('Request POST /topic/%s/unpublish', req.params.id);

  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    topicDoc.publishedAt = null;
    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('unpublished topic %s', topicDoc.id);
      res.json(exposeTopic(topicDoc, req.user));
    });
  });
});

app.delete('/topic/:id', restrict, hasAccess, function (req, res) {
  log('Request DEL /topic/%s', req.params.id);

  api.topic.get(req.params.id, function (err, topicDoc) {
    if (err) return _handleError(err, req, res);

    topicDoc.deletedAt = new Date;
    topicDoc.save(function (err, saved) {
      if (err) return _handleError(err, req, res);
      log('deleted topic %s', topicDoc.id);
      res.json(200);
    });
  });
});

function _handleError (err, req, res) {
  log("Error found: %s", err);
  var error = err;
  if (err.errors && err.errors.text) error = err.errors.text;
  if (error.type) error = error.type;

  res.json(400, { error: error });
}

function _handleUnauthorizedError (err, req, res) {
  log("Error found: %s", err);
  var error = err;
  if (err.errors && err.errors.text) error = err.errors.text;
  if (error.type) error = error.type;

  res.json(403, { error: error });
}
<|endoftext|>


<commit_before>
const {
  mode,
  pathTo,
  plugins,
  loaders,
  resolve,
  stats
} = require('./common');

module.exports = {
  mode,
  devtool: 'eval',

  entry: [
    pathTo('example', 'index.js'),
    'webpack-dev-server/client?http://localhost:8080'
  ],
  output: {
    filename: 'bundle.js',
    path: pathTo('dev')
  },
  plugins: [
    plugins.html
  ],
  module: {
    rules: [
      loaders.babel,
      loaders.css
    ]
  },
  resolve,
  stats,
  devServer: {
    historyApiFallback: true,
    stats: {
      // Do not show list of hundreds of files included in a bundle
      chunkModules: false,
      colors: true
    }
  }
};
<commit_msg>Better dev sourcemaps<commit_after>
const {
  mode,
  pathTo,
  plugins,
  loaders,
  resolve,
  stats
} = require('./common');

module.exports = {
  mode,
  devtool: 'source-map',

  entry: [
    pathTo('example', 'index.js'),
    'webpack-dev-server/client?http://localhost:8080'
  ],
  output: {
    filename: 'bundle.js',
    path: pathTo('dev')
  },
  plugins: [
    plugins.html
  ],
  module: {
    rules: [
      loaders.babel,
      loaders.css
    ]
  },
  resolve,
  stats,
  devServer: {
    historyApiFallback: true,
    stats: {
      // Do not show list of hundreds of files included in a bundle
      chunkModules: false,
      colors: true
    }
  }
};
<|endoftext|>


<commit_before>const d3Array = require("d3-array");
const {CANON_API, CANON_LOGICLAYER_CUBE} = process.env;

module.exports = {
  logiclayer: {
    aliases: {
      "CIP": "cip",
      "Geography": "geo",
      "measures": ["measure", "required"],
      "PUMS Industry": "naics",
      "PUMS Occupation": "soc",
      "University": "university",
      "Year": "year",
      "NAPCS": "napcs"
    },
    cubeFilters: [
      {
        filter: (cubes, query, caches) => {

          if (cubes.find(cube => cube.name.includes("_c_"))) {
            cubes = cubes.filter(cube => cube.name.includes("_c_"));
          }
          else if (cubes.find(cube => cube.name.includes("_2016_"))) {
            cubes = cubes.filter(cube => cube.name.includes("_2016_"));
          }

          const {pops} = caches;
          const ids = d3Array.merge(query.dimensions
            .filter(d => d.dimension === "Geography")
            .map(d => d.id instanceof Array ? d.id : [d.id]));

          const onlyNation = cubes.find(cube =>
            cube.name.includes("acs_ygpsar_poverty_by_gender_age_race") ||
            cube.name.includes("acs_ygf_place_of_birth_for_foreign_born")
          );

          const bigGeos = ids.length ? onlyNation ? ids.every(g => g === "01000US") : ids.every(g => pops[g] && pops[g] >= 250000) : true;
          const drilldowns = query.dimensions.filter(d => {
            const relation = d.relation || "";
            return relation.includes("Tract") || relation.includes("Place");
          }).length;

          return cubes.length === 1 ? cubes : cubes.filter(cube => cube.name.match(bigGeos && !drilldowns ? /_1$/g : /_5$/g));

        },
        key: cube => cube.name.replace("_c_", "_").replace("_2016_", "_").replace(/_[0-9]$/g, "")
      },
      {
        filter: cubes => cubes.filter(c => c.name === "ipeds_graduation_demographics_v3"),
        key: cube => !cube ? "test" : cube.name === "ipeds_undergrad_grad_rate_demographics" || cube.name === "ipeds_graduation_demographics_v2" ? "ipeds_graduation_demographics_v3" : cube.name
      }
    ],
    dimensionMap: {
      "CIP2": "CIP",
      "Industry": "PUMS Industry",
      "Commodity L0": "PUMS Industry",
      // "Commodity L1": "PUMS Industry",
      "Industry L0": "PUMS Industry",
      // "Industry L1": "PUMS Industry",
      "Occupation": "PUMS Occupation",
      "OPEID": "University",
      "SCTG2": "NAPCS",
      "Destination State": "Geography",
      "Origin State": "Geography"
    },
    relations: {
      "Origin State": {
        neighbors: {
          url: id => `${CANON_LOGICLAYER_CUBE}/geoservice-api/neighbors/${id}`,
          callback: resp => {
            if (resp.error) {
              console.error("[geoservice error]");
              console.error(resp.error);
              return [];
            }
            else {
              return (resp  || []).map(d => d.geoid);
            }
          }
        },
        parents: {
          url: id => `${CANON_API}/api/parents/geo/${id}`,
          callback: arr => arr.map(d => d.id)
        },
        similar: {
          url: id => `${CANON_API}/api/geo/similar/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "Geography": {
        children: {
          url: id => `${CANON_API}/api/geo/children/${id}/`
        },
        tracts: {
          url: id => `${CANON_API}/api/geo/children/${id}/?level=Tract`
        },
        places: {
          url: id => `${CANON_API}/api/geo/children/${id}/?level=Place`
        },
        childrenCounty: {
          url: id => `${CANON_API}/api/geo/childrenCounty/${id}/`
        },
        neighbors: {
          url: id => `${CANON_LOGICLAYER_CUBE}/geoservice-api/neighbors/${id}`,
          callback: resp => {
            if (resp.error) {
              console.error("[geoservice error]");
              console.error(resp.error);
              return [];
            }
            else {
              return (resp  || []).map(d => d.geoid);
            }
          }
        },
        parents: {
          url: id => `${CANON_API}/api/parents/geo/${id}`,
          callback: arr => arr.map(d => d.id)
        },
        similar: {
          url: id => `${CANON_API}/api/geo/similar/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "CIP": {
        parents: {
          url: id => `${CANON_API}/api/parents/cip/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "PUMS Industry": {
        parents: {
          url: id => `${CANON_API}/api/parents/naics/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "NAPCS": {
        parents: {
          url: id => `${CANON_API}/api/parents/napcs/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "PUMS Occupation": {
        parents: {
          url: id => `${CANON_API}/api/parents/soc/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "University": {
        parents: {
          url: id => `${CANON_API}/api/parents/university/${id}`,
          callback: arr => arr.map(d => d.id)
        },
        similar: {
          url: id => `${CANON_API}/api/university/similar/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      }
    },
    substitutions: {
      "Geography": {
        levels: {
          State: ["Nation"],
          County: ["MSA", "State", "Nation"],
          MSA: ["State", "Nation"],
          Place: ["MSA", "State", "Nation"],
          PUMA: ["State", "Nation"]
        },
        url: (id, level) => {
          const targetLevel = level.toLowerCase();
          return `${CANON_LOGICLAYER_CUBE}/geoservice-api/relations/intersects/${id}?targetLevels=${targetLevel}&overlapSize=true`;
        },
        callback: resp => {
          let arr = [];
          if (resp.error) {
            console.error("[geoservice error]");
            console.error(resp.error);
          }
          else {
            arr = resp  || [];
          }
          arr = arr.sort((a, b) => b.overlap_size - a.overlap_size);
          return arr.length ? arr[0].geoid : "01000US";
        }
      },
      "CIP": {
        levels: {
          CIP6: ["CIP4", "CIP2"],
          CIP4: ["CIP2"]
        },
        url: (id, level) => `${CANON_API}/api/cip/parent/${id}/${level}/`,
        callback: resp => resp.id
      },
      "PUMS Industry": {
        levels: {
          "Industry Group": ["Industry", "Industry L0", "Commodity L0"],
          "Industry Sub-Sector": ["Industry", "Industry L0", "Commodity L0"],
          "Industry Sector": ["Industry", "Industry L0", "Commodity L0"]
        },
        url: (id, level) => `${CANON_API}/api/naics/${id}/${level}`,
        callback: resp => resp
      },
      "PUMS Occupation": {
        levels: {
          "Major Occupation Group": ["Occupation"],
          "Minor Occupation Group": ["Occupation"],
          "Broad Occupation": ["Occupation"],
          "Detailed Occupation": ["Occupation"]
        },
        url: id => `${CANON_API}/api/soc/${id}/bls`,
        callback: resp => resp
      },
      "NAPCS": {
        levels: {
          "NAPCS Section": ["SCTG2"],
          "NAPCS Group": ["SCTG2"],
          "NAPCS Class": ["SCTG2"]
        },
        url: id => `${CANON_API}/api/napcs/${id}/sctg/`,
        callback: resp => resp.map(d => d.id)
      },
      "University": {
        levels: {
          University: ["OPEID"]
        },
        url: id => `${CANON_API}/api/university/opeid/${id}/`,
        callback: resp => resp.opeid
      }
    }
  }
};
<commit_msg>allows Places to bubble up to Counties<commit_after>const d3Array = require("d3-array");
const {CANON_API, CANON_LOGICLAYER_CUBE} = process.env;

module.exports = {
  logiclayer: {
    aliases: {
      "CIP": "cip",
      "Geography": "geo",
      "measures": ["measure", "required"],
      "PUMS Industry": "naics",
      "PUMS Occupation": "soc",
      "University": "university",
      "Year": "year",
      "NAPCS": "napcs"
    },
    cubeFilters: [
      {
        filter: (cubes, query, caches) => {

          if (cubes.find(cube => cube.name.includes("_c_"))) {
            cubes = cubes.filter(cube => cube.name.includes("_c_"));
          }
          else if (cubes.find(cube => cube.name.includes("_2016_"))) {
            cubes = cubes.filter(cube => cube.name.includes("_2016_"));
          }

          const {pops} = caches;
          const ids = d3Array.merge(query.dimensions
            .filter(d => d.dimension === "Geography")
            .map(d => d.id instanceof Array ? d.id : [d.id]));

          const onlyNation = cubes.find(cube =>
            cube.name.includes("acs_ygpsar_poverty_by_gender_age_race") ||
            cube.name.includes("acs_ygf_place_of_birth_for_foreign_born")
          );

          const bigGeos = ids.length ? onlyNation ? ids.every(g => g === "01000US") : ids.every(g => pops[g] && pops[g] >= 250000) : true;
          const drilldowns = query.dimensions.filter(d => {
            const relation = d.relation || "";
            return relation.includes("Tract") || relation.includes("Place");
          }).length;

          return cubes.length === 1 ? cubes : cubes.filter(cube => cube.name.match(bigGeos && !drilldowns ? /_1$/g : /_5$/g));

        },
        key: cube => cube.name.replace("_c_", "_").replace("_2016_", "_").replace(/_[0-9]$/g, "")
      },
      {
        filter: cubes => cubes.filter(c => c.name === "ipeds_graduation_demographics_v3"),
        key: cube => !cube ? "test" : cube.name === "ipeds_undergrad_grad_rate_demographics" || cube.name === "ipeds_graduation_demographics_v2" ? "ipeds_graduation_demographics_v3" : cube.name
      }
    ],
    dimensionMap: {
      "CIP2": "CIP",
      "Industry": "PUMS Industry",
      "Commodity L0": "PUMS Industry",
      // "Commodity L1": "PUMS Industry",
      "Industry L0": "PUMS Industry",
      // "Industry L1": "PUMS Industry",
      "Occupation": "PUMS Occupation",
      "OPEID": "University",
      "SCTG2": "NAPCS",
      "Destination State": "Geography",
      "Origin State": "Geography"
    },
    relations: {
      "Origin State": {
        neighbors: {
          url: id => `${CANON_LOGICLAYER_CUBE}/geoservice-api/neighbors/${id}`,
          callback: resp => {
            if (resp.error) {
              console.error("[geoservice error]");
              console.error(resp.error);
              return [];
            }
            else {
              return (resp  || []).map(d => d.geoid);
            }
          }
        },
        parents: {
          url: id => `${CANON_API}/api/parents/geo/${id}`,
          callback: arr => arr.map(d => d.id)
        },
        similar: {
          url: id => `${CANON_API}/api/geo/similar/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "Geography": {
        children: {
          url: id => `${CANON_API}/api/geo/children/${id}/`
        },
        tracts: {
          url: id => `${CANON_API}/api/geo/children/${id}/?level=Tract`
        },
        places: {
          url: id => `${CANON_API}/api/geo/children/${id}/?level=Place`
        },
        childrenCounty: {
          url: id => `${CANON_API}/api/geo/childrenCounty/${id}/`
        },
        neighbors: {
          url: id => `${CANON_LOGICLAYER_CUBE}/geoservice-api/neighbors/${id}`,
          callback: resp => {
            if (resp.error) {
              console.error("[geoservice error]");
              console.error(resp.error);
              return [];
            }
            else {
              return (resp  || []).map(d => d.geoid);
            }
          }
        },
        parents: {
          url: id => `${CANON_API}/api/parents/geo/${id}`,
          callback: arr => arr.map(d => d.id)
        },
        similar: {
          url: id => `${CANON_API}/api/geo/similar/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "CIP": {
        parents: {
          url: id => `${CANON_API}/api/parents/cip/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "PUMS Industry": {
        parents: {
          url: id => `${CANON_API}/api/parents/naics/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "NAPCS": {
        parents: {
          url: id => `${CANON_API}/api/parents/napcs/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "PUMS Occupation": {
        parents: {
          url: id => `${CANON_API}/api/parents/soc/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      },
      "University": {
        parents: {
          url: id => `${CANON_API}/api/parents/university/${id}`,
          callback: arr => arr.map(d => d.id)
        },
        similar: {
          url: id => `${CANON_API}/api/university/similar/${id}`,
          callback: arr => arr.map(d => d.id)
        }
      }
    },
    substitutions: {
      "Geography": {
        levels: {
          State: ["Nation"],
          County: ["MSA", "State", "Nation"],
          MSA: ["State", "Nation"],
          Place: ["County", "MSA", "State", "Nation"],
          PUMA: ["State", "Nation"]
        },
        url: (id, level) => {
          const targetLevel = level.toLowerCase();
          return `${CANON_LOGICLAYER_CUBE}/geoservice-api/relations/intersects/${id}?targetLevels=${targetLevel}&overlapSize=true`;
        },
        callback: resp => {
          let arr = [];
          if (resp.error) {
            console.error("[geoservice error]");
            console.error(resp.error);
          }
          else {
            arr = resp  || [];
          }
          arr = arr.sort((a, b) => b.overlap_size - a.overlap_size);
          return arr.length ? arr[0].geoid : "01000US";
        }
      },
      "CIP": {
        levels: {
          CIP6: ["CIP4", "CIP2"],
          CIP4: ["CIP2"]
        },
        url: (id, level) => `${CANON_API}/api/cip/parent/${id}/${level}/`,
        callback: resp => resp.id
      },
      "PUMS Industry": {
        levels: {
          "Industry Group": ["Industry", "Industry L0", "Commodity L0"],
          "Industry Sub-Sector": ["Industry", "Industry L0", "Commodity L0"],
          "Industry Sector": ["Industry", "Industry L0", "Commodity L0"]
        },
        url: (id, level) => `${CANON_API}/api/naics/${id}/${level}`,
        callback: resp => resp
      },
      "PUMS Occupation": {
        levels: {
          "Major Occupation Group": ["Occupation"],
          "Minor Occupation Group": ["Occupation"],
          "Broad Occupation": ["Occupation"],
          "Detailed Occupation": ["Occupation"]
        },
        url: id => `${CANON_API}/api/soc/${id}/bls`,
        callback: resp => resp
      },
      "NAPCS": {
        levels: {
          "NAPCS Section": ["SCTG2"],
          "NAPCS Group": ["SCTG2"],
          "NAPCS Class": ["SCTG2"]
        },
        url: id => `${CANON_API}/api/napcs/${id}/sctg/`,
        callback: resp => resp.map(d => d.id)
      },
      "University": {
        levels: {
          University: ["OPEID"]
        },
        url: id => `${CANON_API}/api/university/opeid/${id}/`,
        callback: resp => resp.opeid
      }
    }
  }
};
<|endoftext|>


<commit_before>var syntax = [
  'Supports standard dice notation, as well as some extended functionality.',
  'syntax: <roll>[<operator><roll><operator><roll>...][<operator><constant>]',
  'roll: [<number of dice>]d<number of sides>[<modifiers>]',
  '      default number of dice: 1',
  'operator: + or -',
  'constant: any integer',
  'modifiers:',
  '  d<number> - drop the lowest X rolls from this group',
  '  k<number> - keep the highest X rolls from this group',
  '  h - alter either d or k modifier to affect the highest rolls, e.g. dh3: drop the highest 3 rolls',
  '  l - alter either d or k modifier to affect the lowest rolls, e.g. kl2: keep the lowest 2 rolls',
  '  r - reroll based on certain rules',
  '    r4 - reroll all 4s',
  '    r<3 - reroll anything less than 3',
  '    r>=11 - reroll anything greater than or equal to 11',
  'modifiers can be combined, but d and k are mutually exclusive'
].join('\n');

var specialData = {
  barrel: 'Donkey Kong rolls a barrel down the ramp and crushes you. -1000pts',
  rick: 'No.',
  katamari: 'Na naaaaa, na na na na na na, na na Katamari Damacy....',
  help: syntax,
  syntax: syntax
}

function SpecialFunctions(){
  this.getSpecial = function(expression){
    if(expression && specialData.hasOwnProperty(expression)){
      return specialData[expression];
    }
    return null;
  }
}

module.exports = new SpecialFunctions();<commit_msg>Add fudge dice<commit_after>var syntax = [
  'Supports standard dice notation, as well as some extended functionality.',
  'syntax: <roll>[<operator><roll><operator><roll>...][<operator><constant>]',
  'roll: [<number of dice>]d<number of sides>[<modifiers>]',
  '      default number of dice: 1',
  'number of sides: any integer, or f (for Fudge dice)',
  'operator: + or -',
  'constant: any integer',
  'modifiers:',
  '  d<number> - drop the lowest X rolls from this group',
  '  k<number> - keep the highest X rolls from this group',
  '  h - alter either d or k modifier to affect the highest rolls, e.g. dh3: drop the highest 3 rolls',
  '  l - alter either d or k modifier to affect the lowest rolls, e.g. kl2: keep the lowest 2 rolls',
  '  r - reroll based on certain rules',
  '    r4 - reroll all 4s',
  '    r<3 - reroll anything less than 3',
  '    r>=11 - reroll anything greater than or equal to 11',
  'modifiers can be combined, but d and k are mutually exclusive'
].join('\n');

var specialData = {
  barrel: 'Donkey Kong rolls a barrel down the ramp and crushes you. -1000pts',
  rick: 'No.',
  katamari: 'Na naaaaa, na na na na na na, na na Katamari Damacy....',
  help: syntax,
  syntax: syntax
}

function SpecialFunctions(){
  this.getSpecial = function(expression){
    if(expression && specialData.hasOwnProperty(expression)){
      return specialData[expression];
    }
    return null;
  }
}

module.exports = new SpecialFunctions();
<|endoftext|>


<commit_before>var React = require("react");
var appbaseRef = require("./appbase").appbaseRef;
var config = require("./appbase").config;

var requestObject = {
  type: config.type,
  body: {
    query: {
      match_all: {}
    }
  }
};

var Stats = React.createClass({

  getInitialState: function(){
    return {
      bid: "0",
      last: "0",
      avg: "0",
      total: "0",
      ask: "0"
    };
  },
  componentDidMount: function(){
    var self = this;
    appbaseRef.searchStream(requestObject).on('data', function(stream) {
      self.setState({bid: stream._source.bid});
      self.setState({last: stream._source.last});
      self.setState({avg: stream._source['24h_avg']});
      self.setState({total: stream._source.total_vol});
      self.setState({ask: stream._source.ask});
      self.props.onPriceChange(stream._source)
    }).on('error', function(error) {
      console.log('Error handling code');
    });
  },
  render: function(){
    return (

BID

              {this.state.bid}

LAST

{this.state.last}

AVG OF 24 HRS

{this.state.avg}

TOTAL

{this.state.total}

ASK

{this.state.ask}

   Listening

    )
  }
});
module.exports = Stats;
<commit_msg>Show bitcoin price from historical data<commit_after>var React = require("react");
var appbaseRef = require("./appbase").appbaseRef;
var config = require("./appbase").config;

var requestObject = {
  type: config.type,
  body: {
    size: 1,
    query: {
      match_all: {}
    }
  }
};

var Stats = React.createClass({

  getInitialState: function(){
    return {
      bid: "0",
      last: "0",
      avg: "0",
      total: "0",
      ask: "0"
    };
  },
  componentDidMount: function(){
    var self = this;
    appbaseRef.search(requestObject).on('data', function(res) {
      self.updatePrice(res.hits.hits[0]._source)
      appbaseRef.searchStream(requestObject).on('data', function(stream) {
        self.updatePrice(stream._source)
      }).on('error', function(error) {
        console.log('Error handling code');
      });
    })
  },
  updatePrice: function(data){
    this.setState({bid: data.bid});
    this.setState({last: data.last});
    this.setState({avg: data['24h_avg']});
    this.setState({total: data.total_vol});
    this.setState({ask: data.ask});
    this.props.onPriceChange(data)
  },
  render: function(){
    return (

BID

              {this.state.bid}

LAST

{this.state.last}

AVG OF 24 HRS

{this.state.avg}

TOTAL

{this.state.total}

ASK

{this.state.ask}

   Listening

    )
  }
});
module.exports = Stats;
<|endoftext|>


<commit_before>/*global window*/

(function (window) {
    "use strict";

    var di = {};

    // ********************************************** Cache

    di.createCache = function () {
        var cache = {}, entries = {};

        cache.acquire = function (prop, params) {
            var key, value;
            key = prop + (params ? "::" + (params.join ? params.join(",") : params) : "");
            value = entries[key];

            return {
                exists: function () {
                    return value !== undefined;
                },
                hasValue: function () {
                    return value;
                },
                getValue: function () {
                    return value;
                },
                setValue: function (o) {
                    value = o;
                    entries[key] = value;
                }
            };
        };

        return cache;
    };

    // ********************************************** Injector

    di.createInjector = function (mappings) {
        var injector = {}, cache = di.createCache();

        injector.canPlaceholderBeInjected = function (placeholder) {
            return placeholder === undefined || placeholder === null || typeof placeholder.inject === "string";
        };

        injector.injectIntoTarget = function (target) {
            var propName;

            if (!target)
                return;

            for (propName in target) {
                if (target.hasOwnProperty(propName)) {
                    injector.injectIntoProperty(target, propName);
                }
            }
        };

        injector.getOrCreateDependency = function (target, source, params) {
            var mapType = typeof source, dependency;

            if (mapType === "function") {
                dependency = source.apply(target, Array.isArray(params) ? params : [params]);
            } else if (mapType === "object" && source !== null) {
                dependency = source;
            }

            if (typeof dependency === "object" && dependency !== null) {
                injector.injectIntoTarget(dependency, mappings);
                return dependency;
            }

            return null;
        };

        injector.injectIntoProperty = function (target, prop) {
            var placeholder, dependency, params;

            placeholder = target[prop];

            if (injector.canPlaceholderBeInjected(placeholder)) {

                params = placeholder ? placeholder.ctor : undefined;
                var entry = cache.acquire(prop, params);

                if (!entry.exists()) {
                    dependency = injector.getOrCreateDependency(target, mappings[prop], params);

                    entry.setValue(dependency);
                }

                if (entry.hasValue())
                    target[prop] = entry.getValue();
            }
        };

        return injector;
    };

    // ********************************************** Kernel

    di.createKernel = function () {
        var kernel = {}, mappings = {};

        kernel.inject = function (target) {
            var injector = di.createInjector(mappings);

            injector.injectIntoTarget(target);

            return target;
        };

        kernel.create = function (name, params) {
            var injector = di.createInjector(mappings);

            var result = injector.getOrCreateDependency(this, mappings[name], params);

            return result;
        };

        kernel.map = function (name) {
            return {
                to: function (target) {
                    mappings[name] = target;
                }
            };
        };

        kernel.get = function (name) {
            return mappings[name];
        };

        return kernel;
    };

    // ********************************************** Registering
    window.di = di;
} (window));<commit_msg>Small adjustments for jslint<commit_after>/*global window*/

(function (window) {
    "use strict";

    var di = {};

    // ********************************************** Cache

    di.createCache = function () {
        var cache = {}, entries = {};

        cache.acquire = function (prop, params) {
            var key, value;
            key = prop + (params ? "::" + (params.join ? params.join(",") : params) : "");
            value = entries[key];

            return {
                exists: function () {
                    return value !== undefined;
                },
                hasValue: function () {
                    return value;
                },
                getValue: function () {
                    return value;
                },
                setValue: function (o) {
                    value = o;
                    entries[key] = value;
                }
            };
        };

        return cache;
    };

    // ********************************************** Injector

    di.createInjector = function (mappings) {
        var injector = {}, cache = di.createCache();

        injector.canPlaceholderBeInjected = function (placeholder) {
            return placeholder === undefined || placeholder === null || typeof placeholder.inject === "string";
        };

        injector.injectIntoTarget = function (target) {
            var propName;

            if (!target) {
                return;
            }

            for (propName in target) {
                if (target.hasOwnProperty(propName)) {
                    injector.injectIntoProperty(target, propName);
                }
            }
        };

        injector.getOrCreateDependency = function (target, source, params) {
            var mapType = typeof source, dependency;

            if (mapType === "function") {
                dependency = source.apply(target, Array.isArray(params) ? params : [params]);
            } else if (mapType === "object" && source !== null) {
                dependency = source;
            }

            if (typeof dependency === "object" && dependency !== null) {
                injector.injectIntoTarget(dependency, mappings);
                return dependency;
            }

            return null;
        };

        injector.injectIntoProperty = function (target, prop) {
            var placeholder, dependency, params, entry;

            placeholder = target[prop];

            if (injector.canPlaceholderBeInjected(placeholder)) {

                params = placeholder ? placeholder.ctor : undefined;
                entry = cache.acquire(prop, params);

                if (!entry.exists()) {
                    dependency = injector.getOrCreateDependency(target, mappings[prop], params);

                    entry.setValue(dependency);
                }

                if (entry.hasValue()) {
                    target[prop] = entry.getValue();
                }
            }
        };

        return injector;
    };

    // ********************************************** Kernel

    di.createKernel = function () {
        var kernel = {}, mappings = {};

        kernel.inject = function (target) {
            di.createInjector(mappings).injectIntoTarget(target);

            return target;
        };

        kernel.create = function (name, params) {
            return di.createInjector(mappings).getOrCreateDependency(this, mappings[name], params);
        };

        kernel.map = function (name) {
            return {
                to: function (target) {
                    mappings[name] = target;
                }
            };
        };

        kernel.get = function (name) {
            return mappings[name];
        };

        return kernel;
    };

    // ********************************************** Registering
    window.di = di;
}(window));<|endoftext|>


<commit_before>(function($)
{
//	Some vars
	$field = $('li[data-type="attr"]');
	attrSelector = 'input[data-associative="attr"]';
	label = 'label';

//	Make sortable
	$field.sortable(
	{
		handle: '> .handle',
		items: 'ol.data>li',
		axis: 'y',
		containment: 'parent',
	});

//	Some binds the key
	$field.off('input', 'input[data-associative="attr"]').on('input', 'input[data-associative="attr"]', function()
	{
	//	Get the value
	//	TODO @sf trouver meilleure manière d'empêcher les clef de contenir des accents, espaces...
		input = $(this);
		key = input.val().replace(/ /g,'').toLowerCase();

	//	Have the field names follow the value
	//	TODO @mvd find a best way to catch the second <li> in the tree
		li = input.parent().parent().parent().parent();

	//	Change field names
		li.find('input, select, textarea').each(function()
		{
			input = $(this);
			name = input.attr('name');
			input.attr('name', name.replace(/\[attr\]\[[a-z0-9_]*\]/i, "[attr]["+key+"]"));
		});
	});

//	Expand / Collapse attr
	$field.on('click', label, function()
	{
		$(this).closest('ol').find('>li:not([data-key="key"], [data-key="type"])').toggle();
	});
})(jQuery); <commit_msg>fieldAttr : accept comma in attribute key<commit_after>(function($)
{
//	Some vars
	$field = $('li[data-type="attr"]');
	attrSelector = 'input[data-associative="attr"]';
	label = 'label';

//	Make sortable
	$field.sortable(
	{
		handle: '> .handle',
		items: 'ol.data>li',
		axis: 'y',
		containment: 'parent',
	});

//	Some binds the key
	$field.off('input', 'input[data-associative="attr"]').on('input', 'input[data-associative="attr"]', function()
	{
	//	Get the value
	//	TODO @sf trouver meilleure manière d'empêcher les clef de contenir des accents, espaces...
		input = $(this);
		key = input.val().replace(/ /g,'').toLowerCase();

	//	Have the field names follow the value
	//	TODO @mvd find a best way to catch the second <li> in the tree
		li = input.parent().parent().parent().parent();

	//	Change field names
		li.find('input, select, textarea').each(function()
		{
			input = $(this);
			name = input.attr('name');
			input.attr('name', name.replace(/\[attr\]\[[a-z0-9_-]*\]/i, "[attr]["+key+"]"));
		});
	});

//	Expand
	$field.on('click', label, function()
	{
		$(this).closest('ol').find('li:not(:first-child)').toggle('fast');
	});
})(jQuery); <|endoftext|>


<commit_before>var messy = require('messy'),
    _ = require('underscore'),
    isRegExp = require('./isRegExp');

function isTextualContentType(contentType) {
    if (typeof contentType === 'string') {
        contentType = contentType.toLowerCase().trim().replace(/\s*;.*$/, '');
        return (
            /^text\//.test(contentType) ||
            /^application\/(json|javascript)$/.test(contentType) ||
            /^application\/xml/.test(contentType) ||
            /^application\/x-www-form-urlencoded\b/.test(contentType) ||
            /\+xml$/.test(contentType)
        );
    }
    return false;
}

function bufferCanBeInterpretedAsUtf8(buffer) {
    // Hack: Since Buffer.prototype.toString('utf-8') is very forgiving, convert the buffer to a string
    // with percent-encoded octets, then see if decodeURIComponent accepts it.
    try {
        decodeURIComponent(Array.prototype.map.call(buffer, function (octet) {
            return '%' + (octet < 16 ? '0' : '') + octet.toString(16);
        }).join(''));
    } catch (e) {
        return false;
    }
    return true;
}

function expectMessageToSatisfy(expect, subject, value) {
    if (typeof value === 'string') {
        value = new messy.Message(value);
    }
    if ('headers' in value) {
        expect(subject.headers, 'to [exhaustively] satisfy', value.headers);
    } else if (this.flags.exhaustive) {
        expect(subject.headers.getNames(), 'to be empty');
    }

    subject.upgradeOrDowngradeBodyToMatchSpec(value);
    if ('body' in value) {
        if (isRegExp(value.body)) {
            expect(subject.body, 'to satisfy', value.body);
        } else {
            expect(subject.body, 'to equal', value.body);
        }
    } else if (this.flags.exhaustive) {
        expect(subject.body, 'to be undefined');
    }
}

module.exports = function unexpectedMessy(expect) {
    expect.addType({
        name: 'messy.Headers',
        identify: function (obj) {
            return obj instanceof messy.Headers;
        },
        equal: function (headers1, headers2) {
            return headers1.equals(headers2);
        },
        inspect: function (output, headers, inspect) {
            headers.getNames().forEach(function (headerName) {
                headers.valuesByName[headerName].forEach(function (headerValue) {
                    output
                        .text(messy.formatHeaderName(headerName + ':'), 'gray')
                        .text(' ')
                        .text(headerValue, 'cyan')
                        .nl();
                });
            });
            return output;
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            function outputHeader(name, value) {
                return (
                    output
                        .text(messy.formatHeaderName(name) + ':', 'gray')
                        .text(' ')
                        .text(value, 'cyan')
                );
            }

            var remainingExpectedHeaders = expected.clone(),
                remainingActualHeaders = actual.clone();

            actual.getNames().forEach(function (headerName) {
                // First pass: Find exact matches
                var actualHeaderValues = actual.getAll(headerName),
                    i;
                for (i = 0 ; i < actualHeaderValues.length ; i += 1) {
                    var actualHeaderValue = actualHeaderValues[i];
                    if (remainingExpectedHeaders.remove(headerName, actualHeaderValue)) {
                        remainingActualHeaders.remove(headerName, actualHeaderValue);
                        outputHeader(headerName, actualHeaderValue).nl();
                    }
                }
                // Second pass: Find changed headers
                actualHeaderValues = remainingActualHeaders.getAll(headerName);
                if (actualHeaderValues) {
                    for (i = 0 ; i < actualHeaderValues.length ; i += 1) {
                        outputHeader(headerName, actualHeaderValues[i]);
                        if (remainingExpectedHeaders.has(headerName)) {
                            var expectedHeaderValue = remainingExpectedHeaders.get(headerName);
                            output.sp().error('// should be: ').text(expectedHeaderValue).nl();
                            remainingExpectedHeaders.remove(headerName, expectedHeaderValue);
                        } else {
                            output.sp().error('// should be removed').nl();
                        }
                    }
                }
            });
            // All the headers remaining in remainingExpectedHeaders are missing:
            remainingExpectedHeaders.getNames().forEach(function (headerName) {
                remainingExpectedHeaders.getAll(headerName).forEach(function (headerValue) {
                    output.error('// missing: ');
                    outputHeader(headerName, headerValue).nl();
                });
            });
            return {
                diff: output
            };
        }
    }).addAssertion('messy.Headers', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if (isRegExp(value)) {
            expect(subject.toString(), 'to match', value);
        } else {
            var exhaustively = this.flags.exhaustively;
            if (value && typeof value === 'object' && !(value instanceof messy.Headers)) {
                expect(value, 'to be a hash whose keys satisfy', function (headerName) {
                    expect(typeof value[headerName] !== 'undefined' || !subject.has(headerName), 'to be true');
                });
            }

            var expectedHeaders = value instanceof messy.Headers ? value : new messy.Headers(value, true),
                expectedHeaderNames = expectedHeaders.getNames();

            if (exhaustively) {
                expect(expectedHeaderNames.length, 'not to be less than', subject.getNames().length);
            }
            expect(expectedHeaderNames, 'to be an array whose items satisfy', function (expectedHeaderName) {
                var expectedValues = expectedHeaders.getAll(expectedHeaderName);
                if (exhaustively) {
                    expect(subject.getAll(expectedHeaderName).sort(), 'to equal', expectedValues.sort());
                } else {
                    expect(subject.has(expectedHeaderName, expectedValues), 'to be ok');
                }
            });
        }
    }).addType({
        name: 'messy.Message',
        identify: function (obj) {
            return obj instanceof messy.Message;
        },
        equal: function (message1, message2) {
            return message1.equals(message2);
        },
        inspect: function (output, message, inspect) {
            if (message.headers.getNames().length > 0) {
                inspect(output, message.headers);
            }
            if (typeof message.body !== 'undefined') {
                message.upgradeBody();
                output.nl();
                if (typeof message.body === 'string') {
                    output.text(message.body);
                } else {
                    // Buffer instance or parsed JSON
                    inspect(message.body);
                }
            }
            return output;
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output.append(diff(actual.headers, expected.headers).diff);

            output.nl();

            if (typeof expected.body !== 'undefined') {
                var isJson = /^application\/json\b|\+json/i.test(expected.headers.get('Content-Type'));
                if ((typeof Buffer === 'function' && Buffer.isBuffer(expected.body)) && isTextualContentType(expected.headers.get('Content-Type')) && bufferCanBeInterpretedAsUtf8(expected.body)) {
                    // TODO: Avoid mutating?
                    expected.body = expected.body.toString('utf-8');
                }
                if (isJson && typeof expected.body === 'string') {
                    try {
                        expected.body = JSON.parse(expected.body);
                    } catch (e) {
                        // The body cannot be parsed as JSON, keep as a string instance
                    }
                }
            }
            expected.upgradeBody();

            actual.upgradeOrDowngradeBodyToMatchSpec(expected.body);

            var bodyDiff = diff(actual.body, expected.body);
            if (bodyDiff) {
                output.append(bodyDiff.diff);
            } else {
                if (typeof actual.body === 'string') {
                    var contentType = actual.headers.get('Content-Type');
                    if (contentType) {
                        var language = contentType.replace(/\s*;.*$/, ''); // Strip charset etc.
                        output.code(actual.body, language);
                    } else {
                        output.text(actual.body);
                    }
                } else {
                    // Buffer instance or parsed JSON
                    output.append(inspect(actual.body));
                }
            }

            return {
                diff: output
            };
        }

    }).addAssertion('messy.Message', 'to [exhaustively] satisfy', expectMessageToSatisfy)
    .addType({
        name: 'messy.RequestLine',
        identify: function (obj) {
            return obj instanceof messy.RequestLine;
        },
        equal: function (requestLine1, requestLine2) {
            return requestLine1.equals(requestLine2);
        },
        inspect: function (output, requestLine, inspect) {
            return (output
                .text(requestLine.method, 'blue')
                .text(' ')
                .text(requestLine.url, 'gray')
                .text(' ')
                .text(requestLine.protocolName, 'blue')
                .text('/')
                .text(requestLine.protocolVersion, 'cyan')
            );
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output.append(inspect(actual));
            if (!equal(actual, expected)) {
                var shouldBeFragments = [];
                ['method', 'url', 'protocol'].forEach(function (propertyName) {
                    if (shouldBeFragments.length > 0 || actual[propertyName] !== expected[propertyName]) {
                        shouldBeFragments.push(expected[propertyName]);
                    }
                });
                output.sp().error('// should be').sp().text(shouldBeFragments.join(' '));
            }
            return {
                diff: output
            };
        }
    }).addAssertion('messy.RequestLine', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if (value && typeof value === 'object') {
            expect(_.pick(subject, messy.RequestLine.propertyNames), 'to [exhaustively] satisfy', value);
        } else {
            expect(subject.toString(), 'to [exhaustively] satisfy', value);
        }
    }).addType({
        name: 'messy.HttpRequest',
        base: 'messy.Message',
        identify: function (obj) {
            return obj instanceof messy.HttpRequest;
        },
        equal: function (httpRequest1, httpRequest2) {
            return httpRequest1.equals(httpRequest2);
        },
        inspect: function (output, httpRequest, inspect) {
            return output.append(inspect(httpRequest.requestLine))
                .nl()
                .append(this.baseType.inspect(output, httpRequest, inspect)); //MJMMMMMMMMMMM
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output
                .append(diff(actual.requestLine, expected.requestLine).diff)
                .nl()
                .append(this.baseType.diff(output.clone(), actual, expected, diff, inspect, equal).diff); // HMMMMMMMMMMMM

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpRequest', 'to [exhaustively] satisfy', function (expect, subject, value) {
        expectMessageToSatisfy.call(this, expect, subject, value);
        if ('requestLine' in value) {
            expect(subject.requestLine, 'to [exhaustively] satisfy', value.requestLine);
        }
        // Make the RequestLine properties available for matching:
        expect(subject.requestLine, 'to [exhaustively] satisfy', _.pick(value, messy.RequestLine.propertyNames));
    }).addType({
        name: 'messy.StatusLine',
        identify: function (obj) {
            return obj instanceof messy.StatusLine;
        },
        equal: function (statusLine1, statusLine2) {
            return statusLine1.equals(statusLine2);
        },
        inspect: function (output, statusLine, inspect) {
            return (output
                .text(statusLine.protocolName, 'blue')
                .text('/')
                .text(statusLine.protocolVersion, 'cyan')
                .text(' ')
                .text(statusLine.statusCode, 'cyan')
                .text(' ')
                .text(statusLine.statusMessage, 'yellow')
            );
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output.append(inspect(actual));
            if (!equal(actual, expected)) {
                var shouldBeFragments = [];
                ['protocol', 'statusCode', 'statusMessage'].forEach(function (propertyName) {
                    if (shouldBeFragments.length > 0 || actual[propertyName] !== expected[propertyName]) {
                        shouldBeFragments.push(expected[propertyName]);
                    }
                });
                output.sp().error('// should be').sp().text(shouldBeFragments.join(' '));
            }
            return {
                diff: output
            };
        }
    }).addAssertion('messy.StatusLine', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if (value && typeof value === 'object') {
            expect(_.pick(subject, messy.StatusLine.propertyNames), 'to [exhaustively] satisfy', value);
        } else {
            expect(subject.toString(), 'to [exhaustively] satisfy', value);
        }
    }).addType({
        name: 'messy.HttpResponse',
        base: 'messy.Message',
        equal: function (httpResponse1, httpResponse2) {
            return httpResponse1.equals(httpResponse2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpResponse;
        },
        inspect: function (output, httpResponse, inspect) {
            return output.append(inspect(httpResponse.statusLine))
                .nl()
                .append(this.baseType.inspect(output, httpResponse, inspect)); // HMMMMMMMMMMMM
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output
                .append(diff(actual.statusLine, expected.statusLine).diff)
                .nl()
                .append(this.baseType.diff(output.clone(), actual, expected, diff, inspect, equal).diff); // HMMMMMMMMMMMM

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpResponse', 'to [exhaustively] satisfy', function (expect, subject, value) {
        expectMessageToSatisfy.call(this, expect, subject, value);

        if ('statusLine' in value) {
            expect(subject.statusLine, 'to [exhaustively] satisfy', value.statusLine);
        }
        // Make the RequestLine properties available for matching:
        expect(subject.statusLine, 'to [exhaustively] satisfy', _.pick(value, messy.StatusLine.propertyNames));
    }).addType({
        name: 'messy.HttpExchange',
        equal: function (httpExchange1, httpExchange2) {
            return httpExchange1.equals(httpExchange2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpExchange;
        },
        inspect: function (output, httpExchange, inspect) {
            return output.append(inspect(httpExchange.request))
                .nl(2)
                .append(httpExchange.response);
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output
                .append(diff(actual.request, expected.request).diff)
                .nl(2)
                .append(diff(actual.response, expected.response).diff);

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpExchange', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if ('request' in value) {
            expect(subject.request, 'to [exhaustively] satisfy', value.request);
        } else {
            expect(this.flags.exhaustively, 'to be false');
        }
        if ('response' in value) {
            expect(subject.response, 'to [exhaustively] satisfy', value.response);
        } else {
            expect(this.flags.exhaustively, 'to be false');
        }
    }).addType({
        name: 'messy.HttpConversation',
        equal: function (httpConversation1, httpConversation2) {
            return httpConversation1.equals(httpConversation2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpConversation;
        },
        inspect: function (output, httpConversation, inspect) {
            httpConversation.exchanges.forEach(function (httpExchange, i) {
                if (i > 0) {
                    output.nl(2);
                }
                inspect(output, httpExchange);
            });
            return output;
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            for (var i = 0 ; i < Math.max(actual.exchanges.length, expected.exchanges.length) ; i += 1) {
                if (i < actual.exchanges.length && i < expected.exchanges.length) {
                    if (i > 0) {
                        output.nl(2);
                    }
                    output
                        .append(diff(actual.exchanges[i], expected.exchanges[i]).diff);
                } else {
                    throw new Error('Not implemented: diffing conversations with a different number of exchanges');
                }
            }

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpConversation', 'to [exhaustively] satisfy', function (expect, subject, value) {
        expect(value, 'to be an object');
        expect(value.exchanges, 'to be an array');
        expect(subject.exchanges, 'to be an array whose items satisfy', function (httpExchange, i) {
            expect(httpExchange, 'to [exhaustively] satisfy', value.exchanges[i]);
        });
        expect(subject.exchanges.length, 'to equal', value.exchanges.length);
    });
};
<commit_msg>Fix diffing of messy.Message bodies.<commit_after>var messy = require('messy'),
    _ = require('underscore'),
    isRegExp = require('./isRegExp');

function isTextualContentType(contentType) {
    if (typeof contentType === 'string') {
        contentType = contentType.toLowerCase().trim().replace(/\s*;.*$/, '');
        return (
            /^text\//.test(contentType) ||
            /^application\/(json|javascript)$/.test(contentType) ||
            /^application\/xml/.test(contentType) ||
            /^application\/x-www-form-urlencoded\b/.test(contentType) ||
            /\+xml$/.test(contentType)
        );
    }
    return false;
}

function bufferCanBeInterpretedAsUtf8(buffer) {
    // Hack: Since Buffer.prototype.toString('utf-8') is very forgiving, convert the buffer to a string
    // with percent-encoded octets, then see if decodeURIComponent accepts it.
    try {
        decodeURIComponent(Array.prototype.map.call(buffer, function (octet) {
            return '%' + (octet < 16 ? '0' : '') + octet.toString(16);
        }).join(''));
    } catch (e) {
        return false;
    }
    return true;
}

function expectMessageToSatisfy(expect, subject, value) {
    if (typeof value === 'string') {
        value = new messy.Message(value);
    }
    if ('headers' in value) {
        expect(subject.headers, 'to [exhaustively] satisfy', value.headers);
    } else if (this.flags.exhaustive) {
        expect(subject.headers.getNames(), 'to be empty');
    }

    subject.upgradeOrDowngradeBodyToMatchSpec(value);
    if ('body' in value) {
        if (isRegExp(value.body)) {
            expect(subject.body, 'to satisfy', value.body);
        } else {
            expect(subject.body, 'to equal', value.body);
        }
    } else if (this.flags.exhaustive) {
        expect(subject.body, 'to be undefined');
    }
}

module.exports = function unexpectedMessy(expect) {
    expect.addType({
        name: 'messy.Headers',
        identify: function (obj) {
            return obj instanceof messy.Headers;
        },
        equal: function (headers1, headers2) {
            return headers1.equals(headers2);
        },
        inspect: function (output, headers, inspect) {
            headers.getNames().forEach(function (headerName) {
                headers.valuesByName[headerName].forEach(function (headerValue) {
                    output
                        .text(messy.formatHeaderName(headerName + ':'), 'gray')
                        .text(' ')
                        .text(headerValue, 'cyan')
                        .nl();
                });
            });
            return output;
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            function outputHeader(name, value) {
                return (
                    output
                        .text(messy.formatHeaderName(name) + ':', 'gray')
                        .text(' ')
                        .text(value, 'cyan')
                );
            }

            var remainingExpectedHeaders = expected.clone(),
                remainingActualHeaders = actual.clone();

            actual.getNames().forEach(function (headerName) {
                // First pass: Find exact matches
                var actualHeaderValues = actual.getAll(headerName),
                    i;
                for (i = 0 ; i < actualHeaderValues.length ; i += 1) {
                    var actualHeaderValue = actualHeaderValues[i];
                    if (remainingExpectedHeaders.remove(headerName, actualHeaderValue)) {
                        remainingActualHeaders.remove(headerName, actualHeaderValue);
                        outputHeader(headerName, actualHeaderValue).nl();
                    }
                }
                // Second pass: Find changed headers
                actualHeaderValues = remainingActualHeaders.getAll(headerName);
                if (actualHeaderValues) {
                    for (i = 0 ; i < actualHeaderValues.length ; i += 1) {
                        outputHeader(headerName, actualHeaderValues[i]);
                        if (remainingExpectedHeaders.has(headerName)) {
                            var expectedHeaderValue = remainingExpectedHeaders.get(headerName);
                            output.sp().error('// should be: ').text(expectedHeaderValue).nl();
                            remainingExpectedHeaders.remove(headerName, expectedHeaderValue);
                        } else {
                            output.sp().error('// should be removed').nl();
                        }
                    }
                }
            });
            // All the headers remaining in remainingExpectedHeaders are missing:
            remainingExpectedHeaders.getNames().forEach(function (headerName) {
                remainingExpectedHeaders.getAll(headerName).forEach(function (headerValue) {
                    output.error('// missing: ');
                    outputHeader(headerName, headerValue).nl();
                });
            });
            return {
                diff: output
            };
        }
    }).addAssertion('messy.Headers', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if (isRegExp(value)) {
            expect(subject.toString(), 'to match', value);
        } else {
            var exhaustively = this.flags.exhaustively;
            if (value && typeof value === 'object' && !(value instanceof messy.Headers)) {
                expect(value, 'to be a hash whose keys satisfy', function (headerName) {
                    expect(typeof value[headerName] !== 'undefined' || !subject.has(headerName), 'to be true');
                });
            }

            var expectedHeaders = value instanceof messy.Headers ? value : new messy.Headers(value, true),
                expectedHeaderNames = expectedHeaders.getNames();

            if (exhaustively) {
                expect(expectedHeaderNames.length, 'not to be less than', subject.getNames().length);
            }
            expect(expectedHeaderNames, 'to be an array whose items satisfy', function (expectedHeaderName) {
                var expectedValues = expectedHeaders.getAll(expectedHeaderName);
                if (exhaustively) {
                    expect(subject.getAll(expectedHeaderName).sort(), 'to equal', expectedValues.sort());
                } else {
                    expect(subject.has(expectedHeaderName, expectedValues), 'to be ok');
                }
            });
        }
    }).addType({
        name: 'messy.Message',
        identify: function (obj) {
            return obj instanceof messy.Message;
        },
        equal: function (message1, message2) {
            return message1.equals(message2);
        },
        inspect: function (output, message, inspect) {
            if (message.headers.getNames().length > 0) {
                inspect(output, message.headers);
            }
            if (typeof message.body !== 'undefined') {
                message.upgradeBody();
                output.nl();
                if (typeof message.body === 'string') {
                    output.text(message.body);
                } else {
                    // Buffer instance or parsed JSON
                    inspect(message.body);
                }
            }
            return output;
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output.append(diff(actual.headers, expected.headers).diff);

            output.nl();

            if (typeof expected.body !== 'undefined') {
                var isJson = /^application\/json\b|\+json/i.test(expected.headers.get('Content-Type'));
                if ((typeof Buffer === 'function' && Buffer.isBuffer(expected.body)) && isTextualContentType(expected.headers.get('Content-Type')) && bufferCanBeInterpretedAsUtf8(expected.body)) {
                    // TODO: Avoid mutating?
                    expected.body = expected.body.toString('utf-8');
                }
                if (isJson && typeof expected.body === 'string') {
                    try {
                        expected.body = JSON.parse(expected.body);
                    } catch (e) {
                        // The body cannot be parsed as JSON, keep as a string instance
                    }
                }
            }
            expected.upgradeBody();

            actual.upgradeOrDowngradeBodyToMatchSpec(expected);

            var bodyDiff = diff(actual.body, expected.body);
            if (bodyDiff) {
                output.append(bodyDiff.diff);
            } else {
                if (typeof actual.body === 'string') {
                    var contentType = actual.headers.get('Content-Type');
                    if (contentType) {
                        var language = contentType.replace(/\s*;.*$/, ''); // Strip charset etc.
                        output.code(actual.body, language);
                    } else {
                        output.text(actual.body);
                    }
                } else {
                    // Buffer instance or parsed JSON
                    output.append(inspect(actual.body));
                }
            }

            return {
                diff: output
            };
        }

    }).addAssertion('messy.Message', 'to [exhaustively] satisfy', expectMessageToSatisfy)
    .addType({
        name: 'messy.RequestLine',
        identify: function (obj) {
            return obj instanceof messy.RequestLine;
        },
        equal: function (requestLine1, requestLine2) {
            return requestLine1.equals(requestLine2);
        },
        inspect: function (output, requestLine, inspect) {
            return (output
                .text(requestLine.method, 'blue')
                .text(' ')
                .text(requestLine.url, 'gray')
                .text(' ')
                .text(requestLine.protocolName, 'blue')
                .text('/')
                .text(requestLine.protocolVersion, 'cyan')
            );
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output.append(inspect(actual));
            if (!equal(actual, expected)) {
                var shouldBeFragments = [];
                ['method', 'url', 'protocol'].forEach(function (propertyName) {
                    if (shouldBeFragments.length > 0 || actual[propertyName] !== expected[propertyName]) {
                        shouldBeFragments.push(expected[propertyName]);
                    }
                });
                output.sp().error('// should be').sp().text(shouldBeFragments.join(' '));
            }
            return {
                diff: output
            };
        }
    }).addAssertion('messy.RequestLine', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if (value && typeof value === 'object') {
            expect(_.pick(subject, messy.RequestLine.propertyNames), 'to [exhaustively] satisfy', value);
        } else {
            expect(subject.toString(), 'to [exhaustively] satisfy', value);
        }
    }).addType({
        name: 'messy.HttpRequest',
        base: 'messy.Message',
        identify: function (obj) {
            return obj instanceof messy.HttpRequest;
        },
        equal: function (httpRequest1, httpRequest2) {
            return httpRequest1.equals(httpRequest2);
        },
        inspect: function (output, httpRequest, inspect) {
            return output.append(inspect(httpRequest.requestLine))
                .nl()
                .append(this.baseType.inspect(output, httpRequest, inspect)); //MJMMMMMMMMMMM
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output
                .append(diff(actual.requestLine, expected.requestLine).diff)
                .nl()
                .append(this.baseType.diff(output.clone(), actual, expected, diff, inspect, equal).diff); // HMMMMMMMMMMMM

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpRequest', 'to [exhaustively] satisfy', function (expect, subject, value) {
        expectMessageToSatisfy.call(this, expect, subject, value);
        if ('requestLine' in value) {
            expect(subject.requestLine, 'to [exhaustively] satisfy', value.requestLine);
        }
        // Make the RequestLine properties available for matching:
        expect(subject.requestLine, 'to [exhaustively] satisfy', _.pick(value, messy.RequestLine.propertyNames));
    }).addType({
        name: 'messy.StatusLine',
        identify: function (obj) {
            return obj instanceof messy.StatusLine;
        },
        equal: function (statusLine1, statusLine2) {
            return statusLine1.equals(statusLine2);
        },
        inspect: function (output, statusLine, inspect) {
            return (output
                .text(statusLine.protocolName, 'blue')
                .text('/')
                .text(statusLine.protocolVersion, 'cyan')
                .text(' ')
                .text(statusLine.statusCode, 'cyan')
                .text(' ')
                .text(statusLine.statusMessage, 'yellow')
            );
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output.append(inspect(actual));
            if (!equal(actual, expected)) {
                var shouldBeFragments = [];
                ['protocol', 'statusCode', 'statusMessage'].forEach(function (propertyName) {
                    if (shouldBeFragments.length > 0 || actual[propertyName] !== expected[propertyName]) {
                        shouldBeFragments.push(expected[propertyName]);
                    }
                });
                output.sp().error('// should be').sp().text(shouldBeFragments.join(' '));
            }
            return {
                diff: output
            };
        }
    }).addAssertion('messy.StatusLine', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if (value && typeof value === 'object') {
            expect(_.pick(subject, messy.StatusLine.propertyNames), 'to [exhaustively] satisfy', value);
        } else {
            expect(subject.toString(), 'to [exhaustively] satisfy', value);
        }
    }).addType({
        name: 'messy.HttpResponse',
        base: 'messy.Message',
        equal: function (httpResponse1, httpResponse2) {
            return httpResponse1.equals(httpResponse2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpResponse;
        },
        inspect: function (output, httpResponse, inspect) {
            return output.append(inspect(httpResponse.statusLine))
                .nl()
                .append(this.baseType.inspect(output, httpResponse, inspect)); // HMMMMMMMMMMMM
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output
                .append(diff(actual.statusLine, expected.statusLine).diff)
                .nl()
                .append(this.baseType.diff(output.clone(), actual, expected, diff, inspect, equal).diff); // HMMMMMMMMMMMM

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpResponse', 'to [exhaustively] satisfy', function (expect, subject, value) {
        expectMessageToSatisfy.call(this, expect, subject, value);

        if ('statusLine' in value) {
            expect(subject.statusLine, 'to [exhaustively] satisfy', value.statusLine);
        }
        // Make the RequestLine properties available for matching:
        expect(subject.statusLine, 'to [exhaustively] satisfy', _.pick(value, messy.StatusLine.propertyNames));
    }).addType({
        name: 'messy.HttpExchange',
        equal: function (httpExchange1, httpExchange2) {
            return httpExchange1.equals(httpExchange2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpExchange;
        },
        inspect: function (output, httpExchange, inspect) {
            return output.append(inspect(httpExchange.request))
                .nl(2)
                .append(httpExchange.response);
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            output
                .append(diff(actual.request, expected.request).diff)
                .nl(2)
                .append(diff(actual.response, expected.response).diff);

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpExchange', 'to [exhaustively] satisfy', function (expect, subject, value) {
        if ('request' in value) {
            expect(subject.request, 'to [exhaustively] satisfy', value.request);
        } else {
            expect(this.flags.exhaustively, 'to be false');
        }
        if ('response' in value) {
            expect(subject.response, 'to [exhaustively] satisfy', value.response);
        } else {
            expect(this.flags.exhaustively, 'to be false');
        }
    }).addType({
        name: 'messy.HttpConversation',
        equal: function (httpConversation1, httpConversation2) {
            return httpConversation1.equals(httpConversation2);
        },
        identify: function (obj) {
            return obj instanceof messy.HttpConversation;
        },
        inspect: function (output, httpConversation, inspect) {
            httpConversation.exchanges.forEach(function (httpExchange, i) {
                if (i > 0) {
                    output.nl(2);
                }
                inspect(output, httpExchange);
            });
            return output;
        },
        diff: function (output, actual, expected, diff, inspect, equal) {
            for (var i = 0 ; i < Math.max(actual.exchanges.length, expected.exchanges.length) ; i += 1) {
                if (i < actual.exchanges.length && i < expected.exchanges.length) {
                    if (i > 0) {
                        output.nl(2);
                    }
                    output
                        .append(diff(actual.exchanges[i], expected.exchanges[i]).diff);
                } else {
                    throw new Error('Not implemented: diffing conversations with a different number of exchanges');
                }
            }

            return {
                diff: output
            };
        }
    }).addAssertion('messy.HttpConversation', 'to [exhaustively] satisfy', function (expect, subject, value) {
        expect(value, 'to be an object');
        expect(value.exchanges, 'to be an array');
        expect(subject.exchanges, 'to be an array whose items satisfy', function (httpExchange, i) {
            expect(httpExchange, 'to [exhaustively] satisfy', value.exchanges[i]);
        });
        expect(subject.exchanges.length, 'to equal', value.exchanges.length);
    });
};
<|endoftext|>