<commit_before>var EventEmitter = require('events').EventEmitter;
var util = require('util');
var extend = util._extend;
var Emitter = require('pattern-emitter');
var Entities = require('html-entities').AllHtmlEntities;
var entities = new Entities();
var moment = require('moment');
var fs = require('fs');
var path = require('path');
var templates = require('./hbs');

'use strict';

module.exports = {
  DopeBot: DopeBot
}

function DopeBot(logger, slack, mopidy, spotify, settings) {
  this.logger = logger;
  this.slack = slack;
  this.mopidy = mopidy;
  this.spotify = spotify;
  this.settings = settings;
  this.emitter = new Emitter();
  this.commands = [];
  this.channels = {};
  this.params = this.settings.params;
  this.state = {};
}

util.inherits(DopeBot, EventEmitter);

DopeBot.prototype.command = function(cmd) {
  this.commands.push(cmd);
  return this;
}

DopeBot.prototype.post = function(channel, message) {
  this.slack.postMessage(channel, entities.decode(message), this.params);
}

DopeBot.prototype.broadcast = function(message) {
  for(var id in this.channels) {
    this.post(id, message);
  }
}

DopeBot.prototype.run = function() {
  this.logger.info('Starting dopebot', this.settings);
  this._init();
	this.mopidy.connect();
}

DopeBot.prototype.getState = function(full) {
  var self = this;

  if (full) {
    this.mopidy.playback.getState()
    .done(function(state) {
      self.state.state = state;
    });
    this.mopidy.playback.getCurrentTlTrack()
    .done(function(tlTrack) {
      self.state.tlTrack = tlTrack;
    });
    this.mopidy.mixer.getVolume()
    .done(function(volume) {
      self.state.volume = volume;
    })
  }

  this.mopidy.tracklist.getConsume()
  .done(function(value) {
    self.state.consume = value;
  });
  this.mopidy.tracklist.getRandom()
  .done(function(value) {
    self.state.random = value;
  });
  this.mopidy.tracklist.getRepeat()
  .done(function(value) {
    self.state.repeat = value;
  });
  this.mopidy.tracklist.getSingle()
  .done(function(value) {
    self.state.single = value;
  });
}

DopeBot.prototype._init = function() {

  var self = this;

  if (this.settings.history === true) {

    this.history = {};

    this.on('tracklist:add', function(message, tlTracks) {
      if (!self.history[message.user]) {
        self.history[message.user] = {
          tracks: {}
        }
      }

      for(var i in tlTracks) {
        tlTrack = tlTracks[i];
        self.history[message.user].tracks[tlTrack.tlid] =  {
          track: tlTrack,
          timestamp: Date.now()
        }
      }
    });

    this.on('tracklist:clear', function() {
      self.history = {};
    });
  }

  this.mopidy.on("state:online", function () {
    self.logger.info('Connected to Mopidy');
  });

  this.slack.on('start', function () {
    self.info = this;
    self.baseRegEx = self.settings.dialog === true ? new RegExp('<@' + self.info.self.id + '>:?[ ]+') : /^/;
    self.logger.info('Connected to Slack. I am %s', self.info.self.id);

    self.emitter.on(/.*/, function(message) {
      if (message && message.channel) {
        for(var i in self.info.channels) {
          if (self.info.channels[i].id === message.channel) {
            self.channels[self.info.channels[i].id] = self.info.channels[i];
          }
        }
      }
    });

    self.command({regEx: /state/, help: 'Gets the current playback state', execute: function(message) {
      self.post(message.channel, templates.state({state: self.state}));
    }});

    self.command({regEx: /help/, help: 'Prints out this message', execute: function(message) {
      self.post(message.channel, templates.help({commands: self.commands}));
    }});

    for(var i in self.commands) {
      var cmd = self.commands[i];
      self.emitter.on(new RegExp(self.baseRegEx.source + cmd.regEx.source + /$/.source), cmd.execute.bind(cmd));
    }

    self.getState(true);

  });

  this.mopidy.on('event:trackPlaybackStarted', function (event) {
    self.logger.info('Track playback started');
    self.state.tlTrack = event.tl_track;
    if (self.settings.history === true) {
      for (var user in self.history) {
        for (var tlid in self.history[user].tracks) {
          if (event.tl_track.tlid === parseInt(tlid)) {
            var name
            for(var u in self.info.users) {
              if (self.info.users[u].id === user) {
                self.state.history = {
                  user: self.info.users[u].name,
                  timestamp: moment(self.history[user].tracks[tlid].timestamp).fromNow()
                }
                self.broadcast(templates.state({state: self.state}));
                return;
              }
            }
          }
        }
      }
    }
    self.broadcast(templates.state({state: self.state}));
  });

  this.mopidy.on('event:volumeChanged', function (event) {
    self.logger.info('Volume changed');
    self.state.volume = event.volume;
  });

  this.mopidy.on('event:playbackStateChanged', function(event) {
    self.logger.info('Plaback state changed');
    self.state.state = event.new_state;
  });

  this.mopidy.on('event:optionsChanged', function() {
    self.logger.info('Playback options changed');
    self.getState();
  });

  this.slack.on('message', function (message) {
    self.logger.debug('Received message', message);
    if(message.type === 'message' && message.text) {
      // remove slack URL formatting
      message.text = message.text.replace(/[<>]/g, '');
      self.emitter.emit(message.text, message, self);
    }
  });

}

<commit_msg>Improved history state<commit_after>var EventEmitter = require('events').EventEmitter;
var util = require('util');
var extend = util._extend;
var Emitter = require('pattern-emitter');
var Entities = require('html-entities').AllHtmlEntities;
var entities = new Entities();
var moment = require('moment');
var fs = require('fs');
var path = require('path');
var templates = require('./hbs');

'use strict';

module.exports = {
  DopeBot: DopeBot
}

function DopeBot(logger, slack, mopidy, spotify, settings) {
  this.logger = logger;
  this.slack = slack;
  this.mopidy = mopidy;
  this.spotify = spotify;
  this.settings = settings;
  this.emitter = new Emitter();
  this.commands = [];
  this.channels = {};
  this.params = this.settings.params;
  this.state = {};
}

util.inherits(DopeBot, EventEmitter);

DopeBot.prototype.command = function(cmd) {
  this.commands.push(cmd);
  return this;
}

DopeBot.prototype.post = function(channel, message) {
  this.slack.postMessage(channel, entities.decode(message), this.params);
}

DopeBot.prototype.broadcast = function(message) {
  for(var id in this.channels) {
    this.post(id, message);
  }
}

DopeBot.prototype.run = function() {
  this.logger.info('Starting dopebot', this.settings);
  this._init();
	this.mopidy.connect();
}

DopeBot.prototype.getState = function(full) {
  var self = this;

  if (full) {
    this.mopidy.playback.getState()
    .done(function(state) {
      self.state.state = state;
    });
    this.mopidy.playback.getCurrentTlTrack()
    .done(function(tlTrack) {
      self.state.tlTrack = tlTrack;
    });
    this.mopidy.mixer.getVolume()
    .done(function(volume) {
      self.state.volume = volume;
    })
  }

  this.mopidy.tracklist.getConsume()
  .done(function(value) {
    self.state.consume = value;
  });
  this.mopidy.tracklist.getRandom()
  .done(function(value) {
    self.state.random = value;
  });
  this.mopidy.tracklist.getRepeat()
  .done(function(value) {
    self.state.repeat = value;
  });
  this.mopidy.tracklist.getSingle()
  .done(function(value) {
    self.state.single = value;
  });
}

DopeBot.prototype.updateHistoryState = function() {
  for (var user in this.history) {
    for (var tlid in this.history[user].tracks) {
      if (event.tl_track.tlid === parseInt(tlid)) {
        for(var u in this.info.users) {
          if (this.info.users[u].id === user) {
            this.state.history = {
              user: this.info.users[u].name,
              timestamp: moment(this.history[user].tracks[tlid].timestamp).fromNow()
            }
            return;
          }
        }
      }
    }
  }
}

DopeBot.prototype._init = function() {

  var self = this;

  if (this.settings.history === true) {

    this.history = {};

    this.on('tracklist:add', function(message, tlTracks) {
      if (!self.history[message.user]) {
        self.history[message.user] = {
          tracks: {}
        }
      }

      for(var i in tlTracks) {
        tlTrack = tlTracks[i];
        self.history[message.user].tracks[tlTrack.tlid] =  {
          track: tlTrack,
          timestamp: Date.now()
        }
      }
    });

    this.on('tracklist:clear', function() {
      self.history = {};
    });
  }

  this.mopidy.on("state:online", function () {
    self.logger.info('Connected to Mopidy');
  });

  this.slack.on('start', function () {
    self.info = this;
    self.baseRegEx = self.settings.dialog === true ? new RegExp('<@' + self.info.self.id + '>:?[ ]+') : /^/;
    self.logger.info('Connected to Slack. I am %s', self.info.self.id);

    self.emitter.on(/.*/, function(message) {
      if (message && message.channel) {
        for(var i in self.info.channels) {
          if (self.info.channels[i].id === message.channel) {
            self.channels[self.info.channels[i].id] = self.info.channels[i];
          }
        }
      }
    });

    self.command({regEx: /state/, help: 'Gets the current playback state', execute: function(message) {
      if (self.settings.history === true) {
        self.updateHistoryState();
      }
      self.post(message.channel, templates.state({state: self.state}));
    }});

    self.command({regEx: /help/, help: 'Prints out this message', execute: function(message) {
      self.post(message.channel, templates.help({commands: self.commands}));
    }});

    for(var i in self.commands) {
      var cmd = self.commands[i];
      self.emitter.on(new RegExp(self.baseRegEx.source + cmd.regEx.source + /$/.source), cmd.execute.bind(cmd));
    }

    self.getState(true);

  });

  this.mopidy.on('event:trackPlaybackStarted', function (event) {
    self.logger.info('Track playback started');
    self.state.tlTrack = event.tl_track;
    if (self.settings.history === true) {
      self.updateHistoryState();
    }
    self.broadcast(templates.state({state: self.state}));
  });

  this.mopidy.on('event:volumeChanged', function (event) {
    self.logger.info('Volume changed');
    self.state.volume = event.volume;
  });

  this.mopidy.on('event:playbackStateChanged', function(event) {
    self.logger.info('Plaback state changed');
    self.state.state = event.new_state;
  });

  this.mopidy.on('event:optionsChanged', function() {
    self.logger.info('Playback options changed');
    self.getState();
  });

  this.slack.on('message', function (message) {
    self.logger.debug('Received message', message);
    if(message.type === 'message' && message.text) {
      // remove slack URL formatting
      message.text = message.text.replace(/[<>]/g, '');
      self.emitter.emit(message.text, message, self);
    }
  });

}

