<commit_before>// Parse SRC HTML (input format) to AST
//
// data:
// - input - SRC HTML (String)
// - output - AST with methods .html(), .text()
// - options:
//   - baseUrl - the URL or array of the URLs (example: [ 'nodeca.com', 'www.nodeca.com' ])
//   - cleanupRules - rules to cleanup HTML
//   - smiles - rules to parse smiles
//   - medialinkProviders - rules to parse medialink
//
//

'use strict';

var $     = require('./cheequery');
var _     = require('lodash');
var async = require('async');

var cleanupRules;
var smilesRules;
var medialinkProviders;

// Compile cleanup rules
//
var compileCleanupRules = _.memoize(function(cleanupRules) {
  var rules = _.cloneDeep(cleanupRules);
  var regExpParse = /^\/(.*?)\/(g?i?m?y?)$/;

  var groups = {};
  var all = Object.keys(rules);

  // Extract groups
  _.forEach(rules, function (rule, tag) {
    if (rule.group) {
      groups[rule.group] = groups[rule.group] || [];
      groups[rule.group].push(tag);
    }
  });

  _.forEach(rules, function (rule) {

    // Prepare attributes
    var attributes = {};

    if (_.isPlainObject(rule.attributes)) {
      var attrs = [];

      _.forEach(rule.attributes, function (val, attr) {
        var attrObj = {};

        attrObj[attr] = val;
        attrs.push(attrObj);
      });
      rule.attributes = attrs;
    }

    if (_.isString(rule.attributes)) {
      rule.attributes = [ rule.attributes ];
    }

    _.forEach(rule.attributes || [], function (val) {
      var attr;

      if (_.isObject(val)) {
        attr = Object.keys(val)[0];
        val = val[attr];
      } else {
        attr = val;
        val = '.*';
      }

      // If val is regexp
      if (val.length > 0 && val[0] === '/') {
        attributes[attr] = new RegExp(
          val.replace(regExpParse, '$1'),
          val.replace(regExpParse, '$2')
        );
        return;
      }

      // If val is enum
      if (_.isArray(val)) {
        val = val.join('|');
      }

      attributes[attr] = new RegExp('^' + val + '$');
    });

    rule.attributes = attributes;

    // Prepare children
    var children = [];

    rule.children = rule.children || [];
    rule.children = _.isArray(rule.children) ? rule.children : [ rule.children ];

    _.forEach(rule.children, function (val) {
      var add = true;

      if (val.length > 0 && val[0] === '!') {
        add = false;
        val = val.substr(1);
      }

      if (val === '*') {

        if (add) {
          children = _.union(children, all);
        } else {
          children = _.difference(children, all);
        }
        return;
      }

      if (val.length > 6 && val.substr(0, 6) === 'group-') {

        if (add) {
          children = _.union(children, groups[val.substr(6)] || []);
        } else {
          children = _.difference(children, groups[val.substr(6)] || []);
        }
        return;
      }

      if (all.indexOf(val) !== -1 || val === 'text') {

        if (add) {
          children = _.union(children, [ val ]);
        } else {
          children = _.difference(children, [ val ]);
        }
        return;
      }
    });

    rule.children = children;
  });

  return rules;
}, JSON.stringify);

// Escape RegExp special characters
// http://stackoverflow.com/questions/3115150
//
function escapeRegexp(text) {
  return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}

// Compile smiles rules
//
var compileSmilesRules = _.memoize(function(smilesConfig) {
  if (Object.keys(smilesConfig).length === 0) {
    return null;
  }

  var smiles = [], pattern;
  var result = {
    templates: {},
    matchRE: null
  };

  _.forEach(smilesConfig, function (val, name) {
    if (!_.isArray(val)) {
      val = [ val ];
    }

    for (var i = 0; i < val.length; i++) {

      smiles.push(val[i]);

      // Create replacement template for each smile
      result.templates[val[i]] = _.template(
        '<span data-nd-type="smile" data-nd-id="<%=id%>" data-nd-src="<%=src%>" class="smiles smile_<%=id%>">',
        {
          id: name,
          src: val[i]
        }
      );
    }
  });

  // Compile regular expression to find all smiles
  pattern = smiles.map(function (smile) { return escapeRegexp(smile); }).join('|');

  result.matchRE = new RegExp('(' + pattern + ')', 'g');

  return result;
}, JSON.stringify);

// Cleanup tag attributes
//
function cleanupAttrs($tag, tagName) {
  _.forEach($tag.attrs(), function (value, name) {
    var attrOptions = cleanupRules[tagName].attributes;

    if (!attrOptions[name] || !attrOptions[name].test(value)) {
      $tag.removeAttr(name);
    }
  });
}