<|endoftext|>


<commit_before>'use strict';

/**
 * This cheap replica of DOM/Builder puts me to shame :-)
 *
 * Attributes are in the element.attrs object. Children is a list of
 * either other Elements or Strings for text content.
 **/
function Element(name, attrs) {
    this.name = name
    this.parent = null
    this.attrs = attrs || {}
    this.children = []
}

/*** Accessors ***/

/**
 * if (element.is('message', 'jabber:client')) ...
 **/
Element.prototype.is = function(name, xmlns) {
    return (this.getName() === name) &&
        (!xmlns || (this.getNS() === xmlns))
}

/* without prefix */
Element.prototype.getName = function() {
    if (this.name.indexOf(':') >= 0)
        return this.name.substr(this.name.indexOf(':') + 1)
    else
        return this.name
}

/**
 * retrieves the namespace of the current element, upwards recursively
 **/
Element.prototype.getNS = function() {
    if (this.name.indexOf(':') >= 0) {
        var prefix = this.name.substr(0, this.name.indexOf(':'))
        return this.findNS(prefix)
    } else {
        return this.findNS()
    }
}

/**
 * find the namespace to the given prefix, upwards recursively
 **/
Element.prototype.findNS = function(prefix) {
    if (!prefix) {
        /* default namespace */
        if (this.attrs.xmlns)
            return this.attrs.xmlns
        else if (this.parent)
            return this.parent.findNS()
    } else {
        /* prefixed namespace */
        var attr = 'xmlns:' + prefix
        if (this.attrs[attr])
            return this.attrs[attr]
        else if (this.parent)
            return this.parent.findNS(prefix)
    }
}

/**
 * Recursiverly gets all xmlns defined, in the form of {url:prefix}
 **/
Element.prototype.getXmlns = function() {
    var namespaces = {}

    if (this.parent)
        namespaces = this.parent.getXmlns()

    for (var attr in this.attrs) {
        var m = attr.match('xmlns:?(.*)')
        if (this.attrs.hasOwnProperty(attr) && m) {
            namespaces[this.attrs[attr]] = m[1]
        }
    }
    return namespaces
}

/**
 * xmlns can be null, returns the matching attribute.
 **/
Element.prototype.getAttr = function(name, xmlns) {
    if (!xmlns)
        return this.attrs[name]

    var namespaces = this.getXmlns()

    if (!namespaces[xmlns])
        return null

    return this.attrs[[namespaces[xmlns], name].join(':')]
}

/**
 * xmlns can be null
 **/
Element.prototype.getChild = function(name, xmlns) {
    return this.getChildren(name, xmlns)[0]
}

/**
 * xmlns can be null
 **/
Element.prototype.getChildren = function(name, xmlns) {
    var result = []
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if (child.getName &&
            (child.getName() === name) &&
            (!xmlns || (child.getNS() === xmlns)))
            result.push(child)
    }
    return result
}

/**
 * xmlns and recursive can be null
 **/
Element.prototype.getChildByAttr = function(attr, val, xmlns, recursive) {
    return this.getChildrenByAttr(attr, val, xmlns, recursive)[0]
}

/**
 * xmlns and recursive can be null
 **/
Element.prototype.getChildrenByAttr = function(attr, val, xmlns, recursive) {
    var result = []
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if (child.attrs &&
            (child.attrs[attr] === val) &&
            (!xmlns || (child.getNS() === xmlns)))
            result.push(child)
        if (recursive && child.getChildrenByAttr) {
            result.push(child.getChildrenByAttr(attr, val, xmlns, true))
        }
    }
    if (recursive) result = [].concat.apply([], result)
    return result
}

Element.prototype.getChildrenByFilter = function(filter, recursive) {
    var result = []
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if (filter(child))
            result.push(child)
        if (recursive && child.getChildrenByFilter){
            result.push(child.getChildrenByFilter(filter, true))
        }
    }
    if (recursive) {
        result = [].concat.apply([], result)
    }
    return result
}

Element.prototype.getText = function() {
    var text = ''
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if ((typeof child === 'string') || (typeof child === 'number')) {
            text += child
        }
    }
    return text
}

Element.prototype.getChildText = function(name, xmlns) {
    var child = this.getChild(name, xmlns)
    return child ? child.getText() : null
}

/**
 * Return all direct descendents that are Elements.
 * This differs from `getChildren` in that it will exclude text nodes,
 * processing instructions, etc.
 */
Element.prototype.getChildElements = function() {
    return this.getChildrenByFilter(function(child) {
        return child instanceof Element
    })
}

/*** Builder ***/

/** returns uppermost parent */
Element.prototype.root = function() {
    if (this.parent)
        return this.parent.root()
    else
        return this
}
Element.prototype.tree = Element.prototype.root

/** just parent or itself */
Element.prototype.up = function() {
    if (this.parent)
        return this.parent
    else
        return this
}

Element.prototype._getElement = function(name, attrs) {
    var element = new Element(name, attrs)
    return element
}

/** create child node and return it */
Element.prototype.c = function(name, attrs) {
    return this.cnode(this._getElement(name, attrs))
}

Element.prototype.cnode = function(child) {
    this.children.push(child)
    child.parent = this
    return child
}

/** add text node and return element */
Element.prototype.t = function(text) {
    this.children.push(text)
    return this
}

/*** Manipulation ***/

/**
 * Either:
 *   el.remove(childEl)
 *   el.remove('author', 'urn:...')
 */
Element.prototype.remove = function(el, xmlns) {
    var filter
    if (typeof el === 'string') {
        /* 1st parameter is tag name */
        filter = function(child) {
            return !(child.is &&
                 child.is(el, xmlns))
        }
    } else {
        /* 1st parameter is element */
        filter = function(child) {
            return child !== el
        }
    }

    this.children = this.children.filter(filter)

    return this
}

/**
 * To use in case you want the same XML data for separate uses.
 * Please refrain from this practise unless you know what you are
 * doing. Building XML with ltx is easy!
 */
Element.prototype.clone = function() {
    var clone = this._getElement(this.name, {})
    for (var k in this.attrs) {
        if (this.attrs.hasOwnProperty(k))
            clone.attrs[k] = this.attrs[k]
    }
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        clone.cnode(child.clone ? child.clone() : child)
    }
    return clone
}

Element.prototype.text = function(val) {
    if (val && this.children.length === 1) {
        this.children[0] = val
        return this
    }
    return this.getText()
}

Element.prototype.attr = function(attr, val) {
    if (((typeof val !== 'undefined') || (val === null))) {
        if (!this.attrs) {
            this.attrs = {}
        }
        this.attrs[attr] = val
        return this
    }
    return this.attrs[attr]
}

/*** Serialization ***/

Element.prototype.toString = function() {
    var s = ''
    this.write(function(c) {
        s += c
    })
    return s
}

Element.prototype._addChildren = function(writer) {
    writer('>')
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        /* Skip null/undefined */
        if (child || (child === 0)) {
            if (child.write) {
                child.write(writer)
            } else if (typeof child === 'string') {
                writer(escapeXmlText(child))
            } else if (child.toString) {
                writer(escapeXmlText(child.toString(10)))
            }
        }
    }
    writer('</')
    writer(this.name)
    writer('>')
}

Element.prototype.write = function(writer) {
    writer('<')
    writer(this.name)
    for (var k in this.attrs) {
        var v = this.attrs[k]
        if (v || (v === '') || (v === 0)) {
            writer(' ')
            writer(k)
            writer('="')
            if (typeof v !== 'string') {
                v = v.toString(10)
            }
            writer(escapeXml(v))
            writer('"')
        }
    }
    if (this.children.length === 0) {
        writer('/>')
    } else {
        this._addChildren(writer)
    }
}

function escapeXml(s) {
    return s.
        replace(/\&/g, '&').
        replace(/</g, '<').
        replace(/>/g, '>').
        replace(/"/g, '"').
        replace(/"/g, ''')
}

function escapeXmlText(s) {
    return s.
        replace(/\&/g, '&').
        replace(/</g, '<').
        replace(/>/g, '>')
}

exports.Element = Element
exports.escapeXml = escapeXml
<commit_msg>add toJSON to element<commit_after>'use strict';

/**
 * This cheap replica of DOM/Builder puts me to shame :-)
 *
 * Attributes are in the element.attrs object. Children is a list of
 * either other Elements or Strings for text content.
 **/
function Element(name, attrs) {
    this.name = name
    this.parent = null
    this.attrs = attrs || {}
    this.children = []
}

/*** Accessors ***/

/**
 * if (element.is('message', 'jabber:client')) ...
 **/
Element.prototype.is = function(name, xmlns) {
    return (this.getName() === name) &&
        (!xmlns || (this.getNS() === xmlns))
}

/* without prefix */
Element.prototype.getName = function() {
    if (this.name.indexOf(':') >= 0)
        return this.name.substr(this.name.indexOf(':') + 1)
    else
        return this.name
}

/**
 * retrieves the namespace of the current element, upwards recursively
 **/
Element.prototype.getNS = function() {
    if (this.name.indexOf(':') >= 0) {
        var prefix = this.name.substr(0, this.name.indexOf(':'))
        return this.findNS(prefix)
    } else {
        return this.findNS()
    }
}

/**
 * find the namespace to the given prefix, upwards recursively
 **/
Element.prototype.findNS = function(prefix) {
    if (!prefix) {
        /* default namespace */
        if (this.attrs.xmlns)
            return this.attrs.xmlns
        else if (this.parent)
            return this.parent.findNS()
    } else {
        /* prefixed namespace */
        var attr = 'xmlns:' + prefix
        if (this.attrs[attr])
            return this.attrs[attr]
        else if (this.parent)
            return this.parent.findNS(prefix)
    }
}

/**
 * Recursiverly gets all xmlns defined, in the form of {url:prefix}
 **/
Element.prototype.getXmlns = function() {
    var namespaces = {}

    if (this.parent)
        namespaces = this.parent.getXmlns()

    for (var attr in this.attrs) {
        var m = attr.match('xmlns:?(.*)')
        if (this.attrs.hasOwnProperty(attr) && m) {
            namespaces[this.attrs[attr]] = m[1]
        }
    }
    return namespaces
}

/**
 * xmlns can be null, returns the matching attribute.
 **/
Element.prototype.getAttr = function(name, xmlns) {
    if (!xmlns)
        return this.attrs[name]

    var namespaces = this.getXmlns()

    if (!namespaces[xmlns])
        return null

    return this.attrs[[namespaces[xmlns], name].join(':')]
}

/**
 * xmlns can be null
 **/
Element.prototype.getChild = function(name, xmlns) {
    return this.getChildren(name, xmlns)[0]
}

/**
 * xmlns can be null
 **/
Element.prototype.getChildren = function(name, xmlns) {
    var result = []
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if (child.getName &&
            (child.getName() === name) &&
            (!xmlns || (child.getNS() === xmlns)))
            result.push(child)
    }
    return result
}

/**
 * xmlns and recursive can be null
 **/
Element.prototype.getChildByAttr = function(attr, val, xmlns, recursive) {
    return this.getChildrenByAttr(attr, val, xmlns, recursive)[0]
}

/**
 * xmlns and recursive can be null
 **/
Element.prototype.getChildrenByAttr = function(attr, val, xmlns, recursive) {
    var result = []
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if (child.attrs &&
            (child.attrs[attr] === val) &&
            (!xmlns || (child.getNS() === xmlns)))
            result.push(child)
        if (recursive && child.getChildrenByAttr) {
            result.push(child.getChildrenByAttr(attr, val, xmlns, true))
        }
    }
    if (recursive) result = [].concat.apply([], result)
    return result
}

Element.prototype.getChildrenByFilter = function(filter, recursive) {
    var result = []
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if (filter(child))
            result.push(child)
        if (recursive && child.getChildrenByFilter){
            result.push(child.getChildrenByFilter(filter, true))
        }
    }
    if (recursive) {
        result = [].concat.apply([], result)
    }
    return result
}

Element.prototype.getText = function() {
    var text = ''
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        if ((typeof child === 'string') || (typeof child === 'number')) {
            text += child
        }
    }
    return text
}

Element.prototype.getChildText = function(name, xmlns) {
    var child = this.getChild(name, xmlns)
    return child ? child.getText() : null
}

/**
 * Return all direct descendents that are Elements.
 * This differs from `getChildren` in that it will exclude text nodes,
 * processing instructions, etc.
 */
Element.prototype.getChildElements = function() {
    return this.getChildrenByFilter(function(child) {
        return child instanceof Element
    })
}

/*** Builder ***/

/** returns uppermost parent */
Element.prototype.root = function() {
    if (this.parent)
        return this.parent.root()
    else
        return this
}
Element.prototype.tree = Element.prototype.root

/** just parent or itself */
Element.prototype.up = function() {
    if (this.parent)
        return this.parent
    else
        return this
}

Element.prototype._getElement = function(name, attrs) {
    var element = new Element(name, attrs)
    return element
}

/** create child node and return it */
Element.prototype.c = function(name, attrs) {
    return this.cnode(this._getElement(name, attrs))
}

Element.prototype.cnode = function(child) {
    this.children.push(child)
    child.parent = this
    return child
}

/** add text node and return element */
Element.prototype.t = function(text) {
    this.children.push(text)
    return this
}

/*** Manipulation ***/

/**
 * Either:
 *   el.remove(childEl)
 *   el.remove('author', 'urn:...')
 */
Element.prototype.remove = function(el, xmlns) {
    var filter
    if (typeof el === 'string') {
        /* 1st parameter is tag name */
        filter = function(child) {
            return !(child.is &&
                 child.is(el, xmlns))
        }
    } else {
        /* 1st parameter is element */
        filter = function(child) {
            return child !== el
        }
    }

    this.children = this.children.filter(filter)

    return this
}

/**
 * To use in case you want the same XML data for separate uses.
 * Please refrain from this practise unless you know what you are
 * doing. Building XML with ltx is easy!
 */
Element.prototype.clone = function() {
    var clone = this._getElement(this.name, {})
    for (var k in this.attrs) {
        if (this.attrs.hasOwnProperty(k))
            clone.attrs[k] = this.attrs[k]
    }
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        clone.cnode(child.clone ? child.clone() : child)
    }
    return clone
}

Element.prototype.text = function(val) {
    if (val && this.children.length === 1) {
        this.children[0] = val
        return this
    }
    return this.getText()
}

Element.prototype.attr = function(attr, val) {
    if (((typeof val !== 'undefined') || (val === null))) {
        if (!this.attrs) {
            this.attrs = {}
        }
        this.attrs[attr] = val
        return this
    }
    return this.attrs[attr]
}

/*** Serialization ***/

Element.prototype.toString = function() {
    var s = ''
    this.write(function(c) {
        s += c
    })
    return s
}

Element.prototype.toJSON = function() {
    return {
        name: this.name,
        attrs: this.attrs,
        children: this.children.map(function(child) {
            return child && child.toJSON ? child.toJSON() : child;
        })
    }
}

Element.prototype._addChildren = function(writer) {
    writer('>')
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i]
        /* Skip null/undefined */
        if (child || (child === 0)) {
            if (child.write) {
                child.write(writer)
            } else if (typeof child === 'string') {
                writer(escapeXmlText(child))
            } else if (child.toString) {
                writer(escapeXmlText(child.toString(10)))
            }
        }
    }
    writer('</')
    writer(this.name)
    writer('>')
}

Element.prototype.write = function(writer) {
    writer('<')
    writer(this.name)
    for (var k in this.attrs) {
        var v = this.attrs[k]
        if (v || (v === '') || (v === 0)) {
            writer(' ')
            writer(k)
            writer('="')
            if (typeof v !== 'string') {
                v = v.toString(10)
            }
            writer(escapeXml(v))
            writer('"')
        }
    }
    if (this.children.length === 0) {
        writer('/>')
    } else {
        this._addChildren(writer)
    }
}