// Cleanup disallowed tags
//
function cleanupTags(parent, tagOptions) {
  if (!tagOptions) {
    return;
  }

  var children = parent.contents();
  var length = children.length;

  for (var i = 0; i < length; i++) {

    var $tag = $(children[i]);
    var tagName = ($tag.prop('tagName') || 'text').toLowerCase();

    // Tag doesn't allowed
    if (tagOptions.children.indexOf(tagName) === -1) {

      if (tagName === 'text' && tagOptions.children.indexOf('p') !== -1) {
        $tag.after('
' + $tag.text() + '
');
      } else {
        $tag.after($tag.contents());
      }

      $tag.remove();

      i--;
      children = parent.contents();
      length = children.length;

      continue;
    }

    cleanupAttrs($tag, tagName);

    cleanupTags($tag, cleanupRules[tagName]);
  }
}

// Replace plain links to tags 'a'
//
function plainLinks2html(ast) {
  var urlRegExp = /(https?:\/\/[^\s'"\)\(<>$]+)/gim;

  var replaceLink = function ($tag) {

    // If text node already in tag 'a' skip it
    if ($tag.closest('a').length !== 0) {
      return;
    }

    var parts = $tag.text().split(urlRegExp);

    // Text node doesn't contains links
    if (parts.length === 1) {
      return;
    }

    for (var i = 0; i < parts.length; i++) {
      if (urlRegExp.test(parts[i])) {

        // Replace links
        $tag.before(_.template(
          '<a href="<%-link%>"><%-text%>',
          {
            link: parts[i],
            // For long links show first 30 symbols
            text: parts[i].length > 30 ? parts[i].substr(0, 30) + '...' : parts[i]
          }
        ));
      } else {

        // Append text parts
        $tag.before($.createTextNode(parts[i]));
      }
    }

    // Remove old text node
    $tag.remove();
  };

  // Find text nodes in root
  ast.textNodes().each(function () {
    replaceLink($(this));
  });

  // Find text nodes in nested elements
  ast.find('*').textNodes().each(function () {
    replaceLink($(this));
  });
}

// Replace smiles by html codes
//
function smiles2html(ast) {

  var replaceSmiles = function ($tag) {
    if (!smilesRules) {
      return;
    }

    var text = $tag.text();
    var positions = [];
    var pos;

    // Search positions of every smile
    while ((pos = smilesRules.matchRE.exec(text)) !== null) {
      positions.push({ smile: pos[1], start: pos.index, end: smilesRules.matchRE.lastIndex });
    }

    var length = positions.length;

    // No smiles found
    if (length === 0) {
      return;
    }

    var lastPos = 0;

    for (var i = 0; i < length; i++) {
      pos = positions[i];

      // Append text part before first smile
      $tag.before($.createTextNode(text.substr(lastPos, pos.start - lastPos)));

      // Append smile
      $tag.before(smilesRules.templates[pos.smile]);

      lastPos = pos.end;

      // Append last text part (only for last position)
      if (i === length - 1) {
        $tag.before($.createTextNode(text.substr(pos.end, text.length - pos.end)));
      }
    }

    // Remove old text node
    $tag.remove();
  };

  // Find text nodes in root
  ast.textNodes().each(function () {
    replaceSmiles($(this));
  });

  // Find text nodes in nested elements
  ast.find('*').textNodes().each(function () {
    replaceSmiles($(this));
  });
}

// Replace medialinks by html codes
//
function medialinks2html(ast, callback) {

  var replace = [];

  ast.find('a').each(function() {
    var tag = $(this);

    _.forEach(medialinkProviders, function (provider) {
      for (var i = 0; i < provider.match.length; i++) {

        if (provider.match[i].test(tag.attr('href'))) {
          replace.push({ provider: provider, tag: tag });
          return false; // break forEach
        }
      }
    });
  });

  async.eachSeries(replace, function (item, next) {
    item.provider.fetch(item.tag.attr('href'), function (err, data) {
      if (err) {
        next(err);
        return;
      }

      item.tag.replaceWith(item.provider.template(data));
      next();
    });
  }, callback);
}

// Replace spoiler tag
//
function replaceSpoiler(ast) {
  ast.find('spoiler').each(function () {
    var $this = $(this);
    var $spoiler = $('
');
    var $spoilerTitle = $('
');
    var $spoilerContent = $('
');

    $spoilerTitle.text($this.attr('title'));

    $spoilerContent.append($this.contents());

    $spoiler
      .attr('data-nd-type', 'spoiler')
      .attr('data-nd-title', $this.attr('title'))
      .append($spoilerTitle)
      .append($spoilerContent);

    $this.replaceWith($spoiler);
  });
}

// Replace tags
//
var replaceTagsConfig = {
  i: 'em',
  b: 'strong',
  s: 'del'
};

function replaceTags(ast) {
  var $this, $replace;

  _.forEach(replaceTagsConfig, function (dest, src) {
    ast.find(src).each(function () {
      $this = $(this);
      $replace = $('<' + dest + '>');

      _.forEach($this.attrs(), function (value, name) {
        $replace.attr(name, value);
      });

      $replace.append($this.contents());

      $this.replaceWith($replace);
    });
  });
}

// Merge node with same node right of this
//
function mergeNextSibling($node) {
  var $sibling, siblingAttrs, nodeAttrs, attrsKeys;

  $sibling = $node.nextNode();

  if (!$sibling) {
    return;
  }

  if ($sibling.prop('tagName') === $node.prop('tagName')) {

    // Check is text node
    if (!$node.prop('tagName')) {
      $node.replaceWith($.createTextNode($node.text() + $sibling.text()));
      $sibling.remove();
      return;
    }

    nodeAttrs = $node.attrs();
    siblingAttrs = $sibling.attrs();
    attrsKeys = Object.keys(nodeAttrs);

    // Can't merge nodes if attributes is not equal, check size of attributes
    if (attrsKeys.length !== Object.keys(siblingAttrs).length) {
      return;
    }

    // Check all attributes are equals
    for (var i = 0; i < attrsKeys.length; i++) {
      if (nodeAttrs[attrsKeys[i]] !== siblingAttrs[attrsKeys[i]]) {
        return;
      }
    }

    $node.append($sibling.contents());
    $sibling.remove();
  }
}

// Merge siblings text nodes
//
// $node - parent node
//
function normalizeTextChildren($node) {
  var textNodes = $node.textNodes();

  for (var i = 0; i < textNodes.length; i++) {
    mergeNextSibling($(textNodes[i]));
  }
}

// Get CSS selector for tags that can be merged from config
//
var mergeSiblingsSelector = _.memoize(function () {
  var tags = [];

  // Find tags marked as 'merge_same' in config
  _.forEach(cleanupRules, function(cfg, tag) {
    if (cfg.merge_same) {
      tags.push(tag);
    }
  });

  return tags.join(', ');
});

// Merge siblings
//
function mergeSiblings($node) {
  var $this, selector;

  selector = mergeSiblingsSelector();

  $node.children().each(function () {
    $this = $(this);

    if ($this.is(selector)) {
      mergeNextSibling($this);
      normalizeTextChildren($this.parent());
    }

    mergeSiblings($this);
  });
}

// Get CSS selector for tags that can be removed
//
var createRemovalSelector = _.memoize(function () {
  var tags = [];

  // Find tags marked as 'remove_empty' in config
  _.forEach(cleanupRules, function(cfg, tag) {
    if (cfg.remove_empty) {
      tags.push(tag);
    }
  });

  return tags.join(', ');
});

// Remove empty tags
//
function removeEmptyTags(ast) {
  var $this, $parent;

  ast.find(createRemovalSelector()).each(function () {
    $this = $(this);

    if ($this.contents().length === 0) {
      $parent = $this.parent();
      $this.remove();
      normalizeTextChildren($parent);
    }
  });
}

// Cut 'http:', 'https:' from internal links, add 'target=_blank' to external links
//
function prepareLinks(ast, baseUrl) {
  if (!baseUrl) {
    return;
  }

  var $this, href;
  var url = _.isArray(baseUrl) ? baseUrl.join('|') : baseUrl;

  url = url.replace(/\./g, '\\.');

  var checkInternalRE = new RegExp('(^https?:\\/\\/' + url + ')|(^\\/)', 'i');
  var removeProtocolRE = /^https?:/;

  ast.find('a').each(function () {
    $this = $(this);
    href = $this.attr('href');

    // Check is URL internal
    if (checkInternalRE.test(href)) {

      // Cut 'http:', 'https:'
      $this.attr('href', href.replace(removeProtocolRE, ''));
    } else {

      // add 'target=_blank'
      $this.attr('target', '_blank');
    }
  });
}

module.exports = function (data, callback) {
  cleanupRules = compileCleanupRules(data.options.cleanupRules);
  smilesRules = compileSmilesRules(data.options.smiles);
  medialinkProviders = data.options.medialinkProviders;

  data.output = $.parse(data.input);

  replaceTags(data.output);
  removeEmptyTags(data.output);
  mergeSiblings(data.output);
  cleanupTags(data.output, cleanupRules.root);
  plainLinks2html(data.output);
  prepareLinks(data.output, data.options.baseUrl);
  smiles2html(data.output);
  replaceSpoiler(data.output);
  medialinks2html(data.output, callback);
};
<commit_msg>parser: remove disallowed tags<commit_after>// Parse SRC HTML (input format) to AST
//
// data:
// - input - SRC HTML (String)
// - output - AST with methods .html(), .text()
// - options:
//   - baseUrl - the URL or array of the URLs (example: [ 'nodeca.com', 'www.nodeca.com' ])
//   - cleanupRules - rules to cleanup HTML
//   - smiles - rules to parse smiles
//   - medialinkProviders - rules to parse medialink
//
//

'use strict';

var $     = require('./cheequery');
var _     = require('lodash');
var async = require('async');

var cleanupRules;
var smilesRules;
var medialinkProviders;

// Compile cleanup rules
//
var compileCleanupRules = _.memoize(function(cleanupRules) {
  var rules = _.cloneDeep(cleanupRules);
  var regExpParse = /^\/(.*?)\/(g?i?m?y?)$/;

  var groups = {};
  var all = Object.keys(rules);

  // Extract groups
  _.forEach(rules, function (rule, tag) {
    if (rule.group) {
      groups[rule.group] = groups[rule.group] || [];
      groups[rule.group].push(tag);
    }
  });

  _.forEach(rules, function (rule) {

    // Prepare attributes
    var attributes = {};

    if (_.isPlainObject(rule.attributes)) {
      var attrs = [];

      _.forEach(rule.attributes, function (val, attr) {
        var attrObj = {};

        attrObj[attr] = val;
        attrs.push(attrObj);
      });
      rule.attributes = attrs;
    }

    if (_.isString(rule.attributes)) {
      rule.attributes = [ rule.attributes ];
    }

    _.forEach(rule.attributes || [], function (val) {
      var attr;

      if (_.isObject(val)) {
        attr = Object.keys(val)[0];
        val = val[attr];
      } else {
        attr = val;
        val = '.*';
      }

      // If val is regexp
      if (val.length > 0 && val[0] === '/') {
        attributes[attr] = new RegExp(
          val.replace(regExpParse, '$1'),
          val.replace(regExpParse, '$2')
        );
        return;
      }

      // If val is enum
      if (_.isArray(val)) {
        val = val.join('|');
      }

      attributes[attr] = new RegExp('^' + val + '$');
    });

    rule.attributes = attributes;

    // Prepare children
    var children = [];

    rule.children = rule.children || [];
    rule.children = _.isArray(rule.children) ? rule.children : [ rule.children ];

    _.forEach(rule.children, function (val) {
      var add = true;

      if (val.length > 0 && val[0] === '!') {
        add = false;
        val = val.substr(1);
      }

      if (val === '*') {

        if (add) {
          children = _.union(children, all);
        } else {
          children = _.difference(children, all);
        }
        return;
      }

      if (val.length > 6 && val.substr(0, 6) === 'group-') {

        if (add) {
          children = _.union(children, groups[val.substr(6)] || []);
        } else {
          children = _.difference(children, groups[val.substr(6)] || []);
        }
        return;
      }

      if (all.indexOf(val) !== -1 || val === 'text') {

        if (add) {
          children = _.union(children, [ val ]);
        } else {
          children = _.difference(children, [ val ]);
        }
        return;
      }
    });

    rule.children = children;
  });

  return rules;
}, JSON.stringify);

// Escape RegExp special characters
// http://stackoverflow.com/questions/3115150
//
function escapeRegexp(text) {
  return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}

// Compile smiles rules
//
var compileSmilesRules = _.memoize(function(smilesConfig) {
  if (Object.keys(smilesConfig).length === 0) {
    return null;
  }

  var smiles = [], pattern;
  var result = {
    templates: {},
    matchRE: null
  };

  _.forEach(smilesConfig, function (val, name) {
    if (!_.isArray(val)) {
      val = [ val ];
    }

    for (var i = 0; i < val.length; i++) {

      smiles.push(val[i]);

      // Create replacement template for each smile
      result.templates[val[i]] = _.template(
        '<span data-nd-type="smile" data-nd-id="<%=id%>" data-nd-src="<%=src%>" class="smiles smile_<%=id%>">',
        {
          id: name,
          src: val[i]
        }
      );
    }
  });

  // Compile regular expression to find all smiles
  pattern = smiles.map(function (smile) { return escapeRegexp(smile); }).join('|');

  result.matchRE = new RegExp('(' + pattern + ')', 'g');

  return result;
}, JSON.stringify);

// Cleanup tag attributes
//
function cleanupAttrs($tag, tagName) {
  _.forEach($tag.attrs(), function (value, name) {
    var attrOptions = cleanupRules[tagName].attributes;

    if (!attrOptions[name] || !attrOptions[name].test(value)) {
      $tag.removeAttr(name);
    }
  });
}

// Cleanup disallowed tags
//
function cleanupTags(parent, tagOptions) {
  if (!tagOptions) {
    return;
  }

  var children = parent.contents();
  var length = children.length;

  for (var i = 0; i < length; i++) {

    var $tag = $(children[i]);
    var tagName = ($tag.prop('tagName') || 'text').toLowerCase();

    // Tag doesn't allowed
    if (tagOptions.children.indexOf(tagName) === -1) {
      $tag.remove();
    }

    cleanupAttrs($tag, tagName);

    cleanupTags($tag, cleanupRules[tagName]);
  }
}

// Replace plain links to tags 'a'
//
function plainLinks2html(ast) {
  var urlRegExp = /(https?:\/\/[^\s'"\)\(<>$]+)/gim;

  var replaceLink = function ($tag) {

    // If text node already in tag 'a' skip it
    if ($tag.closest('a').length !== 0) {
      return;
    }

    var parts = $tag.text().split(urlRegExp);

    // Text node doesn't contains links
    if (parts.length === 1) {
      return;
    }

    for (var i = 0; i < parts.length; i++) {
      if (urlRegExp.test(parts[i])) {

        // Replace links
        $tag.before(_.template(
          '<a href="<%-link%>"><%-text%>',
          {
            link: parts[i],
            // For long links show first 30 symbols
            text: parts[i].length > 30 ? parts[i].substr(0, 30) + '...' : parts[i]
          }
        ));
      } else {

        // Append text parts
        $tag.before($.createTextNode(parts[i]));
      }
    }

    // Remove old text node
    $tag.remove();
  };

  // Find text nodes in root
  ast.textNodes().each(function () {
    replaceLink($(this));
  });

  // Find text nodes in nested elements
  ast.find('*').textNodes().each(function () {
    replaceLink($(this));
  });
}

// Replace smiles by html codes
//
function smiles2html(ast) {

  var replaceSmiles = function ($tag) {
    if (!smilesRules) {
      return;
    }

    var text = $tag.text();
    var positions = [];
    var pos;

    // Search positions of every smile
    while ((pos = smilesRules.matchRE.exec(text)) !== null) {
      positions.push({ smile: pos[1], start: pos.index, end: smilesRules.matchRE.lastIndex });
    }

    var length = positions.length;

    // No smiles found
    if (length === 0) {
      return;
    }

    var lastPos = 0;

    for (var i = 0; i < length; i++) {
      pos = positions[i];

      // Append text part before first smile
      $tag.before($.createTextNode(text.substr(lastPos, pos.start - lastPos)));

      // Append smile
      $tag.before(smilesRules.templates[pos.smile]);

      lastPos = pos.end;

      // Append last text part (only for last position)
      if (i === length - 1) {
        $tag.before($.createTextNode(text.substr(pos.end, text.length - pos.end)));
      }
    }

    // Remove old text node
    $tag.remove();
  };

  // Find text nodes in root
  ast.textNodes().each(function () {
    replaceSmiles($(this));
  });

  // Find text nodes in nested elements
  ast.find('*').textNodes().each(function () {
    replaceSmiles($(this));
  });
}

// Replace medialinks by html codes
//
function medialinks2html(ast, callback) {

  var replace = [];

  ast.find('a').each(function() {
    var tag = $(this);

    _.forEach(medialinkProviders, function (provider) {
      for (var i = 0; i < provider.match.length; i++) {

        if (provider.match[i].test(tag.attr('href'))) {
          replace.push({ provider: provider, tag: tag });
          return false; // break forEach
        }
      }
    });
  });

  async.eachSeries(replace, function (item, next) {
    item.provider.fetch(item.tag.attr('href'), function (err, data) {
      if (err) {
        next(err);
        return;
      }

      item.tag.replaceWith(item.provider.template(data));
      next();
    });
  }, callback);
}

// Replace spoiler tag
//
function replaceSpoiler(ast) {
  ast.find('spoiler').each(function () {
    var $this = $(this);
    var $spoiler = $('
');
    var $spoilerTitle = $('
');
    var $spoilerContent = $('
');

    $spoilerTitle.text($this.attr('title'));

    $spoilerContent.append($this.contents());

    $spoiler
      .attr('data-nd-type', 'spoiler')
      .attr('data-nd-title', $this.attr('title'))
      .append($spoilerTitle)
      .append($spoilerContent);

    $this.replaceWith($spoiler);
  });
}

// Replace tags
//
var replaceTagsConfig = {
  i: 'em',
  b: 'strong',
  s: 'del'
};

function replaceTags(ast) {
  var $this, $replace;

  _.forEach(replaceTagsConfig, function (dest, src) {
    ast.find(src).each(function () {
      $this = $(this);
      $replace = $('<' + dest + '>');

      _.forEach($this.attrs(), function (value, name) {
        $replace.attr(name, value);
      });

      $replace.append($this.contents());

      $this.replaceWith($replace);
    });
  });
}

// Merge node with same node right of this
//
function mergeNextSibling($node) {
  var $sibling, siblingAttrs, nodeAttrs, attrsKeys;

  $sibling = $node.nextNode();

  if (!$sibling) {
    return;
  }

  if ($sibling.prop('tagName') === $node.prop('tagName')) {

    // Check is text node
    if (!$node.prop('tagName')) {
      $node.replaceWith($.createTextNode($node.text() + $sibling.text()));
      $sibling.remove();
      return;
    }

    nodeAttrs = $node.attrs();
    siblingAttrs = $sibling.attrs();
    attrsKeys = Object.keys(nodeAttrs);

    // Can't merge nodes if attributes is not equal, check size of attributes
    if (attrsKeys.length !== Object.keys(siblingAttrs).length) {
      return;
    }

    // Check all attributes are equals
    for (var i = 0; i < attrsKeys.length; i++) {
      if (nodeAttrs[attrsKeys[i]] !== siblingAttrs[attrsKeys[i]]) {
        return;
      }
    }

    $node.append($sibling.contents());
    $sibling.remove();
  }
}

// Merge siblings text nodes
//
// $node - parent node
//
function normalizeTextChildren($node) {
  var textNodes = $node.textNodes();

  for (var i = 0; i < textNodes.length; i++) {
    mergeNextSibling($(textNodes[i]));
  }
}

// Get CSS selector for tags that can be merged from config
//
var mergeSiblingsSelector = _.memoize(function () {
  var tags = [];

  // Find tags marked as 'merge_same' in config
  _.forEach(cleanupRules, function(cfg, tag) {
    if (cfg.merge_same) {
      tags.push(tag);
    }
  });

  return tags.join(', ');
});

// Merge siblings
//
function mergeSiblings($node) {
  var $this, selector;

  selector = mergeSiblingsSelector();

  $node.children().each(function () {
    $this = $(this);

    if ($this.is(selector)) {
      mergeNextSibling($this);
      normalizeTextChildren($this.parent());
    }

    mergeSiblings($this);
  });
}

// Get CSS selector for tags that can be removed
//
var createRemovalSelector = _.memoize(function () {
  var tags = [];

  // Find tags marked as 'remove_empty' in config
  _.forEach(cleanupRules, function(cfg, tag) {
    if (cfg.remove_empty) {
      tags.push(tag);
    }
  });

  return tags.join(', ');
});

// Remove empty tags
//
function removeEmptyTags(ast) {
  var $this, $parent;

  ast.find(createRemovalSelector()).each(function () {
    $this = $(this);

    if ($this.contents().length === 0) {
      $parent = $this.parent();
      $this.remove();
      normalizeTextChildren($parent);
    }
  });
}

// Cut 'http:', 'https:' from internal links, add 'target=_blank' to external links
//
function prepareLinks(ast, baseUrl) {
  if (!baseUrl) {
    return;
  }

  var $this, href;
  var url = _.isArray(baseUrl) ? baseUrl.join('|') : baseUrl;

  url = url.replace(/\./g, '\\.');

  var checkInternalRE = new RegExp('(^https?:\\/\\/' + url + ')|(^\\/)', 'i');
  var removeProtocolRE = /^https?:/;

  ast.find('a').each(function () {
    $this = $(this);
    href = $this.attr('href');

    // Check is URL internal
    if (checkInternalRE.test(href)) {

      // Cut 'http:', 'https:'
      $this.attr('href', href.replace(removeProtocolRE, ''));
    } else {

      // add 'target=_blank'
      $this.attr('target', '_blank');
    }
  });
}

module.exports = function (data, callback) {
  cleanupRules = compileCleanupRules(data.options.cleanupRules);
  smilesRules = compileSmilesRules(data.options.smiles);
  medialinkProviders = data.options.medialinkProviders;

  data.output = $.parse(data.input);

  replaceTags(data.output);
  removeEmptyTags(data.output);
  mergeSiblings(data.output);
  cleanupTags(data.output, cleanupRules.root);
  plainLinks2html(data.output);
  prepareLinks(data.output, data.options.baseUrl);
  smiles2html(data.output);
  replaceSpoiler(data.output);
  medialinks2html(data.output, callback);
};
<|endoftext|>


<commit_before>/* Copyright (c) 2012-2013 Cristian Ianto, MIT License */
"use strict";

var _              = require('underscore');
var URL            = require('url');

var PaypalExpress  = require('paypal-express').PaypalExpress;

var name = "pay-paypal-express"

module.exports = function(options){
  var seneca = this;

  var endpoints = {
    pay: '/pay/paypal-express/pay',
    completeCallback: '/pay/paypal-express/callback-complete',
    cancelCallback: '/pay/paypal-express/callback-cancel'
  }

  var paypalExpress;

  seneca.add({role:name, cmd:'init-gateway'}, function (args, done) {
    var options = args.options;

    if (options) {
      paypalExpress = new PaypalExpress(
        options.username,
        options.password,
        options.signature);

      paypalExpress.useSandbox(options.useSandbox);
    }

    done(null, paypalExpress)
  })

  var hostUrl;
  var successUrl, failUrl;

  seneca.add({role:name, cmd:'init-routes'}, function (args, done) {
    var routes = args.routes;

    if(args.redirect) {

      hostUrl = args.redirect.hostUrl;
      successUrl = args.redirect.success;
      failUrl = args.redirect.fail;

    } else {
      hostUrl = options.redirect.hostUrl;
      successUrl = options.redirect.success;
      failUrl = options.redirect.fail;

    }

    routes[endpoints.pay] = { POST: payHandler };
    routes[endpoints.completeCallback] = { GET: completeCallbackHandler };
    routes[endpoints.cancelCallback] = { GET: cancelCallbackHandler };

    done(null, routes)
  })

  function urljoin () {
    var args = [].slice.call(arguments)
    return args.join('/').replace(/\/+/g, '/').replace(/:\//,'://')
  }

  function urlappend(url, name, value) {
    var urlobj = URL.parse(url, true);
    if (typeof value !== 'undefined' && value !== null) {
      urlobj.query[name] = value;
    }
    return URL.format(urlobj);
  }

  function payHandler(req, res, next) {

    var input = req.body;
    var transactionData = { refno:input.refno, status:'created' };

    seneca.act({role:'transaction', cmd:'create', data:transactionData}, function(err, out) {
      if (err) {
        res.redirect(urlappend(failUrl, 'refno', input.refno));
        return
      }

      var transaction = out.transaction;

      var host = hostUrl;
      if (!host && req.headers.host) {
        host = (req.connection.encrypted ? 'https' : 'http') + '://' + req.headers.host;
      }

      var completeCallbackUrl = urlappend(urljoin(host, endpoints.completeCallback), 'refno', transaction.refno);
      var cancelCallbackUrl = urlappend(urljoin(host, endpoints.cancelCallback), 'refno', transaction.refno);

      paypalExpress.beginInstantPayment({
          'RETURNURL': completeCallbackUrl,
          'CANCELURL': cancelCallbackUrl,
          'CURRENCYCODE': input.currencyCode,
          'PAYMENTREQUEST_0_AMT': input.amount,
          'PAYMENTREQUEST_0_ITEMAMT': input.itemAmount,
          'PAYMENTREQUEST_0_TAXAMT': input.taxAmount,
          'PAYMENTREQUEST_0_DESC': input.description
        },
        function(err, data) {
          if (err) {
            seneca.log.debug('beginPayment: error', err);
            res.redirect(urlappend(failUrl, 'refno', input.refno));
          }
          else {
            seneca.log.debug('beginPayment: init transaction', data.payment_url);

            transaction.expressCheckout = data;
            transaction.status = 'started';

            seneca.act({role:'transaction', cmd:'update', id:transaction.id, data:transaction}, function(err) {
              seneca.log.debug('beginPayment: redirecting to', data.payment_url);

              res.redirect(data.payment_url)
            })
          }
        }
      )
    })
  }

  function completeCallbackHandler(req, res, next) {

    var token = req.query['token'];
    var payerID = req.query['PayerID'];
    var refno = req.query['refno'];

    seneca.act({role:'transaction', cmd:'find', q:{'refno':refno}}, function(err, out) {
      if (err) {
        seneca.log.error('find', 'transaction', 'error', err, {token:token})
        res.redirect(urlappend(failUrl, 'refno', refno));
        return
      }

    var transaction = out.transaction;
    transaction.expressCheckout.PayerID = payerID;
    transaction.status = 'completed';

    seneca.act({role:'transaction', cmd:'update', id:transaction.id, data:transaction}, function(err) {

      res.redirect(urlappend(successUrl, 'refno', transaction.refno));

    }) })
  }

  function cancelCallbackHandler(req, res, next) {

    var token = req.query['token'];
    var payerID = req.query['PayerID'];
    var refno = req.query['refno'];

    seneca.act({role:'transaction', cmd:'find', q:{'refno':refno}}, function(err, out) {
      if (err) {
        seneca.log.error('find', 'transaction', 'error', err, {token:token})
        res.redirect(urlappend(failUrl, 'refno', refno));
        return
      }

      var transaction = out.transaction;
      transaction.expressCheckout.PayerID = payerID;
      transaction.status = 'cancelled';

      seneca.act({
        role:'transaction',
        cmd:'update',
        id:transaction.id,
        data:transaction
      }, function(err) {
        res.redirect(urlappend(failUrl, 'refno', refno));
      })
    })
  }
}
<commit_msg>truncate description before passing to paypal, store additional details in transaction<commit_after>/* Copyright (c) 2012-2013 Cristian Ianto, MIT License */
"use strict";

var _              = require('underscore');
var URL            = require('url');

var PaypalExpress  = require('paypal-express').PaypalExpress;

var name = "pay-paypal-express"

module.exports = function(options){
  var seneca = this;

  var endpoints = {
    pay: '/pay/paypal-express/pay',
    completeCallback: '/pay/paypal-express/callback-complete',
    cancelCallback: '/pay/paypal-express/callback-cancel'
  }

  var paypalExpress;

  seneca.add({role:name, cmd:'init-gateway'}, function (args, done) {
    var options = args.options;

    if (options) {
      paypalExpress = new PaypalExpress(
        options.username,
        options.password,
        options.signature);

      paypalExpress.useSandbox(options.useSandbox);
    }

    done(null, paypalExpress)
  })

  var hostUrl;
  var successUrl, failUrl;

  seneca.add({role:name, cmd:'init-routes'}, function (args, done) {
    var routes = args.routes;

    if(args.redirect) {

      hostUrl = args.redirect.hostUrl;
      successUrl = args.redirect.success;
      failUrl = args.redirect.fail;

    } else {
      hostUrl = options.redirect.hostUrl;
      successUrl = options.redirect.success;
      failUrl = options.redirect.fail;

    }

    routes[endpoints.pay] = { POST: payHandler };
    routes[endpoints.completeCallback] = { GET: completeCallbackHandler };
    routes[endpoints.cancelCallback] = { GET: cancelCallbackHandler };

    done(null, routes)
  })

  function urljoin () {
    var args = [].slice.call(arguments)
    return args.join('/').replace(/\/+/g, '/').replace(/:\//,'://')
  }

  function urlappend(url, name, value) {
    var urlobj = URL.parse(url, true);
    if (typeof value !== 'undefined' && value !== null) {
      urlobj.query[name] = value;
    }
    return URL.format(urlobj);
  }

  function payHandler(req, res, next) {

    var input = req.body;

    var transactionData = {
      refno:input.refno,
      status:'created',
      customer: {
        name: input.name,
        company: input.company,
        email: input.email
      },
      description: input.description,
      priceTag: input.priceTag,
      amount: input.amount,
      currencyCode: input.currencyCode,
      plan: input.plan
    };

    seneca.act({role:'transaction', cmd:'create', data:transactionData}, function(err, out) {
      if (err) {
        res.redirect(urlappend(failUrl, 'refno', input.refno));
        return
      }

      var transaction = out.transaction;

      var host = hostUrl;
      if (!host && req.headers.host) {
        host = (req.connection.encrypted ? 'https' : 'http') + '://' + req.headers.host;
      }

      var completeCallbackUrl = urlappend(urljoin(host, endpoints.completeCallback), 'refno', transaction.refno);
      var cancelCallbackUrl = urlappend(urljoin(host, endpoints.cancelCallback), 'refno', transaction.refno);

      paypalExpress.beginInstantPayment({
          'RETURNURL': completeCallbackUrl,
          'CANCELURL': cancelCallbackUrl,
          'CURRENCYCODE': input.currencyCode,
          'PAYMENTREQUEST_0_AMT': input.amount,
          'PAYMENTREQUEST_0_ITEMAMT': input.itemAmount,
          'PAYMENTREQUEST_0_TAXAMT': input.taxAmount,
          'PAYMENTREQUEST_0_DESC': (input.description || '').substring(0, 127)
        },
        function(err, data) {
          if (err) {
            seneca.log.debug('beginPayment: error', err);
            res.redirect(urlappend(failUrl, 'refno', input.refno));
          }
          else {
            seneca.log.debug('beginPayment: init transaction', data.payment_url);

            transaction.expressCheckout = data;
            transaction.status = 'started';

            seneca.act({role:'transaction', cmd:'update', id:transaction.id, data:transaction}, function(err) {
              seneca.log.debug('beginPayment: redirecting to', data.payment_url);

              res.redirect(data.payment_url)
            })
          }
        }
      )
    })
  }

  function completeCallbackHandler(req, res, next) {

    var token = req.query['token'];
    var payerID = req.query['PayerID'];
    var refno = req.query['refno'];

    seneca.act({role:'transaction', cmd:'find', q:{'refno':refno}}, function(err, out) {
      if (err) {
        seneca.log.error('find', 'transaction', 'error', err, {token:token})
        res.redirect(urlappend(failUrl, 'refno', refno));
        return
      }

    var transaction = out.transaction;
    transaction.expressCheckout.PayerID = payerID;
    transaction.status = 'completed';

    seneca.act({role:'transaction', cmd:'update', id:transaction.id, data:transaction}, function(err) {

      res.redirect(urlappend(successUrl, 'refno', transaction.refno));

    }) })
  }

  function cancelCallbackHandler(req, res, next) {

    var token = req.query['token'];
    var payerID = req.query['PayerID'];
    var refno = req.query['refno'];

    seneca.act({role:'transaction', cmd:'find', q:{'refno':refno}}, function(err, out) {
      if (err) {
        seneca.log.error('find', 'transaction', 'error', err, {token:token})
        res.redirect(urlappend(failUrl, 'refno', refno));
        return
      }

      var transaction = out.transaction;
      transaction.expressCheckout.PayerID = payerID;
      transaction.status = 'cancelled';

      seneca.act({
        role:'transaction',
        cmd:'update',
        id:transaction.id,
        data:transaction
      }, function(err) {
        res.redirect(urlappend(failUrl, 'refno', refno));
      })
    })
  }
}
<|endoftext|>


<commit_before>'use strict';
/*global Notification */
$(function () {
    var status,
        tag = (Math.random() * 10e15).toString(16);

    function notify(title, text) {
        if (Notification.permission === 'granted') {
            var now = new Date().toTimeString().replace(/ .*$/, '');
            new Notification(title + ' @' + now, {
                body: text,
                tag: tag,
                icon: '/static/images/wtf.png'
            });
        }
    }

    function init() {
        var res = setupNotifications();
        if (!res) {
            setTimeout(init, 100);
        }
    }

    function registerListeners() {
        if (Notification.permission === 'granted') {
            window.socket.on('summary', onSummary);
            window.socket.on('disconnect', onDisconnect);
            window.socket.on('reconnect', onReconnect);
        }
    }

    function setupNotifications() {
        if (!window.socket) {
            return false;
        }
        if (window.Notification && Notification.permission !== "granted") {
            Notification.requestPermission(function (status) {
                if (Notification.permission !== status) {
                    Notification.permission = status;
                }
                registerListeners();
            });
        } else {
            registerListeners();
        }
        return true;
    }

    var connected = true;

    function onDisconnect() {
        connected = false;
        setTimeout(function () {
            if (connected) {
                return;
            }
            notify('Connection to servercooties.com lost.',
                'Visual down! Radar down! What\'s happening‽');
        }, 1000);
    }

    function onReconnect() {
        connected = true;
        notify('Connection to servercooties.com restored.',
            'I\'m baaack! Did you miss me?');
    }

    var notice, readonly= false;
    function onSummary(summary) {

        if (status !== undefined && status !== summary.up) {
            if (status) {
                notify('Site Offline',
                    'Cooties have infected the server; recommend to keep a ' +
                    'safe distance and don\'t obstruct the Hazmat team');
            } else {
                notify('Site Online', 'Server cootie infection neutralised; ' +
                    'normal service will now resume');
            }
        }
        if (summary.global_notice_text && summary.global_notice_text !== notice){
            notice = summary.global_notice_text;
            notify('Global Notice Posted', summary.global_notice_text);
        } else {
            if (readonly && ! summary.readonly){
                notify('chmod +w','Site is no longer read-only');
            } else if (!readonly && summary.readonly){
                notify('Admin Abuse!', 'Site has been marked Readonly');
            }
            readonly = summary.readonly;
        }
        status = summary.up;
    }

    init();
}());
<commit_msg>Add notify window handler<commit_after>'use strict';
/*global Notification */

$(function () {
    var status,
        tag = (Math.random() * 10e15).toString(16);

    function notify(title, text, icon) {
        if (Notification.permission === 'granted') {
            icon = icon || '/static/images/wtf.png';

            var now = new Date().toTimeString().replace(/ .*$/, '');
            new Notification(title + ' @' + now, {
                body: text,
                tag: tag,
                icon: icon
            });
        }
    }

    window.Notify = notify;

    function init() {
        var res = setupNotifications();
        if (!res) {
            setTimeout(init, 100);
        }
    }

    function registerListeners() {
        if (Notification.permission === 'granted') {
            window.socket.on('summary', onSummary);
            window.socket.on('disconnect', onDisconnect);
            window.socket.on('reconnect', onReconnect);
        }
    }

    function setupNotifications() {
        if (!window.socket) {
            return false;
        }
        if (window.Notification && Notification.permission !== "granted") {
            Notification.requestPermission(function (status) {
                if (Notification.permission !== status) {
                    Notification.permission = status;
                }
                registerListeners();
            });
        } else {
            registerListeners();
        }
        return true;
    }

    var connected = true;

    function onDisconnect() {
        connected = false;
        setTimeout(function () {
            if (connected) {
                return;
            }
            notify('Connection to servercooties.com lost.',
                'Visual down! Radar down! What\'s happening‽');
        }, 1000);
    }

    function onReconnect() {
        connected = true;
        notify('Connection to servercooties.com restored.',
            'I\'m baaack! Did you miss me?');
    }

    var notice, readonly= false;
    function onSummary(summary) {

        if (status !== undefined && status !== summary.up) {
            if (status) {
                notify('Site Offline',
                    'Cooties have infected the server; recommend to keep a ' +
                    'safe distance and don\'t obstruct the Hazmat team');
            } else {
                notify('Site Online', 'Server cootie infection neutralised; ' +
                    'normal service will now resume');
            }
        }
        if (summary.global_notice_text && summary.global_notice_text !== notice){
            notice = summary.global_notice_text;
            notify('Global Notice Posted', summary.global_notice_text);
        } else {
            if (readonly && ! summary.readonly){
                notify('chmod +w','Site is no longer read-only');
            } else if (!readonly && summary.readonly){
                notify('Admin Abuse!', 'Site has been marked Readonly');
            }
            readonly = summary.readonly;
        }
        status = summary.up;
    }

    init();
}());
<|endoftext|>


<commit_before>/**
 * This module deals with how pages are generated, rendered and delivered to
 * the client. Each server controller is expected to use this API (or the res
 * object) to send a response to the client.
 *
 * @class page (server)
 * @module Superfluous
 * @submodule Server
 *
 */

"use strict";

var context = require("./context");
var bridge = require("./bridge");
var EventEmitter = require('events').EventEmitter;
var quick_hash = require_core("server/hash");
var readfile = require_core("server/readfile");
var async = require("async");

var cheerio = require("cheerio");

var __async_id = 0;
context.setDefault("ASYNC_FUTURES", []);

/**
 * Async page rendering function. It takes a function to run inside an async context
 * and returns a placeholder to be rendered in its placed.
 *
 * @method async
 */
var render_async = function(func) {
  var div = cheerio("
");
  var futures = context("ASYNC_FUTURES");
  var res = context("res");
  var id = "async" + __async_id;
  __async_id += 1;

  return function() {
    div.attr("id", id);
    var future_func = function(cb) {
      func(function(data) {
        try {
          bridge.flush_data(data, id, cb);
        } catch(e) {
          console.error(e);
        }
      });
    };

    futures.push(future_func);

    return div;
  };
};

var do_async = function(func) {
  render_async(func)();
}

function resolve_futures() {
  // FUTURES ARE ASYNC, NEED TO USE context() HERE
  // Execute all future funcs, now
  var futures = context("ASYNC_FUTURES");

  var ctx = context.get();
  ctx.enter();
  context("ASYNC_FUTURES", []);

  var async_start = Date.now();

  var pending = futures.length;
  function resolution() {
    try {
      var now = Date.now();
      var elapsed = now - context("__start");
      var async_elapsed = now - async_start;
      debug("Ending futures for ", context("id"), ", request duration:",
        elapsed, "async duration: ", async_elapsed);

      // What happens if someone enqueues futures during a future? (this is
      // what happens...)
      futures = context("ASYNC_FUTURES");
      if (futures.length) {
        resolve_futures();
      } else {
        module.exports.emit("async");
        module.exports.emit("finished");
        context("stream").write("
");
        context("stream").end();

        // DO NOT RELY ON THIS, IT IS A TEST HOOK!
        if (context("__testing")) {
          var test_hook = context("__on_page_end");
          if (test_hook) {
            test_hook();
          }
        }

        var controller_instance = context("controller_instance");
        if (controller_instance && controller_instance.after_request) {
          controller_instance.after_request();
        }

        ctx.exit();
      }
    } catch(e) {
      console.error(e);
    }
  }

  function resolve_pending() {
    pending -= 1;

    if (!pending) {
      resolution();
    }
  }

  if (!pending) {
    resolution();
  } else {
    _.each(futures, function(future) {
      future(resolve_pending);
    });
  }
}

/**
 * The main page rendering function. It takes an array of options specifying
 * what to render in the page.
 *
 * @method render
 */
var render_page = function(page_options) {
  var bootloader_controller = require_core("controllers/bootloader/server");
  var $$ = context.get();

  // any dependencies after this will have to be bootloaded in
  var controller = $$.controller;

  // strip controller slashes (just in case)
  controller = controller.replace(/^\/*/, '');

  // render into page formata
  var controller_include = require("path").join(controller, "client");
  var controller_client_path = require_core("server/controller").get_full_path(controller_include);
  var hash = quick_hash(readfile(controller_client_path));
  var css_hash, js_hash, socket_hash;

  async.parallel([function(after) {
    bootloader_controller.get_css_prelude_hash(function(hash) {
      css_hash = hash;
      after();
    });
  }, function(after) {
    bootloader_controller.get_js_prelude_hash(function(hash) {
      js_hash = hash;
      after();
    });
  }, function(after) {
    bootloader_controller.get_socket_hash(function(hash) {
      socket_hash = hash;
      after();
    });
  }],
  function () { // after everything finishes
    var config = require("./config");
    var template = require("./template");
    var misc_header = "";
    var use_socket = page_options.socket || context("added_socket");
    var use_component = page_options.component || false;

    if (use_component) {
      template.add_javascript("core/client/component");
    }

    var use_storage = false; // TODO: turn this on for production environments
    if (config.RELEASE) {
      use_storage = true;
    }

    var use_chunked_xhr = false; // TODO: turn this on after more testing

    var use_fullscreen = $$.use_fullscreen;
    var pageId = _.uniqueId("pg_");
    var simple_pipe = config.simple_pipe;
    if ($$.req.query.simple_pipe) {
      console.info("Using simple pipe delivery");
      simple_pipe = true;
    }

    var page = template.render_core("helpers/page.html.erb", {
      header: page_options.header,
      head_supplements: $$.HEAD_SUPPLEMENTS,
      controller: controller,
      use_storage: use_storage,
      use_fullscreen: use_fullscreen,
      use_chunked_xhr: use_chunked_xhr,
      js_deps: [],
      css_deps: [],
      content: page_options.content,
      hash: hash,
      release: config.RELEASE,
      socket_header: use_socket && template.socket_header(socket_hash),
      title: $$.title || "SF",
      id: pageId,
      misc_header: misc_header,
      js_header: template.js_header(js_hash),
      simple_pipe: simple_pipe,
      css_header: template.css_header(css_hash)
    });

    var pageStr = page.toString();
    // TODO: work on how the order of things are initialized happens
    $$.res.setHeader('Content-Type', 'text/html');
    $$.res.setHeader('Content-Encoding', 'gzip');
    $$.stream.write(pageStr);
    // Update the name of the controller on the page, when we can.
    // This also sets the $page element on the controller, inevitably
    bridge.call("core/client/controller", "set", controller, pageId, hash);

    if (!simple_pipe) {
      bridge.flush_data(page_options.content, "page_content");
    }

    $$.stream.write("
");
    $$.stream.write(bridge.render());
    try {

      $$.stream.flush();
    } catch(e) {
      console.error(e);
      console.trace();
      return;
    }

    resolve_futures();

  });
};

var emitter = new EventEmitter();
module.exports = _.extend(emitter, {
  render: render_page,
  async: render_async,
  defer: do_async,
  placeholder: render_async
});
<commit_msg>having an end_request hook is useful for access logs<commit_after>/**
 * This module deals with how pages are generated, rendered and delivered to
 * the client. Each server controller is expected to use this API (or the res
 * object) to send a response to the client.
 *
 * @class page (server)
 * @module Superfluous
 * @submodule Server
 *
 */

"use strict";

var context = require("./context");
var bridge = require("./bridge");
var EventEmitter = require('events').EventEmitter;
var quick_hash = require_core("server/hash");
var readfile = require_core("server/readfile");
var async = require("async");
var hooks = require_core("server/hooks");

var cheerio = require("cheerio");

var __async_id = 0;
context.setDefault("ASYNC_FUTURES", []);

/**
 * Async page rendering function. It takes a function to run inside an async context
 * and returns a placeholder to be rendered in its placed.
 *
 * @method async
 */
var render_async = function(func) {
  var div = cheerio("
");
  var futures = context("ASYNC_FUTURES");
  var res = context("res");
  var id = "async" + __async_id;
  __async_id += 1;

  return function() {
    div.attr("id", id);
    var future_func = function(cb) {
      func(function(data) {
        try {
          bridge.flush_data(data, id, cb);
        } catch(e) {
          console.error(e);
        }
      });
    };

    futures.push(future_func);

    return div;
  };
};

var do_async = function(func) {
  render_async(func)();
}

function resolve_futures() {
  // FUTURES ARE ASYNC, NEED TO USE context() HERE
  // Execute all future funcs, now
  var futures = context("ASYNC_FUTURES");

  var ctx = context.get();
  ctx.enter();
  context("ASYNC_FUTURES", []);

  var async_start = Date.now();

  var pending = futures.length;
  function resolution() {
    try {
      var now = Date.now();
      var elapsed = now - context("__start");
      var async_elapsed = now - async_start;
      debug("Ending futures for ", context("id"), ", request duration:",
        elapsed, "async duration: ", async_elapsed);

      // What happens if someone enqueues futures during a future? (this is
      // what happens...)
      futures = context("ASYNC_FUTURES");
      if (futures.length) {
        resolve_futures();
      } else {
        module.exports.emit("async");
        module.exports.emit("finished");
        context("stream").write("
");
        context("stream").end();

        // DO NOT RELY ON THIS, IT IS A TEST HOOK!
        if (context("__testing")) {
          var test_hook = context("__on_page_end");
          if (test_hook) {
            test_hook();
          }
        }

        var controller_instance = context("controller_instance");
        if (controller_instance && controller_instance.after_request) {
          controller_instance.after_request();
        }

        hooks.invoke("end_request", context("req"), context("res"), function() { });

        ctx.exit();
      }
    } catch(e) {
      console.error(e);
    }
  }

  function resolve_pending() {
    pending -= 1;

    if (!pending) {
      resolution();
    }
  }

  if (!pending) {
    resolution();
  } else {
    _.each(futures, function(future) {
      future(resolve_pending);
    });
  }
}

/**
 * The main page rendering function. It takes an array of options specifying
 * what to render in the page.
 *
 * @method render
 */
var render_page = function(page_options) {
  var bootloader_controller = require_core("controllers/bootloader/server");
  var $$ = context.get();

  // any dependencies after this will have to be bootloaded in
  var controller = $$.controller;

  // strip controller slashes (just in case)
  controller = controller.replace(/^\/*/, '');

  // render into page formata
  var controller_include = require("path").join(controller, "client");
  var controller_client_path = require_core("server/controller").get_full_path(controller_include);
  var hash = quick_hash(readfile(controller_client_path));
  var css_hash, js_hash, socket_hash;

  async.parallel([function(after) {
    bootloader_controller.get_css_prelude_hash(function(hash) {
      css_hash = hash;
      after();
    });
  }, function(after) {
    bootloader_controller.get_js_prelude_hash(function(hash) {
      js_hash = hash;
      after();
    });
  }, function(after) {
    bootloader_controller.get_socket_hash(function(hash) {
      socket_hash = hash;
      after();
    });
  }],
  function () { // after everything finishes
    var config = require("./config");
    var template = require("./template");
    var misc_header = "";
    var use_socket = page_options.socket || context("added_socket");
    var use_component = page_options.component || false;

    if (use_component) {
      template.add_javascript("core/client/component");
    }

    var use_storage = false; // TODO: turn this on for production environments
    if (config.RELEASE) {
      use_storage = true;
    }

    var use_chunked_xhr = false; // TODO: turn this on after more testing

    var use_fullscreen = $$.use_fullscreen;
    var pageId = _.uniqueId("pg_");
    var simple_pipe = config.simple_pipe;
    if ($$.req.query.simple_pipe) {
      console.info("Using simple pipe delivery");
      simple_pipe = true;
    }

    var page = template.render_core("helpers/page.html.erb", {
      header: page_options.header,
      head_supplements: $$.HEAD_SUPPLEMENTS,
      controller: controller,
      use_storage: use_storage,
      use_fullscreen: use_fullscreen,
      use_chunked_xhr: use_chunked_xhr,
      js_deps: [],
      css_deps: [],
      content: page_options.content,
      hash: hash,
      release: config.RELEASE,
      socket_header: use_socket && template.socket_header(socket_hash),
      title: $$.title || "SF",
      id: pageId,
      misc_header: misc_header,
      js_header: template.js_header(js_hash),
      simple_pipe: simple_pipe,
      css_header: template.css_header(css_hash)
    });

    var pageStr = page.toString();
    // TODO: work on how the order of things are initialized happens
    $$.res.setHeader('Content-Type', 'text/html');
    $$.res.setHeader('Content-Encoding', 'gzip');
    $$.stream.write(pageStr);
    // Update the name of the controller on the page, when we can.
    // This also sets the $page element on the controller, inevitably
    bridge.call("core/client/controller", "set", controller, pageId, hash);

    if (!simple_pipe) {
      bridge.flush_data(page_options.content, "page_content");
    }

    $$.stream.write("
");
    $$.stream.write(bridge.render());
    try {

      $$.stream.flush();
    } catch(e) {
      console.error(e);
      console.trace();
      return;
    }

    resolve_futures();

  });
};

var emitter = new EventEmitter();
module.exports = _.extend(emitter, {
  render: render_page,
  async: render_async,
  defer: do_async,
  placeholder: render_async
});
<|endoftext|>


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

var Command = require('./util/command'),
    project = require('./util/project'),
    soundwave = require('phonegap-soundwave'),
    util = require('util');

/*!
 * Command setup.
 */

module.exports = {
    create: function(phonegap) {
        return new AppCommand(phonegap);
    }
};

function AppCommand(phonegap) {
    return Command.apply(this, arguments);
}

util.inherits(AppCommand, Command);

/**
 * Serve the App.
 *
 * Creates a local server to serve up the project. The intended
 * receiver is the PhoneGap App but any browser can consume the
 * content.
 *
 * Options:
 *
 *   - `options` {Object}
 *     - `[port]` {Number} is the server port (default: 3000).
 *   - `[callback]` {Function} is triggered when server starts.
 *     - `e` {Error} is null unless there is an error.
 *
 * Returns:
 *
 *   {PhoneGap} for chaining.
 */

AppCommand.prototype.run = function(options, callback) {
    // require options
    if (!options) throw new Error('requires option parameter');

    // optional parameters
    options.port = options.port || 3000;
    callback = callback || function() {};

    // start app
    this.execute(options, callback);

    return this.phonegap;
};

/*!
 * Execute command.
 */

AppCommand.prototype.execute = function(options, callback) {
    var self = this;

    // change to project directory and delegate errors
    if (!project.cd({ emitter: self.phonegap, callback: callback })) return;

    soundwave.serve(options,callback);
    self.phonegap.emit('log', 'starting app server...');

    // bind error
    soundwave.on('error', function(e) {
        self.phonegap.emit('error', e);
        callback(e);
    });

    // bind log
    soundwave.on('log', function(msg) {
        self.phonegap.emit('log', msg);
    });
};
<commit_msg>[#214] Cleanup serve command formatting.<commit_after>/*!
 * Module dependencies.
 */

var Command = require('./util/command'),
    project = require('./util/project'),
    soundwave = require('phonegap-soundwave'),
    util = require('util');

/*!
 * Command setup.
 */

module.exports = {
    create: function(phonegap) {
        return new AppCommand(phonegap);
    }
};

function AppCommand(phonegap) {
    return Command.apply(this, arguments);
}

util.inherits(AppCommand, Command);

/**
 * Serve the App.
 *
 * Creates a local server to serve up the project. The intended
 * receiver is the PhoneGap App but any browser can consume the
 * content.
 *
 * Options:
 *
 *   - `options` {Object}
 *     - `[port]` {Number} is the server port (default: 3000).
 *   - `[callback]` {Function} is triggered when server starts.
 *     - `e` {Error} is null unless there is an error.
 *
 * Returns:
 *
 *   {PhoneGap} for chaining.
 */

AppCommand.prototype.run = function(options, callback) {
    // require options
    if (!options) throw new Error('requires option parameter');

    // optional parameters
    options.port = options.port || 3000;
    callback = callback || function() {};

    // start app
    this.execute(options, callback);

    return this.phonegap;
};

/*!
 * Execute command.
 */

AppCommand.prototype.execute = function(options, callback) {
    var self = this;

    // change to project directory and delegate errors
    if (!project.cd({ emitter: self.phonegap, callback: callback })) return;

    soundwave.serve(options, callback);
    self.phonegap.emit('log', 'starting app server...');

    // bind error
    soundwave.on('error', function(e) {
        self.phonegap.emit('error', e);
        callback(e);
    });

    // bind log
    soundwave.on('log', function(message) {
        self.phonegap.emit('log', message);
    });
};
<|endoftext|>


<commit_before>const handlebars = require('handlebars');
const Plugin = require('../plugin');

module.exports = class Issues extends Plugin {
  comment(context, content) {
    const template = handlebars.compile(content)(context.payload);
    return context.github.issues.createComment(context.toIssue({body: template}));
  }

  assign(context, ...assignees) {
    return context.github.issues.addAssigneesToIssue(context.toIssue({assignees}));
  }

  unassign(context, ...assignees) {
    return context.github.issues.removeAssigneesFromIssue(context.toIssue({body: {assignees}}));
  }

  label(context, ...labels) {
    return context.github.issues.addLabels(context.toIssue({body: labels}));
  }

  unlabel(context, ...labels) {
    return labels.map(label => {
      return context.github.issues.removeLabel(
        context.toIssue({name: label})
      );
    });
  }

  lock(context) {
    return context.github.issues.lock(context.toIssue({}));
  }

  unlock(context) {
    return context.github.issues.unlock(context.toIssue({}));
  }

  open(context) {
    return context.github.issues.edit(context.toIssue({state: 'open'}));
  }

  close(context) {
    return context.github.issues.edit(context.toIssue({state: 'closed'}));
  }

  deleteComment(context) {
    const comment = context.payload.comment;
    const github = context.github;

    const deleteFunction =
      (comment.pull_request_review_id && github.pullRequests.deleteComment) ||
      (comment.commit_id && github.repos.deleteCommitComment) ||
      github.issues.deleteComment;

    return deleteFunction(context.toRepo({id: comment.id}));
  }

  create(context, content) {

    var lines = content.split('\n');
    const titleTemplate = handlebars.compile(lines[0])(context.payload);
    const bodyTemplate = handlebars.compile(lines.splice(0,1)join('\n'))(context.payload);//buf.toString())(context.payload);
    return context.github.issues.create(context.toRepo({title: titleTemplate, body: bodyTemplate}));

  }

};
<commit_msg>Fix line splice bug<commit_after>const handlebars = require('handlebars');
const Plugin = require('../plugin');

module.exports = class Issues extends Plugin {
  comment(context, content) {
    const template = handlebars.compile(content)(context.payload);
    return context.github.issues.createComment(context.toIssue({body: template}));
  }

  assign(context, ...assignees) {
    return context.github.issues.addAssigneesToIssue(context.toIssue({assignees}));
  }

  unassign(context, ...assignees) {
    return context.github.issues.removeAssigneesFromIssue(context.toIssue({body: {assignees}}));
  }

  label(context, ...labels) {
    return context.github.issues.addLabels(context.toIssue({body: labels}));
  }

  unlabel(context, ...labels) {
    return labels.map(label => {
      return context.github.issues.removeLabel(
        context.toIssue({name: label})
      );
    });
  }

  lock(context) {
    return context.github.issues.lock(context.toIssue({}));
  }

  unlock(context) {
    return context.github.issues.unlock(context.toIssue({}));
  }

  open(context) {
    return context.github.issues.edit(context.toIssue({state: 'open'}));
  }

  close(context) {
    return context.github.issues.edit(context.toIssue({state: 'closed'}));
  }

  deleteComment(context) {
    const comment = context.payload.comment;
    const github = context.github;

    const deleteFunction =
      (comment.pull_request_review_id && github.pullRequests.deleteComment) ||
      (comment.commit_id && github.repos.deleteCommitComment) ||
      github.issues.deleteComment;

    return deleteFunction(context.toRepo({id: comment.id}));
  }

  create(context, content) {
    var lines = content.split('\n');
    const titleTemplate = handlebars.compile(lines[0])(context.payload);
    lines.splice(0,1);
    const bodyTemplate = handlebars.compile(lines.join('\n'))(context.payload);

    return context.github.issues.create(context.toRepo({title: titleTemplate, body: bodyTemplate}));
  }
};
<|endoftext|>


<commit_before>'use strict';

var utils = require('../utils');

module.exports = function(proto) {

  /**
   * Set view types for a collection.
   *
   * @param {String} `plural` e.g. `pages`
   * @param {Object} `options`
   */

  proto.viewType = proto.viewType || function(plural, types) {
    var len = types.length, i = 0;
    while (len--) {
      var type = types[i++];
      this.viewTypes[type] = this.viewTypes[type] || [];
      if (this.viewTypes[type].indexOf(plural) === -1) {
        this.viewTypes[type].push(plural);
      }
    }
    return types;
  };

  /**
   * Compile `content` with the given `locals`.
   *
   * ```js
   * var indexPage = app.page('some-index-page.hbs');
   * var view = app.compile(indexPage);
   * // view.fn => [function]
   *
   * // you can call the compiled function more than once
   * // to render the view with different data
   * view.fn({title: 'Foo'});
   * view.fn({title: 'Bar'});
   * view.fn({title: 'Baz'});
   * ```
   *
   * @name .compile
   * @param  {Object|String} `view` View object.
   * @param  {Object} `locals`
   * @param  {Boolean} `isAsync` Load async helpers
   * @return {Object} View object with `fn` property with the compiled function.
   * @api public
   */

  proto.compile = function(view, locals, isAsync) {
    if (typeof locals === 'boolean') {
      isAsync = locals;
      locals = {};
    }

    if (typeof locals === 'function' || typeof isAsync === 'function') {
      throw this.formatError('compile', 'callback');
    }

    locals = utils.extend({settings: {}}, locals);

    // if `view` is a string, see if it's a cached view
    if (typeof view === 'string') {
      if (this.isCollection) {
        view = this.getView(view);

      } else if (this.isList) {
        view = this.getItem(view);

      } else {
        view = findView(this, view);
      }
    }

    // handle `preCompile` middleware
    this.handleView('preCompile', view);

    // determine the name of the engine to use
    var ext = utils.resolveEngine(view, locals, this.options);
    // get the actual engine (object)
    var engine = this.getEngine(ext);

    if (typeof engine === 'undefined') {
      throw this.formatError('compile', 'engine', ext);
    }

    engine.options.engineName = engine.options.name;
    delete engine.options.name;

    // get engine options (settings)
    var settings = utils.merge({}, locals.settings, engine.options);
    var ctx = view.context(locals);

    // apply layout
    view = this.applyLayout(view);

    // Bind context to helpers before passing to the engine.
    this.bindHelpers(view, locals, ctx, (locals.async = !!isAsync));

    // shallow clone the context and locals
    settings = utils.merge({}, ctx, locals, settings);
    settings = utils.merge({}, settings, this.mergePartials(settings));

    // compile the string
    view.fn = engine.compile(view.content, settings);

    // handle `postCompile` middleware
    this.handleView('postCompile', view);
    return view;
  };

  /**
   * Render a view with the given `locals` and `callback`.
   *
   * ```js
   * var blogPost = app.post.getView('2015-09-01-foo-bar');
   * app.render(blogPost, {title: 'Foo'}, function(err, view) {
   *   // `view` is an object with a rendered `content` property
   * });
   * ```
   * @name .render
   * @param  {Object|String} `view` Instance of `View`
   * @param  {Object} `locals` Locals to pass to template engine.
   * @param  {Function} `callback`
   * @api public
   */

  proto.render = function(view, locals, cb) {
    if (typeof locals === 'function') {
      cb = locals;
      locals = {};
    }

    if (typeof cb !== 'function') {
      throw this.formatError('render', 'callback');
    }

    // if `view` is a string, see if it's a cached view
    if (typeof view === 'string') {
      if (this.isCollection) {
        view = this.getView(view);

      } else if (this.isList) {
        view = this.getItem(view);

      } else {
        view = findView(this, view);
      }
    }

    // handle `preRender` middleware
    this.handleView('preRender', view);

    // get the engine
    var ext = utils.resolveEngine(view, locals, this.options);
    var engine = this.getEngine(ext);

    if (!engine) {
      cb(this.formatError('render', 'engine', ext));
      return;
    }

    var isAsync = typeof cb === 'function';

    // if it's not already compiled, do that first
    if (typeof view.fn !== 'function') {
      try {
        view = this.compile(view, locals, isAsync);
      } catch (err) {
        this.emit('error', err);
        cb(err);
        return;
      }
    }

    // build the context from the view's data, ctx object
    // created above, and locals passed to the render method
    var context = this.context(view, locals);

    // render the view
    engine.render(view.fn, context, function(err, res) {
      if (err) {
        // rethrow is a noop if `options.rethrow` is not true
        err = this.rethrow('render', err, view, context);
        this.emit('error', err);
        cb(err);
        return;
      }

      view.content = res;

      // handle `postRender` middleware
      this.handle('postRender', view, cb);
    }.bind(this));
  };
};

function findView(app, view) {
  var keys = app.viewTypes.renderable;
  var len = keys.length, i = -1;
  while (++i < len) {
    view = app.find(view, keys[i]);
    if (view) {
      break;
    }
  }
  return view;
}
<commit_msg>comments<commit_after>'use strict';

var utils = require('../utils');

module.exports = function(proto) {

  /**
   * Set view types for a collection.
   *
   * @param {String} `plural` e.g. `pages`
   * @param {Object} `options`
   */

  proto.viewType = proto.viewType || function(plural, types) {
    var len = types.length, i = 0;
    while (len--) {
      var type = types[i++];
      this.viewTypes[type] = this.viewTypes[type] || [];
      if (this.viewTypes[type].indexOf(plural) === -1) {
        this.viewTypes[type].push(plural);
      }
    }
    return types;
  };

  /**
   * Iterates over `renderable` view collections
   * and returns the first view that matches the
   * given `view` name
   */

  function findView(app, name) {
    var keys = app.viewTypes.renderable;
    var len = keys.length;
    var i = -1;

    var res = null;
    while (++i < len) {
      res = app.find(name, keys[i]);
      if (res) {
        break;
      }
    }
    return res;
  }

  /**
   * Compile `content` with the given `locals`.
   *
   * ```js
   * var indexPage = app.page('some-index-page.hbs');
   * var view = app.compile(indexPage);
   * // view.fn => [function]
   *
   * // you can call the compiled function more than once
   * // to render the view with different data
   * view.fn({title: 'Foo'});
   * view.fn({title: 'Bar'});
   * view.fn({title: 'Baz'});
   * ```
   *
   * @name .compile
   * @param  {Object|String} `view` View object.
   * @param  {Object} `locals`
   * @param  {Boolean} `isAsync` Load async helpers
   * @return {Object} View object with `fn` property with the compiled function.
   * @api public
   */

  proto.compile = function(view, locals, isAsync) {
    if (typeof locals === 'boolean') {
      isAsync = locals;
      locals = {};
    }

    if (typeof locals === 'function' || typeof isAsync === 'function') {
      throw this.formatError('compile', 'callback');
    }

    locals = utils.extend({settings: {}}, locals);

    // if `view` is a string, see if it's a cached view
    if (typeof view === 'string') {
      if (this.isCollection) {
        view = this.getView(view);

      } else if (this.isList) {
        view = this.getItem(view);

      } else {
        view = findView(this, view);
      }
    }

    // handle `preCompile` middleware
    this.handleView('preCompile', view);

    // determine the name of the engine to use
    var ext = utils.resolveEngine(view, locals, this.options);
    // get the actual engine (object)
    var engine = this.getEngine(ext);

    if (typeof engine === 'undefined') {
      throw this.formatError('compile', 'engine', ext);
    }

    engine.options.engineName = engine.options.name;
    delete engine.options.name;

    // get engine options (settings)
    var settings = utils.merge({}, locals.settings, engine.options);
    var ctx = view.context(locals);

    // apply layout
    view = this.applyLayout(view);

    // Bind context to helpers before passing to the engine.
    this.bindHelpers(view, locals, ctx, (locals.async = !!isAsync));

    // shallow clone the context and locals
    settings = utils.merge({}, ctx, locals, settings);
    settings = utils.merge({}, settings, this.mergePartials(settings));

    // compile the string
    view.fn = engine.compile(view.content, settings);

    // handle `postCompile` middleware
    this.handleView('postCompile', view);
    return view;
  };

  /**
   * Render a view with the given `locals` and `callback`.
   *
   * ```js
   * var blogPost = app.post.getView('2015-09-01-foo-bar');
   * app.render(blogPost, {title: 'Foo'}, function(err, view) {
   *   // `view` is an object with a rendered `content` property
   * });
   * ```
   * @name .render
   * @param  {Object|String} `view` Instance of `View`
   * @param  {Object} `locals` Locals to pass to template engine.
   * @param  {Function} `callback`
   * @api public
   */

  proto.render = function(view, locals, cb) {
    if (typeof locals === 'function') {
      cb = locals;
      locals = {};
    }

    if (typeof cb !== 'function') {
      throw this.formatError('render', 'callback');
    }

    // if `view` is a string, see if it's a cached view
    if (typeof view === 'string') {
      if (this.isCollection) {
        view = this.getView(view);

      } else if (this.isList) {
        view = this.getItem(view);

      } else {
        view = findView(this, view);
      }
    }

    // handle `preRender` middleware
    this.handleView('preRender', view);

    // get the engine
    var ext = utils.resolveEngine(view, locals, this.options);
    var engine = this.getEngine(ext);

    if (!engine) {
      cb(this.formatError('render', 'engine', ext));
      return;
    }

    var isAsync = typeof cb === 'function';

    // if it's not already compiled, do that first
    if (typeof view.fn !== 'function') {
      try {
        view = this.compile(view, locals, isAsync);
      } catch (err) {
        this.emit('error', err);
        cb(err);
        return;
      }
    }

    // build the context from the view's data, ctx object
    // created above, and locals passed to the render method
    var context = this.context(view, locals);

    // render the view
    engine.render(view.fn, context, function(err, res) {
      if (err) {
        // rethrow is a noop if `options.rethrow` is not true
        err = this.rethrow('render', err, view, context);
        this.emit('error', err);
        cb(err);
        return;
      }

      view.content = res;

      // handle `postRender` middleware
      this.handle('postRender', view, cb);
    }.bind(this));
  };
};
<|endoftext|>


<commit_before>var connect = require('connect');
var http = require('http');
var mocker = require('connect-api-mocker-adv');

console.log("1");

var cors = require('cors');
var app = connect();

console.log("2");

app.use(cors());

 var   options = {
      urlRoot: '/api',
      pathRoot: 'mocks',
      ignoreQuery: false
    };

console.log("3");

app.use(mocker(options));

console.log("4");

//create node.js http server and listen on port
http.createServer(app).listen(3000, "0.0.0.0");

console.log("5");	<commit_msg>Start the node app from "app/mocks" folder for docker image.<commit_after>var connect = require('connect');
var http = require('http');
var mocker = require('connect-api-mocker-adv');

console.log("1");

var cors = require('cors');
var app = connect();

console.log("2");

app.use(cors());

 var   options = {
      urlRoot: '/api',
      pathRoot: 'app/mocks',
      ignoreQuery: false
    };

console.log("3");

app.use(mocker(options));

console.log("4");

//create node.js http server and listen on port
http.createServer(app).listen(3000, "0.0.0.0");

console.log("5");	<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var jsonFile = require('jsonfile');
var compare = require('node-version-compare');
var spawn = require('child_process').spawn;
var _ = require('underscore');
var commondir = require('commondir');

module.exports = function HandleUsingProcess(grunt, options, commands, callback) {
    var version = grunt.file.readJSON('./package.json').version;
    var projectPkg = grunt.file.readJSON('package.json');

    try {
        var cucumberVersion = require('../../cucumber/package.json').version;
    }catch(e) {
        throw new Error('Cucumber.js is not installed. NOTE: You can install Cucumber by "npm i cucumber". ' +
            'For more information please visit "https://www.npmjs.com/package/cucumber"');
    }

    function getBinPath(winBinPath, unixBinPath) {
        return process.platform === 'win32' ? winBinPath : unixBinPath;
    }

    var WIN32_BIN_PATH,
        UNIX_BIN_PATH;

    console.log('compare(\'0.8.0\', cucumberVersion) ', compare('0.8.0', '0.7.0') );
    if (options.executeParallel) {
        if(compare('0.8.0', cucumberVersion) > 0) {
            UNIX_BIN_PATH = 'node_modules/parallel-cucumber/bin/parallel-cucumber-js';
            WIN32_BIN_PATH = 'node_modules\\parallel-cucumber\\bin\\parallel-cucumber-js';
        } else {
            UNIX_BIN_PATH = 'node_modules/grunt-cucumberjs/node_modules/cucumber-parallel/bin/cucumber-parallel';
            WIN32_BIN_PATH = 'node_modules\\grunt-cucumberjs\\node_modules\\cucumber-parallel\\bin\\cucumber-parallel';
            if (!grunt.file.exists(getBinPath(WIN32_BIN_PATH,UNIX_BIN_PATH))) {
                UNIX_BIN_PATH = 'node_modules/cucumber-parallel/bin/cucumber-parallel';
                WIN32_BIN_PATH = 'node_modules\\cucumber-parallel\\bin\\cucumber-parallel';
            }
        }

    } else {
        WIN32_BIN_PATH = 'node_modules\\.bin\\cucumber-js.cmd';
        UNIX_BIN_PATH = 'node_modules/cucumber/bin/cucumber.js';
    }

    var buffer = [];
    var cucumber;
    var binPath = getBinPath(WIN32_BIN_PATH,UNIX_BIN_PATH);

    if (!grunt.file.exists(binPath)) {
        if(options.executeParallel) {
            grunt.log.error('cucumber parallel binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows');
        } else {
            grunt.log.error('cucumberjs binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows');
        }
        return callback(false);
    }

    cucumber = spawn(binPath, commands);

    cucumber.stdout.on('data', function(data) {
        if (options.isHtml) {
            if (options.debug) {
                process.stdout.write(data);
            }
            buffer.push(data);
        } else {
            grunt.log.write(data);
        }
    });

    cucumber.stderr.on('data', function(data) {
        if (options.debug) {
            process.stdout.write(data);
        }
        var stderr = new Buffer(data);
        grunt.log.error(stderr.toString());
    });

    cucumber.on('close', function(code) {
        if (options.isHtml) {

            var featureJsonOutput,
                featureOutput;

            if (options.executeParallel) {
                featureOutput = JSON.stringify(jsonFile.readFileSync(options.output + '.json'));
            } else {
                var jsonOutputFilePath = options.output + '.json';
                var jsonOutput;

                if (fs.existsSync(options.output + '.json')) {
                    jsonOutput= JSON.stringify(jsonFile.readFileSync(jsonOutputFilePath));
                } else {
                    jsonOutput = Buffer.concat(buffer).toString();
                }

                var featureStartIndex = jsonOutput.search(/\[\s*{\s*\"id\"/g);

                var logOutput = jsonOutput.substring(0, featureStartIndex - 1);

                featureOutput = jsonOutput.substring(featureStartIndex);
            }

            try {
                featureJsonOutput = JSON.parse(featureOutput);
            } catch (e) {
                grunt.log.error('Unable to parse cucumberjs output into json.');

                return callback(false);
            }

            generateReport(featureJsonOutput, logOutput);
        }

        return callback(code);
    });

    /**
     * Adds passed/failed properties on features/scenarios
     *
     * @param {object} suite The test suite object
     */
    var setStats = function(suite) {
        var features = suite.features;
        var rootDir = commondir(_.pluck(features, 'uri'));
        var scrshotDir;
        if (options.output.lastIndexOf('/') > -1) {
            scrshotDir = options.output.substring(0, options.output.lastIndexOf('/')) + '/screenshot/';
        } else {
            scrshotDir = 'screenshot/';
        }
        features.forEach(function(feature) {
            feature.passed = 0;
            feature.failed = 0;
            feature.notdefined = 0;
            feature.skipped = 0;
            feature.relativeFolder = feature.uri.slice(rootDir.length);

            if (!feature.elements) {
                return;
            }

            feature.elements.forEach(function(element) {
                element.passed = 0;
                element.failed = 0;
                element.notdefined = 0;
                element.skipped = 0;

                element.steps.forEach(function(step) {
                    if (step.embeddings !== undefined) {
                        var Base64 = require('js-base64').Base64;
                        step.embeddings.forEach(function (embedding) {
                            if (embedding.mime_type === "text/plain") {
                                if (!step.text) {
                                    step.text = Base64.decode(embedding.data)
                                } else {
                                    step.text = step.text.concat('
' + Base64.decode(embedding.data));
                                }
                            }else {
                                var stepData = step.embeddings[0],
                                    name = step.name && step.name.split(' ').join('_') || step.keyword.trim();
                                if (!fs.existsSync(scrshotDir)) {
                                    fs.mkdirSync(scrshotDir);
                                }
                                name = name + Math.round(Math.random() * 10000) + '.png'; //randomize the file name
                                var filename = scrshotDir + name;
                                fs.writeFile(filename, new Buffer(embedding.data, 'base64'), function (err) {
                                    if (err) {
                                        console.error('Error saving screenshot ' + filename); //asynchronously save screenshot
                                    }
                                });
                                step.image = 'screenshot/' + name;
                            }
                        });
                    }

                    if (!step.result) {
                        return 0;
                    }
                    if (step.result.status === 'passed') {
                        return element.passed++;
                    }
                    if (step.result.status === 'failed') {
                        return element.failed++;
                    }
                    if (step.result.status === 'undefined') {
                        return element.notdefined++;
                    }

                    element.skipped++;
                });

                if (element.notdefined > 0) {
                    suite.scenarios.notdefined++;
                    return feature.notdefined++;
                }

                if (element.failed > 0) {
                    suite.scenarios.failed++;
                    return feature.failed++;
                }

                if (element.skipped > 0) {
                    suite.scenarios.skipped++;
                    return feature.skipped++;
                }

                if (element.passed > 0) {
                    suite.scenarios.passed++;
                    return feature.passed++;
                }
            });

            if (feature.failed > 0) {
                return suite.failed++;
            }
            if (feature.passed > 0) {
                return suite.passed++;
            }
        });

        suite.features = features;

        return suite;
    };

    /**
     * Returns the path of a template
     *
     * @param {string} name The template name
     */
    var getPath = function(name) {
        var path = 'node_modules/grunt-cucumberjs/templates/' + options.theme + '/' + name;
        // return the users custom template if it has been defined
        if (grunt.file.exists(options.templateDir + '/' + name)) {
            path = options.templateDir + '/' + name;
        }

        return path;
    };

    /**
     * Generate html report
     *
     * @param {object} featureOutput Features result object
     * @param {string} logOutput Contains any console statements captured during the test run
     */
    var generateReport = function(featureOutput, logOutput) {
        var suite = {
            name: projectPkg.name,
            features: featureOutput,
            passed: 0,
            failed: 0,
            scenarios: {
                passed: 0,
                failed: 0,
                skipped: 0,
                notdefined: 0
            },
            logOutput: logOutput
        };

        suite = setStats(suite);

        if (options.saveJson) {
            grunt.file.write(options.output + '.json', JSON.stringify(featureOutput, null, '\t'));
        }
        grunt.file.write(
            options.output,
            _.template(grunt.file.read(getPath('index.tmpl')))({
                suite: suite,
                version: version,
                time: new Date(),
                features: _.template(grunt.file.read(getPath('features.tmpl')))({
                    suite: suite,
                    _: _
                }),
                styles: grunt.file.read(getPath('style.css')),
                script: grunt.file.read(getPath('script.js')),
                piechart: (options.theme === 'bootstrap')? grunt.file.read(getPath('piechart.js')) : undefined
            })
        );

        grunt.log.writeln('Generated ' + options.output + ' successfully.');
    };

};
<commit_msg>clean up the console logs<commit_after>'use strict';

var fs = require('fs');
var jsonFile = require('jsonfile');
var compare = require('node-version-compare');
var spawn = require('child_process').spawn;
var _ = require('underscore');
var commondir = require('commondir');

module.exports = function HandleUsingProcess(grunt, options, commands, callback) {
    var version = grunt.file.readJSON('./package.json').version;
    var projectPkg = grunt.file.readJSON('package.json');

    try {
        var cucumberVersion = require('../../cucumber/package.json').version;
    }catch(e) {
        throw new Error('Cucumber.js is not installed. NOTE: You can install Cucumber by "npm i cucumber". ' +
            'For more information please visit "https://www.npmjs.com/package/cucumber"');
    }

    function getBinPath(winBinPath, unixBinPath) {
        return process.platform === 'win32' ? winBinPath : unixBinPath;
    }

    var WIN32_BIN_PATH,
        UNIX_BIN_PATH;

    if (options.executeParallel) {
        if(compare('0.8.0', cucumberVersion) > 0) {
            UNIX_BIN_PATH = 'node_modules/parallel-cucumber/bin/parallel-cucumber-js';
            WIN32_BIN_PATH = 'node_modules\\parallel-cucumber\\bin\\parallel-cucumber-js';
        } else {
            UNIX_BIN_PATH = 'node_modules/grunt-cucumberjs/node_modules/cucumber-parallel/bin/cucumber-parallel';
            WIN32_BIN_PATH = 'node_modules\\grunt-cucumberjs\\node_modules\\cucumber-parallel\\bin\\cucumber-parallel';
            if (!grunt.file.exists(getBinPath(WIN32_BIN_PATH,UNIX_BIN_PATH))) {
                UNIX_BIN_PATH = 'node_modules/cucumber-parallel/bin/cucumber-parallel';
                WIN32_BIN_PATH = 'node_modules\\cucumber-parallel\\bin\\cucumber-parallel';
            }
        }

    } else {
        WIN32_BIN_PATH = 'node_modules\\.bin\\cucumber-js.cmd';
        UNIX_BIN_PATH = 'node_modules/cucumber/bin/cucumber.js';
    }

    var buffer = [];
    var cucumber;
    var binPath = getBinPath(WIN32_BIN_PATH,UNIX_BIN_PATH);

    if (!grunt.file.exists(binPath)) {
        if(options.executeParallel) {
            grunt.log.error('cucumber parallel binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows');
        } else {
            grunt.log.error('cucumberjs binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows');
        }
        return callback(false);
    }

    cucumber = spawn(binPath, commands);

    cucumber.stdout.on('data', function(data) {
        if (options.isHtml) {
            if (options.debug) {
                process.stdout.write(data);
            }
            buffer.push(data);
        } else {
            grunt.log.write(data);
        }
    });

    cucumber.stderr.on('data', function(data) {
        if (options.debug) {
            process.stdout.write(data);
        }
        var stderr = new Buffer(data);
        grunt.log.error(stderr.toString());
    });

    cucumber.on('close', function(code) {
        if (options.isHtml) {

            var featureJsonOutput,
                featureOutput;

            if (options.executeParallel) {
                featureOutput = JSON.stringify(jsonFile.readFileSync(options.output + '.json'));
            } else {
                var jsonOutputFilePath = options.output + '.json';
                var jsonOutput;

                if (fs.existsSync(options.output + '.json')) {
                    jsonOutput= JSON.stringify(jsonFile.readFileSync(jsonOutputFilePath));
                } else {
                    jsonOutput = Buffer.concat(buffer).toString();
                }

                var featureStartIndex = jsonOutput.search(/\[\s*{\s*\"id\"/g);

                var logOutput = jsonOutput.substring(0, featureStartIndex - 1);

                featureOutput = jsonOutput.substring(featureStartIndex);
            }

            try {
                featureJsonOutput = JSON.parse(featureOutput);
            } catch (e) {
                grunt.log.error('Unable to parse cucumberjs output into json.');

                return callback(false);
            }

            generateReport(featureJsonOutput, logOutput);
        }

        return callback(code);
    });

    /**
     * Adds passed/failed properties on features/scenarios
     *
     * @param {object} suite The test suite object
     */
    var setStats = function(suite) {
        var features = suite.features;
        var rootDir = commondir(_.pluck(features, 'uri'));
        var scrshotDir;
        if (options.output.lastIndexOf('/') > -1) {
            scrshotDir = options.output.substring(0, options.output.lastIndexOf('/')) + '/screenshot/';
        } else {
            scrshotDir = 'screenshot/';
        }
        features.forEach(function(feature) {
            feature.passed = 0;
            feature.failed = 0;
            feature.notdefined = 0;
            feature.skipped = 0;
            feature.relativeFolder = feature.uri.slice(rootDir.length);

            if (!feature.elements) {
                return;
            }

            feature.elements.forEach(function(element) {
                element.passed = 0;
                element.failed = 0;
                element.notdefined = 0;
                element.skipped = 0;

                element.steps.forEach(function(step) {
                    if (step.embeddings !== undefined) {
                        var Base64 = require('js-base64').Base64;
                        step.embeddings.forEach(function (embedding) {
                            if (embedding.mime_type === "text/plain") {
                                if (!step.text) {
                                    step.text = Base64.decode(embedding.data)
                                } else {
                                    step.text = step.text.concat('
' + Base64.decode(embedding.data));
                                }
                            }else {
                                var stepData = step.embeddings[0],
                                    name = step.name && step.name.split(' ').join('_') || step.keyword.trim();
                                if (!fs.existsSync(scrshotDir)) {
                                    fs.mkdirSync(scrshotDir);
                                }
                                name = name + Math.round(Math.random() * 10000) + '.png'; //randomize the file name
                                var filename = scrshotDir + name;
                                fs.writeFile(filename, new Buffer(embedding.data, 'base64'), function (err) {
                                    if (err) {
                                        console.error('Error saving screenshot ' + filename); //asynchronously save screenshot
                                    }
                                });
                                step.image = 'screenshot/' + name;
                            }
                        });
                    }

                    if (!step.result) {
                        return 0;
                    }
                    if (step.result.status === 'passed') {
                        return element.passed++;
                    }
                    if (step.result.status === 'failed') {
                        return element.failed++;
                    }
                    if (step.result.status === 'undefined') {
                        return element.notdefined++;
                    }

                    element.skipped++;
                });

                if (element.notdefined > 0) {
                    suite.scenarios.notdefined++;
                    return feature.notdefined++;
                }

                if (element.failed > 0) {
                    suite.scenarios.failed++;
                    return feature.failed++;
                }

                if (element.skipped > 0) {
                    suite.scenarios.skipped++;
                    return feature.skipped++;
                }

                if (element.passed > 0) {
                    suite.scenarios.passed++;
                    return feature.passed++;
                }
            });

            if (feature.failed > 0) {
                return suite.failed++;
            }
            if (feature.passed > 0) {
                return suite.passed++;
            }
        });

        suite.features = features;

        return suite;
    };

    /**
     * Returns the path of a template
     *
     * @param {string} name The template name
     */
    var getPath = function(name) {
        var path = 'node_modules/grunt-cucumberjs/templates/' + options.theme + '/' + name;
        // return the users custom template if it has been defined
        if (grunt.file.exists(options.templateDir + '/' + name)) {
            path = options.templateDir + '/' + name;
        }

        return path;
    };

    /**
     * Generate html report
     *
     * @param {object} featureOutput Features result object
     * @param {string} logOutput Contains any console statements captured during the test run
     */
    var generateReport = function(featureOutput, logOutput) {
        var suite = {
            name: projectPkg.name,
            features: featureOutput,
            passed: 0,
            failed: 0,
            scenarios: {
                passed: 0,
                failed: 0,
                skipped: 0,
                notdefined: 0
            },
            logOutput: logOutput
        };

        suite = setStats(suite);

        if (options.saveJson) {
            grunt.file.write(options.output + '.json', JSON.stringify(featureOutput, null, '\t'));
        }
        grunt.file.write(
            options.output,
            _.template(grunt.file.read(getPath('index.tmpl')))({
                suite: suite,
                version: version,
                time: new Date(),
                features: _.template(grunt.file.read(getPath('features.tmpl')))({
                    suite: suite,
                    _: _
                }),
                styles: grunt.file.read(getPath('style.css')),
                script: grunt.file.read(getPath('script.js')),
                piechart: (options.theme === 'bootstrap')? grunt.file.read(getPath('piechart.js')) : undefined
            })
        );

        grunt.log.writeln('Generated ' + options.output + ' successfully.');
    };

};
<|endoftext|>


<commit_before>
var apis = require('../../config/providers').yahoo.api;

function yahoo () {

    this.options.get = function (api, options) {
        this.options.oauth.call(this, options);
    }

    this.options.post = function (api, options) {
        this.options.oauth.call(this, options);
    }

    this.url = function (api, options) {
        return apis[options.api||this.api].domain + this.createPath(api, options);
    }
}

exports = module.exports = yahoo;
<commit_msg>Yahoo get requests can be made by only appid too<commit_after>
var apis = require('../../config/providers').yahoo.api;

function yahoo () {

    this.options.get = function (api, options) {
        if (options.oauth)
            this.options.oauth.call(this, options);
    }

    this.options.post = function (api, options) {
        this.options.oauth.call(this, options);
    }

    this.url = function (api, options) {
        return apis[options.api||this.api].domain + this.createPath(api, options);
    }
}

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