function escapeXml(s) {
    return s.
        replace(/\&/g, '&').
        replace(/</g, '<').
        replace(/>/g, '>').
        replace(/"/g, '"').
        replace(/"/g, ''')
}

function escapeXmlText(s) {
    return s.
        replace(/\&/g, '&').
        replace(/</g, '<').
        replace(/>/g, '>')
}

exports.Element = Element
exports.escapeXml = escapeXml
<|endoftext|>


<commit_before>export function simulate(expr, inputs, overallNumSamples) {
  const numSamples = overallNumSamples/window.workers.length
  return Promise.all(_.map(
    window.workers,
    (worker, index) => simulateOnWorker(worker, {expr, numSamples, inputs: sliceData(index, numSamples, inputs)})
  )).then(
    (results) => {
      let finalResult = {values: [], errors: []}
      for (let result of results) {
        if (result.values) {
          finalResult.values = finalResult.values.concat(result.values)
        }
        if (result.errors) {
          finalResult.errors = finalResult.errors.concat(result.errors)
        }
      }
      finalResult.errors = _.uniq(finalResult.errors)
      return finalResult
    }
  )
}

function sliceData(index, numSamples, inputs) {
  let slicedInputs = {}
  for (let key of Object.keys(inputs)) {
    slicedInputs[key] = inputs[key].slice(numSamples*index, numSamples*(index+1))
  }
  return slicedInputs
}

function simulateOnWorker(worker, data) {
  return new Promise(
    (resolve, reject) => {
      worker.push(data, ({data}) => {resolve(JSON.parse(data))})
    }
  )
}
<commit_msg>Mild cleanup. There is likely a more functional way to do the slicedInput construction, will investigate further.<commit_after>export function simulate(expr, inputs, overallNumSamples) {
  const numSamples = overallNumSamples/window.workers.length
  return Promise.all(_.map(
    window.workers,
    (worker, index) => simulateOnWorker(worker, buildData(index, expr, numSamples, inputs))
  )).then(
    (results) => {
      let finalResult = {values: [], errors: []}
      for (let result of results) {
        if (result.values) {
          finalResult.values = finalResult.values.concat(result.values)
        }
        if (result.errors) {
          finalResult.errors = finalResult.errors.concat(result.errors)
        }
      }
      finalResult.errors = _.uniq(finalResult.errors)
      return finalResult
    }
  )
}

function buildData(index, expr, numSamples, inputs) {
  let slicedInputs = {}
  for (let key of Object.keys(inputs)) {
    slicedInputs[key] = inputs[key].slice(numSamples*index, numSamples*(index+1))
  }
  return {expr, numSamples, inputs: slicedInputs}
}

function simulateOnWorker(worker, data) {
  return new Promise(
    (resolve, reject) => {
      worker.push(data, ({data}) => {resolve(JSON.parse(data))})
    }
  )
}
<|endoftext|>


<commit_before>var Hash = require('traverse/hash')
  , qs = require('querystring')
  , http = require('http')
  , util = require('util')
  , _package = require('embedly/package.json')

/**
 * Like qs.stringify, except that Array parameters are combined with
 * an unescaped comma instead of split up of many paramters.
 *
 * ex.
 *
 * > q({urls:
 *   ['http://youtube.com/watch/video1', 'http://youtube.com/watch/video2'],
 *   key: 'xxxx'})
 *
 * 'urls=http%3A%2F%2Fyoutube.com%2Fwatch%2Fvideo1,http%3A%2F%2Fyoutube.com%2Fwatch%2Fvideo2&key=xxxx'
 */
var q = function(params) {
  return Hash(params).map(function(v,k) {
    if (typeof(v) != 'string' && typeof(v) != 'number') {
      return k+"="+v.map(function(p){return qs.escape(String(p))}).join(',')
    } else {
      return k+"="+qs.escape(v)
    }
  }).values.join('&');
}

/* Flexibly parse host strings.
 *
 * Returns a hash of
 * { protocol:
 * , host:
 * , port:
 * , url:
 * }
 */
var parse_host = function(host) {
  var port = 80
    , protocol = 'http'
    , match = host.match(/^(https?:\/\/)?([^\/]+)(:\d+)?\/?$/)
    ;

  if (!match) {
    throw new Error('invalid host '+host)
  }

  var hostname = match[2]

  if (match[1] == 'https://') {
    protocol = 'https'
  }

  if (match[3]) {
    port = Number(match[3])
  } else if (match[1] == 'https://') {
    port = 443
  }

  var portpart = ""
  if (match[3]) {
    portpart = ":"+match[3]
  }

  url = (protocol+"://"+hostname+portpart+"/")
  return { 'url': url, 'protocol': protocol, 'hostname': hostname, 'port': port }
}

var simple_request = function(host, method, path, headers, callback) {
  var client = http.createClient(host.port, host.hostname, host.protocol == 'https')
    , request = client.request(method, path, headers)

  log.debug("Calling "+host.protocol+"://"+host.hostname+":"+host.port+path)

  request.end()
  request.on('response', function(response) {
    var data=''
    response.on('data', function (chunk) {
      data += chunk
    })
    response.on('end', function () {
      try {
        callback(null, data)
      } catch(e) {
        callback(e, null)
      }
    })
  })
}

try {
  var Syslog = require('node-syslog').Syslog
  var log = new function() {
    Syslog.init("embedly", Syslog.LOG_PID | Syslog.LOG_ODELAY, Syslog.LOG_INFO)

    this.log = function(level, msg) {
      if (typeof(msg) != 'string') {
        msg = util.inspect(msg)
      }
      msg = msg.replace(/\n/g, '\\n')
      Syslog.log(level, msg)
    }
    this.info = function(msg) {
      this.log(Syslog.LOG_INFO, msg)
    }
    this.debug = function(msg) {
      this.log(Syslog.LOG_DEBUG, msg)
    }
    this.warn = function(msg) {
      this.log(Syslog.LOG_WARN, msg)
    }
    this.error = function(msg) {
      this.log(Syslog.LOG_ERROR, msg)
    }
  }
} catch(e) {
  console.log('WARN: `npm install node-syslog@0.6.0` for logging');
  var log = new function() {
    this.log = function() {}
    this.info = function() {}
    this.debug = function() {}
    this.warn = function() {}
    this.error = function() {}
  }
}

/*
 * TODO: doc
 */
this.Api = function(opts) {
  if (!opts) {
    opts = {}
  }
  if (!opts['user_agent']) {
    log.warn("You didn't pass a user_agent option to embedly.api's constructor, please pass one in the future");
  }
  this.key = opts['key']
  this.host = opts['host'] || (this.key ? 'pro.embed.ly' : 'api.embed.ly')
  this.user_agent = opts['user_agent'] || 'Mozilla/5.0 (compatible; embedly-node/'+_package.version
  this.api_version = {
      'objectify': 2
    , 'oembed': 1
    , 'preview': 1
  }
  if (!this.key) {
    this.api_version['objectify'] = 1
  }
  var self = this

  Hash(this.api_version).forEach(function(_, action) {
    self[action] = function(args) {
      this.apicall(
        {action: action, version: this.api_version[action]}
      , args
      )
    }
  })
}

this.Api.prototype = new(function() {
  /* TODO: doc */
  this.apicall = function(opts, args) {
    var params = args['params']
    if (!params.urls) {
      params.urls = []
    }
    if (params.url) {
      params.urls.push(params.url)
      delete params['url']
    }

    var self = this;

    var services_regex = function(callback){self.services_regex(callback)}

    if (this.key) {
      params['key'] = this.key
      services_regex = function(callback){callback()}
    }

    services_regex(function(e, sregex) {
      if (e) {
        args.complete(e, [])
      } else {
        var rejects = []
        if (sregex) {
          params['urls'] = params['urls'].filter(function(url, i) {
            if (!url.match(sregex)) {
              rejects.push([i, {
                  type: 'error'
                , error_code: 401
                , error_message: 'This service requires an Embedly Pro account'
                }
              ])
            } else {
              return true
            }
          })
        }

        if (params['urls'].length > 0) {
          var path = "/"+opts['version']+"/"+opts['action']
            , host = parse_host(self.host)

          simple_request(host, 'GET', path+"?"+q(params)
            , {'User-Agent': self.user_agent, 'Host': host.hostname}
            , function(e, data) {
              if (e) {
                args.complete(e, data)
              } else {
                try {
                  var objs = JSON.parse(data)
                  rejects.forEach(function(reject){
                    objs.splice(reject[0], 0, reject[1])
                  })
                  args.complete(null, objs)
                } catch(e) {
                  args.complete(e, [])
                }
              }
            }
          )
        } else {
          log.debug("Returning all rejects")
          args.complete(null, rejects.map(function(i){return i[1]}))
        }
      }
    })
  }

  this.services = function(oncomplete) {
    if (this.key) {
      log.warn('services isn\'t available using the pro endpoints')
    }
    if (typeof(this._services) == 'undefined') {
      var host = parse_host(this.host)
        , self = this

      simple_request(host, 'GET', '/1/services/javascript'
        , {'User-Agent': self.user_agent, 'Host': host.hostname}
        , function(e, data) {
          if (e) {
            oncomplete(e, data)
          } else {
            try {
              self._services = JSON.parse(data)
              oncomplete(null, self._services)
            } catch(e) {
              oncomplete(e, [])
            }
          }
        }
      )
    } else {
      oncomplete(null, this._services)
    }
  }

  this.services_regex = function(oncomplete) {
    this.services(function(e, services_obj) {
      if (e) {
        oncomplete(e, services_obj)
      } else {
        try {
          var r = new RegExp(services_obj.map(function(p){
            return p.regex.join("|")
          }).join("|"))
          oncomplete(null, r);
        } catch(e) {
          oncomplete(e, null);
        }
      }
    })
  }
})

module.exports = {
   version: _package.version
 , api: this.Api
 , log: log
}
<commit_msg>handle util import<commit_after>var Hash = require('traverse/hash')
  , qs = require('querystring')
  , http = require('http')
  , _package = require('embedly/package.json')

// Older versions of node call it utils, not util
try {
  var util = require('util')
} catch(e) {
  var util = require('utils')
}

/**
 * Like qs.stringify, except that Array parameters are combined with
 * an unescaped comma instead of split up of many paramters.
 *
 * ex.
 *
 * > q({urls:
 *   ['http://youtube.com/watch/video1', 'http://youtube.com/watch/video2'],
 *   key: 'xxxx'})
 *
 * 'urls=http%3A%2F%2Fyoutube.com%2Fwatch%2Fvideo1,http%3A%2F%2Fyoutube.com%2Fwatch%2Fvideo2&key=xxxx'
 */
var q = function(params) {
  return Hash(params).map(function(v,k) {
    if (typeof(v) != 'string' && typeof(v) != 'number') {
      return k+"="+v.map(function(p){return qs.escape(String(p))}).join(',')
    } else {
      return k+"="+qs.escape(v)
    }
  }).values.join('&');
}

/* Flexibly parse host strings.
 *
 * Returns a hash of
 * { protocol:
 * , host:
 * , port:
 * , url:
 * }
 */
var parse_host = function(host) {
  var port = 80
    , protocol = 'http'
    , match = host.match(/^(https?:\/\/)?([^\/]+)(:\d+)?\/?$/)
    ;

  if (!match) {
    throw new Error('invalid host '+host)
  }

  var hostname = match[2]

  if (match[1] == 'https://') {
    protocol = 'https'
  }

  if (match[3]) {
    port = Number(match[3])
  } else if (match[1] == 'https://') {
    port = 443
  }

  var portpart = ""
  if (match[3]) {
    portpart = ":"+match[3]
  }

  url = (protocol+"://"+hostname+portpart+"/")
  return { 'url': url, 'protocol': protocol, 'hostname': hostname, 'port': port }
}

var simple_request = function(host, method, path, headers, callback) {
  var client = http.createClient(host.port, host.hostname, host.protocol == 'https')
    , request = client.request(method, path, headers)

  log.debug("Calling "+host.protocol+"://"+host.hostname+":"+host.port+path)

  request.end()
  request.on('response', function(response) {
    var data=''
    response.on('data', function (chunk) {
      data += chunk
    })
    response.on('end', function () {
      try {
        callback(null, data)
      } catch(e) {
        callback(e, null)
      }
    })
  })
}

try {
  var Syslog = require('node-syslog').Syslog
  var log = new function() {
    Syslog.init("embedly", Syslog.LOG_PID | Syslog.LOG_ODELAY, Syslog.LOG_INFO)

    this.log = function(level, msg) {
      if (typeof(msg) != 'string') {
        msg = util.inspect(msg)
      }
      msg = msg.replace(/\n/g, '\\n')
      Syslog.log(level, msg)
    }
    this.info = function(msg) {
      this.log(Syslog.LOG_INFO, msg)
    }
    this.debug = function(msg) {
      this.log(Syslog.LOG_DEBUG, msg)
    }
    this.warn = function(msg) {
      this.log(Syslog.LOG_WARN, msg)
    }
    this.error = function(msg) {
      this.log(Syslog.LOG_ERROR, msg)
    }
  }
} catch(e) {
  console.log('WARN: `npm install node-syslog@0.6.0` for logging');
  var log = new function() {
    this.log = function() {}
    this.info = function() {}
    this.debug = function() {}
    this.warn = function() {}
    this.error = function() {}
  }
}

/*
 * TODO: doc
 */
this.Api = function(opts) {
  if (!opts) {
    opts = {}
  }
  if (!opts['user_agent']) {
    log.warn("You didn't pass a user_agent option to embedly.api's constructor, please pass one in the future");
  }
  this.key = opts['key']
  this.host = opts['host'] || (this.key ? 'pro.embed.ly' : 'api.embed.ly')
  this.user_agent = opts['user_agent'] || 'Mozilla/5.0 (compatible; embedly-node/'+_package.version
  this.api_version = {
      'objectify': 2
    , 'oembed': 1
    , 'preview': 1
  }
  if (!this.key) {
    this.api_version['objectify'] = 1
  }
  var self = this

  Hash(this.api_version).forEach(function(_, action) {
    self[action] = function(args) {
      this.apicall(
        {action: action, version: this.api_version[action]}
      , args
      )
    }
  })
}

this.Api.prototype = new(function() {
  /* TODO: doc */
  this.apicall = function(opts, args) {
    var params = args['params']
    if (!params.urls) {
      params.urls = []
    }
    if (params.url) {
      params.urls.push(params.url)
      delete params['url']
    }

    var self = this;

    var services_regex = function(callback){self.services_regex(callback)}

    if (this.key) {
      params['key'] = this.key
      services_regex = function(callback){callback()}
    }

    services_regex(function(e, sregex) {
      if (e) {
        args.complete(e, [])
      } else {
        var rejects = []
        if (sregex) {
          params['urls'] = params['urls'].filter(function(url, i) {
            if (!url.match(sregex)) {
              rejects.push([i, {
                  type: 'error'
                , error_code: 401
                , error_message: 'This service requires an Embedly Pro account'
                }
              ])
            } else {
              return true
            }
          })
        }

        if (params['urls'].length > 0) {
          var path = "/"+opts['version']+"/"+opts['action']
            , host = parse_host(self.host)

          simple_request(host, 'GET', path+"?"+q(params)
            , {'User-Agent': self.user_agent, 'Host': host.hostname}
            , function(e, data) {
              if (e) {
                args.complete(e, data)
              } else {
                try {
                  var objs = JSON.parse(data)
                  rejects.forEach(function(reject){
                    objs.splice(reject[0], 0, reject[1])
                  })
                  args.complete(null, objs)
                } catch(e) {
                  args.complete(e, [])
                }
              }
            }
          )
        } else {
          log.debug("Returning all rejects")
          args.complete(null, rejects.map(function(i){return i[1]}))
        }
      }
    })
  }

  this.services = function(oncomplete) {
    if (this.key) {
      log.warn('services isn\'t available using the pro endpoints')
    }
    if (typeof(this._services) == 'undefined') {
      var host = parse_host(this.host)
        , self = this

      simple_request(host, 'GET', '/1/services/javascript'
        , {'User-Agent': self.user_agent, 'Host': host.hostname}
        , function(e, data) {
          if (e) {
            oncomplete(e, data)
          } else {
            try {
              self._services = JSON.parse(data)
              oncomplete(null, self._services)
            } catch(e) {
              oncomplete(e, [])
            }
          }
        }
      )
    } else {
      oncomplete(null, this._services)
    }
  }

  this.services_regex = function(oncomplete) {
    this.services(function(e, services_obj) {
      if (e) {
        oncomplete(e, services_obj)
      } else {
        try {
          var r = new RegExp(services_obj.map(function(p){
            return p.regex.join("|")
          }).join("|"))
          oncomplete(null, r);
        } catch(e) {
          oncomplete(e, null);
        }
      }
    })
  }
})

module.exports = {
   version: _package.version
 , api: this.Api
 , log: log
}
<|endoftext|>


<commit_before>/**
 * embedly.js
 * ----------
 *
 * Listed below is the embedly api client implementation.  Please refer to
 * README.rst for example code using this library.
 */

var VERSION="0.5.1"

/**
 * Cast an array-like object to an array.
 */
function cast_array(a) {
  return Array.prototype.slice.call(a)
}

/**
 * Try to load each module in order, returning when successful.
 * This is used to require modules that changed names between node
 * versions.
 *
 * ex. require_either('util', 'utils')
 */
function require_either() {
  // turn arguments into an array
  var args = cast_array(arguments)
  var libname = args.shift()

  while (libname) {
    try {
      var lib = require(libname)
      return lib
    } catch(e) {
      libname = args.shift()
    }
  }
  throw new Error('Cannot find modules '+cast_array(arguments))
}

var Hash = require_either('hashish', 'traverse/hash')
  , qs = require('querystring')
  , http = require('http')
  , EventEmitter = require('events').EventEmitter
  , util = require_either('util', 'utils')
  , querystring = require('querystring')

/* Flexibly parse host strings.
 *
 * Returns a hash of
 * { protocol:
 * , host:
 * , port:
 * , url:
 * }
 */
var parse_host = function(host) {
  var port = 80
    , protocol = 'http'
    , match = host.match(/^(https?:\/\/)?([^\/:]+)(:\d+)?\/?$/)

  if (!match) {
    throw new Error('invalid host '+host)
  }

  var hostname = match[2]

  if (hostname.indexOf(':') != -1){
    port = Number(hostname.substr(hostname.indexOf(':')+1))
    hostname = hostname.substr(0, hostname.indexOf(':'))
  }

  if (match[3]) {
    port = Number(match[3].substr(1))
  } else if (match[1] == 'https://') {
    port = 443
  }

  url = (protocol+"://"+hostname+"/")

  return { url: url, protocol: protocol, hostname: hostname, port: port }
}

/**
 * A wrapper around http request that collects data and calls back
 * when complete.
 *
 * ex::
 *   var host = parse_host('api.embed.ly')
 *     , r = simple_request(host, 'GET', '/1/services/javascript',
 *           {Host: host.hostname, 'User-Agent': 'Mozilla/5.0 (compatible; myapp/0.1; u@my.com)'})
 *   r.on('complete', function(data) {
 *     services = JSON.parse(data)
 *     console.log(util.inspect(services)
 *   }).on('error', function(error) {
 *     console.log('an error occurred')
 *     console.log(error)
 *   }).start()
 */
var simple_request = function(host, method, path, headers, timeout) {
  var client = http.createClient(host.port, host.hostname, host.protocol == 'https')
    , request = client.request(method, path, headers)
    , emitter = new EventEmitter()
    , timeout = timeout || 200000

  log.debug("Calling "+host.protocol+"://"+host.hostname+":"+host.port+path)
  headers['X-Embedly-Client'] = 'embedly-node '+VERSION

  emitter.start = function() {
    request.end()
    /*
    var timer = setTimeout(function req_timeout(){
      emitter.emit('timeout', 'blah', 'blahblah')
      request.abort()
    }, timeout)
    */
    request.on('response', function (response) {
      request.connection.setTimeout(1)
      var data=[]
      response.on('data', function (chunk) {
        data.push(chunk)
      })
      response.on('end', function () {
        //clearTimeout(timer)
        emitter.emit('complete', response, data.join(''))
      })
      response.on('error', function (e) {
        emitter.emit('error', e)
      })
    })
  }

  return emitter
}

/**
 * A simple logging wrapper. We try libraries in the following order:
 *   * log
 *   * node-syslog
 *
 * If none are present we create a noop logger.
 *
 * ex::
 *   var embedly = require('embedly')
 *   embedly.Api.log.debug('hi log!')
 */
try {
  var Log = require('log')
    , level = process.env.EMBEDLY_LOG_LEVEL
    , file = process.env.EMBEDLY_LOG_FILE

  if (!level) {
    level = 1
  }

  if (file) {
    var fs = require('fs')
      , log = new Log(level, fs.createWriteStream(file, {flags: 'a'}))
  } else {
    var log = new Log(level)
  }

  log.warn = log.warning

} catch(e) {
  try {
    var Syslog = require('node-syslog').Syslog
    var log = new function() {
      Syslog.init("embedly", Syslog.LOG_PID | Syslog.LOG_ODELAY, Syslog.LOG_INFO)

      this.log = function(level, msg) {
        msg = JSON.stringify(util.inspect(msg))
        Syslog.log(level, msg)
      }
      this.info = function(msg) {
        this.log(Syslog.LOG_INFO, msg)
      }
      this.debug = function(msg) {
        this.log(Syslog.LOG_DEBUG, msg)
      }
      this.warn = function(msg) {
        this.log(Syslog.LOG_WARN, msg)
      }
      this.error = function(msg) {
        this.log(Syslog.LOG_ERROR, msg)
      }
    }
  } catch(e) {
    console.log('WARN: `npm install log` for logging.')
    var log = new function() {
      this.log = function() {}
      this.info = function() {}
      this.debug = function() {}
      this.warn = function() {}
      this.error = function() {}
    }
  }
}

/*
 * Class representing the Embedly Api.  Parameters are passed as an object.
 * The following parameters are accepted::
 *   { user_agent: [required]     // ex. 'Mozilla/5.0 (compatible; myapp/1.0; suppoer@myco.com)'
 *   , key: [optional]            // Your embedly api key.  If you use preview or objectify then
 *                                // you will need this.
 *   , host: [optional]           // default is api.embed.ly
 *   }
 */
function Api(opts) {
  if (!opts) {
    opts = {}
  }
  if (!opts['user_agent']) {
    log.warn("You didn't pass a user_agent option to embedly.api's constructor, please pass one in the future")
  }
  this.key = opts['key'] || process.env['EMBEDLY_KEY']
  this.host = opts['host'] || 'api.embed.ly'
  this.user_agent = opts['user_agent'] || 'Mozilla/5.0 (compatible; embedly-node/'+VERSION
  this.api_version = {
      'objectify': 2
    , 'oembed': 1
    , 'preview': 1
  }
  this.timeout = opts['timeout'] || 200

  var self = this

  /*
   * Dynamically adding methods for each of the api calls listed
   * in api_version. (oembed, preview, objectify)
   */
  Hash(this.api_version).forEach(function(_, action) {
    self[action] = function(args) {
      return this.apicall(
        {action: action, version: this.api_version[action]}
      , args
      )
    }
  })
}

Api.prototype = new(function() {
  /*
   * Generic api calling mechanism.  You shouldn't need to call
   * this unless you are doing something special.  Instead,
   * oembed, preview and objectify methods should be used.
   *
   * opts are turned into the first two parts of the called URL path. Accepts
   * the following parameters::
   *
   * opts: {
   *     version: [required] // The version of the api call.
   *   , action: [required] // The api action to be called.
   * }
   *
   * params holds urls and any extra url parameters
   *
   *   { urls: [required] // An array of urls to be resolved by embedly
   *   , ... [optional]   // Other url parameters.  Please see pro.embed.ly/docs
   *   }
   *
   * ex::
   *   api.apicall(
   *     'oembed', '1', {
   *         urls: ['http://someurl.com/foobar', 'http://other.com/barfiz']
   *       , maxwidth: 400
   *     }
   *   ).on('complete', function(objs) {
   *       console.log(util.inspect(objs))
   *   }).on('error', function(error, data) {
   *       console.log(error, data);
   *   }).start()
   */
  this.apicall = function(opts, orig_params) {
    var apicall = new EventEmitter()
      , self = this
      , params = Hash.clone(orig_params)

    apicall.start = function() {
      if (!params.urls) {
        params.urls = []
      }
      if (params.url) {
        params.urls.push(params.url)
        delete params['url']
      }

      var services_regex = function(){return self.services_regex()}

      if (self.key) {
        params['key'] = self.key
        // Create a noop since we don't care about services_regex with key
        services_regex = function(){
          var emitter = new EventEmitter()
          emitter.start = function() {emitter.emit('complete')}
          return emitter
        }
      }

      services_regex()
        .on('complete', function apicall_services_complete(sregex) {
          var rejects = []
          if (sregex) {
            params['urls'] = params['urls'].filter(function url_filter(url, i) {
              if (!url.match(sregex)) {
                rejects.push([i, {
                    type: 'error'
                  , error_code: 401
                  , error_message: 'This service requires an Embedly Pro account'
                  }
                ])
              } else {
                return true
              }
            })
          }
          if (rejects.length > 0) {
            log.warn("reject count: "+rejects.length)
          } else if (params['urls'].length == 0) {
            log.warn('no urls passed')
          }

          if (params['urls'].length > 0) {
            var path = "/"+opts['version']+"/"+opts['action']
              , host = parse_host(self.host)

            simple_request(host, 'GET', path+"?"+querystring.stringify(params)
                , {'User-Agent': self.user_agent, 'Host': host.hostname},
                self.timeout)
              .on('complete', function apicall_complete(response, data) {
                try {
                  if (response.statusCode == 200) {
                    var objs = JSON.parse(data)
                    rejects.forEach(function each_reject(reject){
                      objs.splice(reject[0], 0, reject[1])
                    })
                    apicall.emit('complete', objs)
                  } else {
                    apicall.emit('error', data)
                  }
                } catch(e) {
                  apicall.emit('error', e)
                }
              })
              .on('error', function apicall_error(e) {
                apicall.emit('error', e)
              })
              .on('timeout', function apicall_timeout() {
                apicall.emit('timeout')
              })
              .start()
          } else {
            log.warn("no urls fetched")
            apicall.emit('complete', rejects.map(function rejects_map(i){
              return i[1]
            }))
          }
        })
        .on('error', function apicall_services_error(e) {apicall.emit('error', e)})
        .start()
    }

    return apicall
  }

  /**
   * Retrieve the services object from the embedly
   * `services api <http://api.embed.ly/docs/service>`_
   *
   * ex::
   *   api.services().on('complete', function(obj) {
   *     console.log(util.inspect(obj)
   *   }).start()
   */
  this.services = function() {
    var services = new EventEmitter()
      , self = this

    services.start = function() {
      if (this.key) {
        log.warn('services isn\'t available using the pro endpoints')
      }
      if (typeof(this._services) == 'undefined') {
        var host = parse_host(self.host)

        simple_request(host, 'GET'
                      , '/1/services/javascript'
                      , {'User-Agent': self.user_agent, 'Host': host.hostname}
                      , self.timeout )
          .on('complete', function services_complete(response, data) {
            try {
              self._services = JSON.parse(data)
              services.emit('complete', self._services)
            } catch(e) {
              services.emit('error', e)
            }
          })
          .on('error', function services_error(e) {services.emit('error', e)})
          .on('timeout', function services_timeout() {services.emit('timeout')})
          .start()
      } else {
        services.emit('complete', this._services)
      }
    }

    return services
  }

  /**
   * This method isn't needed for Pro calls.  Pro accepts any URL.
   * This method is automatically called to filter urls in apicall
   * for non-Pro calls and shouldn't need to be called directly.
   *
   * Response with a regex to match accepted providers urls against.
   *
   * ex::
   *   urls = [...]  // some urls
   *   api.services_regex().on('complete', function(regex) {
   *     urls.forEach(function(url) {
   *       if (url.matches(regex)) {
   *         // do something
   *       } else {
   *         // do something else
   *       }
   *     })
   *   }).start()
   */
  this.services_regex = function() {
    var regex = new EventEmitter()
      , self = this

    regex.start = function() {
      self.services()
        .on('complete', function services_regex_complete(repl) {
          try {
            var r = new RegExp(repl.map(function(p){
              return p.regex.join("|")
            }).join("|"))
            regex.emit('complete', r)
          } catch(e) {
            regex.emit('error', e)
          }
        })
        .on('error', function services_regex_error(e) {
          regex.emit('error', e)
        })
        .on('timeout', function services_regex_timeout() {
          regex.emit('timeout')
        })
        .start()
    }

    return regex
  }
})

Api.log = log

module.exports =
{ version: VERSION
, Api: Api
, utils:
  { cast_array: cast_array
  , require_either: require_either
  , simple_request: simple_request
  , parse_host: parse_host
  }
}
<commit_msg>Don't use the depreacated createClient() API.<commit_after>/**
 * embedly.js
 * ----------
 *
 * Listed below is the embedly api client implementation.  Please refer to
 * README.rst for example code using this library.
 */

var VERSION="0.5.1"

/**
 * Cast an array-like object to an array.
 */
function cast_array(a) {
  return Array.prototype.slice.call(a)
}

/**
 * Try to load each module in order, returning when successful.
 * This is used to require modules that changed names between node
 * versions.
 *
 * ex. require_either('util', 'utils')
 */
function require_either() {
  // turn arguments into an array
  var args = cast_array(arguments)
  var libname = args.shift()

  while (libname) {
    try {
      var lib = require(libname)
      return lib
    } catch(e) {
      libname = args.shift()
    }
  }
  throw new Error('Cannot find modules '+cast_array(arguments))
}

var Hash = require_either('hashish', 'traverse/hash')
  , qs = require('querystring')
  , http = require('http')
  , EventEmitter = require('events').EventEmitter
  , util = require_either('util', 'utils')
  , querystring = require('querystring')

/* Flexibly parse host strings.
 *
 * Returns a hash of
 * { protocol:
 * , host:
 * , port:
 * , url:
 * }
 */
var parse_host = function(host) {
  var port = 80
    , protocol = 'http'
    , match = host.match(/^(https?:\/\/)?([^\/:]+)(:\d+)?\/?$/)

  if (!match) {
    throw new Error('invalid host '+host)
  }

  var hostname = match[2]

  if (hostname.indexOf(':') != -1){
    port = Number(hostname.substr(hostname.indexOf(':')+1))
    hostname = hostname.substr(0, hostname.indexOf(':'))
  }

  if (match[3]) {
    port = Number(match[3].substr(1))
  } else if (match[1] == 'https://') {
    port = 443
  }

  url = (protocol+"://"+hostname+"/")

  return { url: url, protocol: protocol, hostname: hostname, port: port }
}

/**
 * A wrapper around http request that collects data and calls back
 * when complete.
 *
 * ex::
 *   var host = parse_host('api.embed.ly')
 *     , r = simple_request(host, 'GET', '/1/services/javascript',
 *           {Host: host.hostname, 'User-Agent': 'Mozilla/5.0 (compatible; myapp/0.1; u@my.com)'})
 *   r.on('complete', function(data) {
 *     services = JSON.parse(data)
 *     console.log(util.inspect(services)
 *   }).on('error', function(error) {
 *     console.log('an error occurred')
 *     console.log(error)
 *   }).start()
 */
var simple_request = function(host, method, path, headers, timeout) {
  host.method = method
  host.path = path
  host.headers = headers
  host.protocol += ':'
  var type = host.protocol == 'https:' ? https : http
    , request = type.request(host)
    , emitter = new EventEmitter()
    , timeout = timeout || 200000

  log.debug("Calling "+host.protocol+"://"+host.hostname+":"+host.port+path)
  headers['X-Embedly-Client'] = 'embedly-node '+VERSION

  emitter.start = function() {
    request.end()
    /*
    var timer = setTimeout(function req_timeout(){
      emitter.emit('timeout', 'blah', 'blahblah')
      request.abort()
    }, timeout)
    */
    request.on('response', function (response) {
      request.connection.setTimeout(1)
      var data=[]
      response.on('data', function (chunk) {
        data.push(chunk)
      })
      response.on('end', function () {
        //clearTimeout(timer)
        emitter.emit('complete', response, data.join(''))
      })
      response.on('error', function (e) {
        emitter.emit('error', e)
      })
    })
  }

  return emitter
}

/**
 * A simple logging wrapper. We try libraries in the following order:
 *   * log
 *   * node-syslog
 *
 * If none are present we create a noop logger.
 *
 * ex::
 *   var embedly = require('embedly')
 *   embedly.Api.log.debug('hi log!')
 */
try {
  var Log = require('log')
    , level = process.env.EMBEDLY_LOG_LEVEL
    , file = process.env.EMBEDLY_LOG_FILE

  if (!level) {
    level = 1
  }

  if (file) {
    var fs = require('fs')
      , log = new Log(level, fs.createWriteStream(file, {flags: 'a'}))
  } else {
    var log = new Log(level)
  }

  log.warn = log.warning

} catch(e) {
  try {
    var Syslog = require('node-syslog').Syslog
    var log = new function() {
      Syslog.init("embedly", Syslog.LOG_PID | Syslog.LOG_ODELAY, Syslog.LOG_INFO)

      this.log = function(level, msg) {
        msg = JSON.stringify(util.inspect(msg))
        Syslog.log(level, msg)
      }
      this.info = function(msg) {
        this.log(Syslog.LOG_INFO, msg)
      }
      this.debug = function(msg) {
        this.log(Syslog.LOG_DEBUG, msg)
      }
      this.warn = function(msg) {
        this.log(Syslog.LOG_WARN, msg)
      }
      this.error = function(msg) {
        this.log(Syslog.LOG_ERROR, msg)
      }
    }
  } catch(e) {
    console.log('WARN: `npm install log` for logging.')
    var log = new function() {
      this.log = function() {}
      this.info = function() {}
      this.debug = function() {}
      this.warn = function() {}
      this.error = function() {}
    }
  }
}

/*
 * Class representing the Embedly Api.  Parameters are passed as an object.
 * The following parameters are accepted::
 *   { user_agent: [required]     // ex. 'Mozilla/5.0 (compatible; myapp/1.0; suppoer@myco.com)'
 *   , key: [optional]            // Your embedly api key.  If you use preview or objectify then
 *                                // you will need this.
 *   , host: [optional]           // default is api.embed.ly
 *   }
 */
function Api(opts) {
  if (!opts) {
    opts = {}
  }
  if (!opts['user_agent']) {
    log.warn("You didn't pass a user_agent option to embedly.api's constructor, please pass one in the future")
  }
  this.key = opts['key'] || process.env['EMBEDLY_KEY']
  this.host = opts['host'] || 'api.embed.ly'
  this.user_agent = opts['user_agent'] || 'Mozilla/5.0 (compatible; embedly-node/'+VERSION
  this.api_version = {
      'objectify': 2
    , 'oembed': 1
    , 'preview': 1
  }
  this.timeout = opts['timeout'] || 200

  var self = this

  /*
   * Dynamically adding methods for each of the api calls listed
   * in api_version. (oembed, preview, objectify)
   */
  Hash(this.api_version).forEach(function(_, action) {
    self[action] = function(args) {
      return this.apicall(
        {action: action, version: this.api_version[action]}
      , args
      )
    }
  })
}

Api.prototype = new(function() {
  /*
   * Generic api calling mechanism.  You shouldn't need to call
   * this unless you are doing something special.  Instead,
   * oembed, preview and objectify methods should be used.
   *
   * opts are turned into the first two parts of the called URL path. Accepts
   * the following parameters::
   *
   * opts: {
   *     version: [required] // The version of the api call.
   *   , action: [required] // The api action to be called.
   * }
   *
   * params holds urls and any extra url parameters
   *
   *   { urls: [required] // An array of urls to be resolved by embedly
   *   , ... [optional]   // Other url parameters.  Please see pro.embed.ly/docs
   *   }
   *
   * ex::
   *   api.apicall(
   *     'oembed', '1', {
   *         urls: ['http://someurl.com/foobar', 'http://other.com/barfiz']
   *       , maxwidth: 400
   *     }
   *   ).on('complete', function(objs) {
   *       console.log(util.inspect(objs))
   *   }).on('error', function(error, data) {
   *       console.log(error, data);
   *   }).start()
   */
  this.apicall = function(opts, orig_params) {
    var apicall = new EventEmitter()
      , self = this
      , params = Hash.clone(orig_params)

    apicall.start = function() {
      if (!params.urls) {
        params.urls = []
      }
      if (params.url) {
        params.urls.push(params.url)
        delete params['url']
      }

      var services_regex = function(){return self.services_regex()}

      if (self.key) {
        params['key'] = self.key
        // Create a noop since we don't care about services_regex with key
        services_regex = function(){
          var emitter = new EventEmitter()
          emitter.start = function() {emitter.emit('complete')}
          return emitter
        }
      }

      services_regex()
        .on('complete', function apicall_services_complete(sregex) {
          var rejects = []
          if (sregex) {
            params['urls'] = params['urls'].filter(function url_filter(url, i) {
              if (!url.match(sregex)) {
                rejects.push([i, {
                    type: 'error'
                  , error_code: 401
                  , error_message: 'This service requires an Embedly Pro account'
                  }
                ])
              } else {
                return true
              }
            })
          }
          if (rejects.length > 0) {
            log.warn("reject count: "+rejects.length)
          } else if (params['urls'].length == 0) {
            log.warn('no urls passed')
          }

          if (params['urls'].length > 0) {
            var path = "/"+opts['version']+"/"+opts['action']
              , host = parse_host(self.host)

            simple_request(host, 'GET', path+"?"+querystring.stringify(params)
                , {'User-Agent': self.user_agent, 'Host': host.hostname},
                self.timeout)
              .on('complete', function apicall_complete(response, data) {
                try {
                  if (response.statusCode == 200) {
                    var objs = JSON.parse(data)
                    rejects.forEach(function each_reject(reject){
                      objs.splice(reject[0], 0, reject[1])
                    })
                    apicall.emit('complete', objs)
                  } else {
                    apicall.emit('error', data)
                  }
                } catch(e) {
                  apicall.emit('error', e)
                }
              })
              .on('error', function apicall_error(e) {
                apicall.emit('error', e)
              })
              .on('timeout', function apicall_timeout() {
                apicall.emit('timeout')
              })
              .start()
          } else {
            log.warn("no urls fetched")
            apicall.emit('complete', rejects.map(function rejects_map(i){
              return i[1]
            }))
          }
        })
        .on('error', function apicall_services_error(e) {apicall.emit('error', e)})
        .start()
    }

    return apicall
  }

  /**
   * Retrieve the services object from the embedly
   * `services api <http://api.embed.ly/docs/service>`_
   *
   * ex::
   *   api.services().on('complete', function(obj) {
   *     console.log(util.inspect(obj)
   *   }).start()
   */
  this.services = function() {
    var services = new EventEmitter()
      , self = this

    services.start = function() {
      if (this.key) {
        log.warn('services isn\'t available using the pro endpoints')
      }
      if (typeof(this._services) == 'undefined') {
        var host = parse_host(self.host)

        simple_request(host, 'GET'
                      , '/1/services/javascript'
                      , {'User-Agent': self.user_agent, 'Host': host.hostname}
                      , self.timeout )
          .on('complete', function services_complete(response, data) {
            try {
              self._services = JSON.parse(data)
              services.emit('complete', self._services)
            } catch(e) {
              services.emit('error', e)
            }
          })
          .on('error', function services_error(e) {services.emit('error', e)})
          .on('timeout', function services_timeout() {services.emit('timeout')})
          .start()
      } else {
        services.emit('complete', this._services)
      }
    }

    return services
  }

  /**
   * This method isn't needed for Pro calls.  Pro accepts any URL.
   * This method is automatically called to filter urls in apicall
   * for non-Pro calls and shouldn't need to be called directly.
   *
   * Response with a regex to match accepted providers urls against.
   *
   * ex::
   *   urls = [...]  // some urls
   *   api.services_regex().on('complete', function(regex) {
   *     urls.forEach(function(url) {
   *       if (url.matches(regex)) {
   *         // do something
   *       } else {
   *         // do something else
   *       }
   *     })
   *   }).start()
   */
  this.services_regex = function() {
    var regex = new EventEmitter()
      , self = this

    regex.start = function() {
      self.services()
        .on('complete', function services_regex_complete(repl) {
          try {
            var r = new RegExp(repl.map(function(p){
              return p.regex.join("|")
            }).join("|"))
            regex.emit('complete', r)
          } catch(e) {
            regex.emit('error', e)
          }
        })
        .on('error', function services_regex_error(e) {
          regex.emit('error', e)
        })
        .on('timeout', function services_regex_timeout() {
          regex.emit('timeout')
        })
        .start()
    }

    return regex
  }
})

Api.log = log

module.exports =
{ version: VERSION
, Api: Api
, utils:
  { cast_array: cast_array
  , require_either: require_either
  , simple_request: simple_request
  , parse_host: parse_host
  }
}
<|endoftext|>


<commit_before>/**
* Enhance-css - https://github.com/GoalSmashers/enhance-css
* Released under the terms of MIT license
*
* Copyright (C) 2011-2013 GoalSmashers.com
*/

var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
var crypto = require('crypto');
var querystring = require('querystring');
var existsSync = fs.existsSync || path.existsSync;

var isWindows = process.platform == 'win32';

var EnhanceCSS = function(options) {
  this.options = options;
  this.options.stamp = ('stamp' in options) ? options.stamp : true;
};

EnhanceCSS.prototype = {
  urlPattern: /url\(([^\)]+)\)/g,
  process: function(css, callback) {
    var self = this;
    var missing = {};
    var data = { original: css };
    var embedUrls = {};
    var allUrls = [];

    // Only to find duplicates
    (css.match(this.urlPattern) || []).forEach(function(url) {
      var pathInfo = self.parseImageUrl(url.substring(4, url.length - 1));

      if (pathInfo.query.embed !== undefined) {
        if (embedUrls[pathInfo.relative])
          embedUrls[pathInfo.relative]++;
        else
          embedUrls[pathInfo.relative] = 1;
      }

      allUrls.push(pathInfo);
    });

    // Get embedded version
    data.embedded = {};
    data.embedded.plain = css.replace(this.urlPattern, function(match, url) {
      var pathInfo = self.parseImageUrl(url);

      // Break early if file does not exist
      if (!pathInfo.exists) {
        missing[pathInfo.relative] = 1;
        return match;
      }

      // Break unless ?embed param or there's more than one such image
      if (pathInfo.query.embed === undefined || embedUrls[pathInfo.relative] > 1) {
        if (self.options.stamp) {
          self._addFileStamp(pathInfo);
        }
        return ['url(', (self.nextAssetHost() || ''), pathInfo.relative, ')'].join('');
      }

      var type = path.extname(pathInfo.relative).substring(1);
      if (type == 'jpg')
        type = 'jpeg';
      if (type == 'svg')
        type = 'svg+xml';

      // Break unless unsupported type
      if (!/(jpeg|gif|png|svg\+xml)/.test(type))
        return match;

      var base64 = fs.readFileSync(pathInfo.absolute).toString('base64');

      return 'url(data:image/' + type + ';base64,' + base64 + ')';
    });

    // Get not embedded version (aka <= IE7)
    if (this.options.noEmbedVersion) {
      data.notEmbedded = {};
      data.notEmbedded.plain = css.replace(this.urlPattern, function(match, url) {
        var pathInfo = self.parseImageUrl(url);

        // Break early if file does not exist
        if (!pathInfo.exists)
          return match;

        if (self.options.stamp)
          self._addFileStamp(pathInfo);

        return ['url(', (self.nextAssetHost() || ''), pathInfo.relative, ')'].join('');
      });
    }

    if (this.options.cryptedStamp) {
      allUrls.forEach(function(pathInfo) {
        self._addFileStamp(pathInfo);
      });
    }

    // Update missing & duplicates lists
    data.missing = Object.keys(missing);
    data.duplicates = [];
    for (var key in embedUrls) {
      if (hasOwnProperty.call(embedUrls, key)) {
        if (embedUrls[key] > 1)
          data.duplicates.push(key);
      }
    }

    // Create gzipped version too if requested
    if (this.options.pregzip) {
      var count = this.options.noEmbedVersion ? 2 : 1;
      var compress = function(type) {
        zlib.gzip(data[type].plain, function(error, result) {
          data[type].compressed = result;
          if (--count === 0)
            callback(null, data);
        });
      };

      compress('embedded');
      if (this.options.noEmbedVersion)
        compress('notEmbedded');

      return;
    }

    if (callback)
      callback(null, data);
    else
      return data;
  },

  parseImageUrl: function(url) {
    var tokens = url.replace(/['"]/g, '').split('?');
    var query = tokens[1] ? querystring.parse(tokens[1]) : {};
    var imagePath = path.normalize(tokens[0]);
    var absolutePath = path.join(this.options.rootPath, imagePath);

    if (isWindows && imagePath.indexOf('data:image') < 0)
      imagePath = imagePath.replace(/\\/g, '/');

    return {
      relative: imagePath,
      absolute: absolutePath,
      query: query,
      exists: existsSync(absolutePath)
    };
  },

  nextAssetHost: function() {
    var hosts = this.options.assetHosts;
    if (!hosts)
      return null;
    if (hosts.indexOf('[') == -1)
      return this._fixAssetHost(hosts);

    if (!this.hostsCycle) {
      this.hostsCycle = {
        next: function() {
          if (!this.cycleList) {
            var cycleList = [];
            var start = hosts.indexOf('[');
            var end = hosts.indexOf(']');
            var pattern = hosts.substring(start + 1, end);

            pattern.split(',').forEach(function(version) {
              cycleList.push(hosts.replace(/\[([^\]])+\]/, version));
            });

            this.cycleList = cycleList;
            this.index = 0;
          }

          if (this.index == this.cycleList.length)
            this.index = 0;
          return this.cycleList[this.index++];
        }
      };
    }

    return this._fixAssetHost(this.hostsCycle.next());
  },

  _fixAssetHost: function(host) {
    if (/^http:\/\//.test(host) ||
        /^https:\/\//.test(host) ||
        /^\/\//.test(host)) {
      return host;
    }
    return '//' + host;
  },

  _addFileStamp: function(pathInfo) {
    if (!existsSync(pathInfo.absolute))
      return;

    if (this.options.cryptedStamp && (pathInfo.query.embed === undefined || this.options.noEmbedVersion)) {
      var source = fs.readFileSync(pathInfo.absolute);
      var encrypted = crypto.createHash('md5');
      var toStampedPath = function(path) {
        var extensionDotIndex = path.lastIndexOf('.');
        return path.substring(0, extensionDotIndex) + '-' + stamp + '.' + path.substring(extensionDotIndex + 1);
      };

      encrypted.update(source.toString('utf8'));
      var stamp = encrypted.digest('hex');
      var targetPath = toStampedPath(pathInfo.absolute);

      if (!existsSync(targetPath))
        fs.writeFileSync(targetPath, source);

      pathInfo.relative = toStampedPath(pathInfo.relative);
    } else {
      pathInfo.relative += '?' + Date.parse(fs.statSync(pathInfo.absolute).mtime) / 1000;
    }
  }
};

module.exports = EnhanceCSS;
<commit_msg>Changes enhance-css object structure to match clean-css ones.<commit_after>/**
* Enhance-css - https://github.com/GoalSmashers/enhance-css
* Released under the terms of MIT license
*
* Copyright (C) 2011-2013 GoalSmashers.com
*/

var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
var crypto = require('crypto');
var querystring = require('querystring');
var existsSync = fs.existsSync || path.existsSync;

var isWindows = process.platform == 'win32';

module.exports = function EnhanceCSS(options) {
  options.stamp = ('stamp' in options) ? options.stamp : true;

  var urlPattern = /url\(([^\)]+)\)/g;
  var hostsCycle = null;

  var process = function(css, callback) {
    var missing = {};
    var data = { original: css };
    var embedUrls = {};
    var allUrls = [];

    // Only to find duplicates
    (css.match(urlPattern) || []).forEach(function(url) {
      var pathInfo = parseImageUrl(url.substring(4, url.length - 1));

      if (pathInfo.query.embed !== undefined) {
        if (embedUrls[pathInfo.relative])
          embedUrls[pathInfo.relative]++;
        else
          embedUrls[pathInfo.relative] = 1;
      }

      allUrls.push(pathInfo);
    });

    // Get embedded version
    data.embedded = {};
    data.embedded.plain = css.replace(urlPattern, function(match, url) {
      var pathInfo = parseImageUrl(url);

      // Break early if file does not exist
      if (!pathInfo.exists) {
        missing[pathInfo.relative] = 1;
        return match;
      }

      // Break unless ?embed param or there's more than one such image
      if (pathInfo.query.embed === undefined || embedUrls[pathInfo.relative] > 1) {
        if (options.stamp) {
          addFileStamp(pathInfo);
        }
        return ['url(', (nextAssetHost() || ''), pathInfo.relative, ')'].join('');
      }

      var type = path.extname(pathInfo.relative).substring(1);
      if (type == 'jpg')
        type = 'jpeg';
      if (type == 'svg')
        type = 'svg+xml';

      // Break unless unsupported type
      if (!/(jpeg|gif|png|svg\+xml)/.test(type))
        return match;

      var base64 = fs.readFileSync(pathInfo.absolute).toString('base64');

      return 'url(data:image/' + type + ';base64,' + base64 + ')';
    });

    // Get not embedded version (aka <= IE7)
    if (options.noEmbedVersion) {
      data.notEmbedded = {};
      data.notEmbedded.plain = css.replace(urlPattern, function(match, url) {
        var pathInfo = parseImageUrl(url);

        // Break early if file does not exist
        if (!pathInfo.exists)
          return match;

        if (options.stamp)
          addFileStamp(pathInfo);

        return ['url(', (nextAssetHost() || ''), pathInfo.relative, ')'].join('');
      });
    }

    if (options.cryptedStamp)
      allUrls.forEach(addFileStamp);

    // Update missing & duplicates lists
    data.missing = Object.keys(missing);
    data.duplicates = [];
    for (var key in embedUrls) {
      if (hasOwnProperty.call(embedUrls, key)) {
        if (embedUrls[key] > 1)
          data.duplicates.push(key);
      }
    }

    // Create gzipped version too if requested
    if (options.pregzip) {
      var count = options.noEmbedVersion ? 2 : 1;
      var compress = function(type) {
        zlib.gzip(data[type].plain, function(error, result) {
          data[type].compressed = result;
          if (--count === 0)
            callback(null, data);
        });
      };

      compress('embedded');
      if (options.noEmbedVersion)
        compress('notEmbedded');

      return;
    }

    if (callback)
      callback(null, data);
    else
      return data;
  };

  var parseImageUrl = function(url) {
    var tokens = url.replace(/['"]/g, '').split('?');
    var query = tokens[1] ? querystring.parse(tokens[1]) : {};
    var imagePath = path.normalize(tokens[0]);
    var absolutePath = path.join(options.rootPath, imagePath);

    if (isWindows && imagePath.indexOf('data:image') < 0)
      imagePath = imagePath.replace(/\\/g, '/');

    return {
      relative: imagePath,
      absolute: absolutePath,
      query: query,
      exists: existsSync(absolutePath)
    };
  };

  var nextAssetHost = function() {
    var hosts = options.assetHosts;
    if (!hosts)
      return null;
    if (hosts.indexOf('[') == -1)
      return fixAssetHost(hosts);

    if (!hostsCycle) {
      hostsCycle = {
        next: function() {
          if (!this.cycleList) {
            var cycleList = [];
            var start = hosts.indexOf('[');
            var end = hosts.indexOf(']');
            var pattern = hosts.substring(start + 1, end);

            pattern.split(',').forEach(function(version) {
              cycleList.push(hosts.replace(/\[([^\]])+\]/, version));
            });

            this.cycleList = cycleList;
            this.index = 0;
          }

          if (this.index == this.cycleList.length)
            this.index = 0;
          return this.cycleList[this.index++];
        }
      };
    }

    return fixAssetHost(hostsCycle.next());
  };

  var fixAssetHost = function(host) {
    if (/^http:\/\//.test(host) || /^https:\/\//.test(host) || /^\/\//.test(host))
      return host;

    return '//' + host;
  };

  var addFileStamp = function(pathInfo) {
    if (!existsSync(pathInfo.absolute))
      return;

    if (options.cryptedStamp && (pathInfo.query.embed === undefined || options.noEmbedVersion)) {
      var source = fs.readFileSync(pathInfo.absolute);
      var encrypted = crypto.createHash('md5');
      var toStampedPath = function(path) {
        var extensionDotIndex = path.lastIndexOf('.');
        return path.substring(0, extensionDotIndex) + '-' + stamp + '.' + path.substring(extensionDotIndex + 1);
      };

      encrypted.update(source.toString('utf8'));
      var stamp = encrypted.digest('hex');
      var targetPath = toStampedPath(pathInfo.absolute);

      if (!existsSync(targetPath))
        fs.writeFileSync(targetPath, source);

      pathInfo.relative = toStampedPath(pathInfo.relative);
    } else {
      pathInfo.relative += '?' + Date.parse(fs.statSync(pathInfo.absolute).mtime) / 1000;
    }
  };

  return {
    nextAssetHost: nextAssetHost,
    parseImageUrl: parseImageUrl,
    process: process,
  };
};
<|endoftext|>