<commit_before>/*!
 * Connect - logger
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var bytes = require('bytes');

/*!
 * Log buffer.
 */

var buf = [];

/*!
 * Default log buffer duration.
 */

var defaultBufferDuration = 1000;

/**
 * Logger:
 *
 * Log requests with the given `options` or a `format` string.
 *
 * Options:
 *
 *   - `format`  Format string, see below for tokens
 *   - `stream`  Output stream, defaults to _stdout_
 *   - `buffer`  Buffer duration, defaults to 1000ms when _true_
 *   - `immediate`  Write log line on request instead of response (for response times)
 *   - `skip`    Function to determine if logging is skipped, called as
 *               `skip(req, res)`, defaults to always false.
 *
 * Tokens:
 *
 *   - `:req[header]` ex: `:req[Accept]`
 *   - `:res[header]` ex: `:res[Content-Length]`
 *   - `:http-version`
 *   - `:response-time`
 *   - `:remote-addr`
 *   - `:date`
 *   - `:method`
 *   - `:url`
 *   - `:referrer`
 *   - `:user-agent`
 *   - `:status`
 *
 * Formats:
 *
 *   Pre-defined formats that ship with connect:
 *
 *    - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
 *    - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
 *    - `tiny`  ':method :url :status :res[content-length] - :response-time ms'
 *    - `dev` concise output colored by response status for development use
 *
 * Examples:
 *
 *      connect.logger() // default
 *      connect.logger('short')
 *      connect.logger('tiny')
 *      connect.logger({ immediate: true, format: 'dev' })
 *      connect.logger(':method :url - :referrer')
 *      connect.logger(':req[content-type] -> :res[content-type]')
 *      connect.logger(function(tokens, req, res){ return 'some format string' })
 *      connect.logger({ format: 'dev', skip: function(req, res){ return res.statusCode === 304; }})
 *
 * Defining Tokens:
 *
 *   To define a token, simply invoke `connect.logger.token()` with the
 *   name and a callback function. The value returned is then available
 *   as ":type" in this case.
 *
 *      connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
 *
 * Defining Formats:
 *
 *   All default formats are defined this way, however it's public API as well:
 *
 *       connect.logger.format('name', 'string or function')
 *
 * @param {String|Function|Object} format or options
 * @return {Function}
 * @api public
 */

exports = module.exports = function logger(options) {
  if ('object' == typeof options) {
    options = options || {};
  } else if (options) {
    options = { format: options };
  } else {
    options = {};
  }

  // output on request instead of response
  var immediate = options.immediate;

  // check if log entry should be skipped
  var skip = options.skip || function () { return false; };

  // format name
  var fmt = exports[options.format] || options.format || exports.default;

  // compile format
  if ('function' != typeof fmt) fmt = compile(fmt);

  // options
  var stream = options.stream || process.stdout
    , buffer = options.buffer;

  // buffering support
  if (buffer) {
    var realStream = stream
      , interval = 'number' == typeof buffer
        ? buffer
        : defaultBufferDuration;

    // flush interval
    setInterval(function(){
      if (buf.length) {
        realStream.write(buf.join(''));
        buf.length = 0;
      }
    }, interval);

    // swap the stream
    stream = {
      write: function(str){
        buf.push(str);
      }
    };
  }

  return function logger(req, res, next) {
    var sock = req.socket;
    req._startTime = new Date;
    req._remoteAddress = sock.socket ? sock.socket.remoteAddress : sock.remoteAddress;

    function logRequest(){
      res.removeListener('finish', logRequest);
      res.removeListener('close', logRequest);
      if (skip(req, res)) return;
      var line = fmt(exports, req, res);
      if (null == line) return;
      stream.write(line + '\n');
    };

    // immediate
    if (immediate) {
      logRequest();
    // proxy end to output logging
    } else {
      res.on('finish', logRequest);
      res.on('close', logRequest);
    }

    next();
  };
};

/**
 * Compile `fmt` into a function.
 *
 * @param {String} fmt
 * @return {Function}
 * @api private
 */

function compile(fmt) {
  fmt = fmt.replace(/"/g, '\\"');
  var js = '  return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
    return '"\n    + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
  }) + '";'
  return new Function('tokens, req, res', js);
};

/**
 * Define a token function with the given `name`,
 * and callback `fn(req, res)`.
 *
 * @param {String} name
 * @param {Function} fn
 * @return {Object} exports for chaining
 * @api public
 */

exports.token = function(name, fn) {
  exports[name] = fn;
  return this;
};

/**
 * Define a `fmt` with the given `name`.
 *
 * @param {String} name
 * @param {String|Function} fmt
 * @return {Object} exports for chaining
 * @api public
 */

exports.format = function(name, str){
  exports[name] = str;
  return this;
};

/**
 * Default format.
 */

exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');

/**
 * Short format.
 */

exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');

/**
 * Tiny format.
 */

exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');

/**
 * dev (colored)
 */

exports.format('dev', function(tokens, req, res){
  var status = res.statusCode
    , len = parseInt(res.getHeader('Content-Length'), 10)
    , color = 32;

  if (status >= 500) color = 31
  else if (status >= 400) color = 33
  else if (status >= 300) color = 36;

  len = isNaN(len)
    ? ''
    : len = ' - ' + bytes(len);

  return '\x1b[90m' + req.method
    + ' ' + req.originalUrl + ' '
    + '\x1b[' + color + 'm' + res.statusCode
    + ' \x1b[90m'
    + (new Date - req._startTime)
    + 'ms' + len
    + '\x1b[0m';
});

/**
 * request url
 */

exports.token('url', function(req){
  return req.originalUrl || req.url;
});

/**
 * request method
 */

exports.token('method', function(req){
  return req.method;
});

/**
 * response time in milliseconds
 */

exports.token('response-time', function(req){
  return String(Date.now() - req._startTime);
});

/**
 * UTC date
 */

exports.token('date', function(){
  return new Date().toUTCString();
});

/**
 * response status code
 */

exports.token('status', function(req, res){
  return res.headersSent ? res.statusCode : null;
});

/**
 * normalized referrer
 */

exports.token('referrer', function(req){
  return req.headers['referer'] || req.headers['referrer'];
});

/**
 * remote address
 */

exports.token('remote-addr', function(req){
  if (req.ip) return req.ip;
  if (req._remoteAddress) return req._remoteAddress;
  var sock = req.socket;
  if (sock.socket) return sock.socket.remoteAddress;
  return sock.remoteAddress;
});

/**
 * HTTP version
 */

exports.token('http-version', function(req){
  return req.httpVersionMajor + '.' + req.httpVersionMinor;
});

/**
 * UA string
 */

exports.token('user-agent', function(req){
  return req.headers['user-agent'];
});

/**
 * request header
 */

exports.token('req', function(req, res, field){
  return req.headers[field.toLowerCase()];
});

/**
 * response header
 */

exports.token('res', function(req, res, field){
  return (res._headers || {})[field.toLowerCase()];
});

<commit_msg>logger: fix url in dev logger to match :url token<commit_after>/*!
 * Connect - logger
 * Copyright(c) 2010 Sencha Inc.
 * Copyright(c) 2011 TJ Holowaychuk
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var bytes = require('bytes');

/*!
 * Log buffer.
 */

var buf = [];

/*!
 * Default log buffer duration.
 */

var defaultBufferDuration = 1000;

/**
 * Logger:
 *
 * Log requests with the given `options` or a `format` string.
 *
 * Options:
 *
 *   - `format`  Format string, see below for tokens
 *   - `stream`  Output stream, defaults to _stdout_
 *   - `buffer`  Buffer duration, defaults to 1000ms when _true_
 *   - `immediate`  Write log line on request instead of response (for response times)
 *   - `skip`    Function to determine if logging is skipped, called as
 *               `skip(req, res)`, defaults to always false.
 *
 * Tokens:
 *
 *   - `:req[header]` ex: `:req[Accept]`
 *   - `:res[header]` ex: `:res[Content-Length]`
 *   - `:http-version`
 *   - `:response-time`
 *   - `:remote-addr`
 *   - `:date`
 *   - `:method`
 *   - `:url`
 *   - `:referrer`
 *   - `:user-agent`
 *   - `:status`
 *
 * Formats:
 *
 *   Pre-defined formats that ship with connect:
 *
 *    - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
 *    - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
 *    - `tiny`  ':method :url :status :res[content-length] - :response-time ms'
 *    - `dev` concise output colored by response status for development use
 *
 * Examples:
 *
 *      connect.logger() // default
 *      connect.logger('short')
 *      connect.logger('tiny')
 *      connect.logger({ immediate: true, format: 'dev' })
 *      connect.logger(':method :url - :referrer')
 *      connect.logger(':req[content-type] -> :res[content-type]')
 *      connect.logger(function(tokens, req, res){ return 'some format string' })
 *      connect.logger({ format: 'dev', skip: function(req, res){ return res.statusCode === 304; }})
 *
 * Defining Tokens:
 *
 *   To define a token, simply invoke `connect.logger.token()` with the
 *   name and a callback function. The value returned is then available
 *   as ":type" in this case.
 *
 *      connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
 *
 * Defining Formats:
 *
 *   All default formats are defined this way, however it's public API as well:
 *
 *       connect.logger.format('name', 'string or function')
 *
 * @param {String|Function|Object} format or options
 * @return {Function}
 * @api public
 */

exports = module.exports = function logger(options) {
  if ('object' == typeof options) {
    options = options || {};
  } else if (options) {
    options = { format: options };
  } else {
    options = {};
  }

  // output on request instead of response
  var immediate = options.immediate;

  // check if log entry should be skipped
  var skip = options.skip || function () { return false; };

  // format name
  var fmt = exports[options.format] || options.format || exports.default;

  // compile format
  if ('function' != typeof fmt) fmt = compile(fmt);

  // options
  var stream = options.stream || process.stdout
    , buffer = options.buffer;

  // buffering support
  if (buffer) {
    var realStream = stream
      , interval = 'number' == typeof buffer
        ? buffer
        : defaultBufferDuration;

    // flush interval
    setInterval(function(){
      if (buf.length) {
        realStream.write(buf.join(''));
        buf.length = 0;
      }
    }, interval);

    // swap the stream
    stream = {
      write: function(str){
        buf.push(str);
      }
    };
  }

  return function logger(req, res, next) {
    var sock = req.socket;
    req._startTime = new Date;
    req._remoteAddress = sock.socket ? sock.socket.remoteAddress : sock.remoteAddress;

    function logRequest(){
      res.removeListener('finish', logRequest);
      res.removeListener('close', logRequest);
      if (skip(req, res)) return;
      var line = fmt(exports, req, res);
      if (null == line) return;
      stream.write(line + '\n');
    };

    // immediate
    if (immediate) {
      logRequest();
    // proxy end to output logging
    } else {
      res.on('finish', logRequest);
      res.on('close', logRequest);
    }

    next();
  };
};

/**
 * Compile `fmt` into a function.
 *
 * @param {String} fmt
 * @return {Function}
 * @api private
 */

function compile(fmt) {
  fmt = fmt.replace(/"/g, '\\"');
  var js = '  return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
    return '"\n    + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
  }) + '";'
  return new Function('tokens, req, res', js);
};

/**
 * Define a token function with the given `name`,
 * and callback `fn(req, res)`.
 *
 * @param {String} name
 * @param {Function} fn
 * @return {Object} exports for chaining
 * @api public
 */

exports.token = function(name, fn) {
  exports[name] = fn;
  return this;
};

/**
 * Define a `fmt` with the given `name`.
 *
 * @param {String} name
 * @param {String|Function} fmt
 * @return {Object} exports for chaining
 * @api public
 */

exports.format = function(name, str){
  exports[name] = str;
  return this;
};

/**
 * Default format.
 */

exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');

/**
 * Short format.
 */

exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');

/**
 * Tiny format.
 */

exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');

/**
 * dev (colored)
 */

exports.format('dev', function(tokens, req, res){
  var status = res.statusCode
    , len = parseInt(res.getHeader('Content-Length'), 10)
    , color = 32;

  if (status >= 500) color = 31
  else if (status >= 400) color = 33
  else if (status >= 300) color = 36;

  len = isNaN(len)
    ? ''
    : len = ' - ' + bytes(len);

  return '\x1b[90m' + req.method
    + ' ' + (req.originalUrl || req.url) + ' '
    + '\x1b[' + color + 'm' + res.statusCode
    + ' \x1b[90m'
    + (new Date - req._startTime)
    + 'ms' + len
    + '\x1b[0m';
});

/**
 * request url
 */

exports.token('url', function(req){
  return req.originalUrl || req.url;
});

/**
 * request method
 */

exports.token('method', function(req){
  return req.method;
});

/**
 * response time in milliseconds
 */

exports.token('response-time', function(req){
  return String(Date.now() - req._startTime);
});

/**
 * UTC date
 */

exports.token('date', function(){
  return new Date().toUTCString();
});

/**
 * response status code
 */

exports.token('status', function(req, res){
  return res.headersSent ? res.statusCode : null;
});

/**
 * normalized referrer
 */

exports.token('referrer', function(req){
  return req.headers['referer'] || req.headers['referrer'];
});

/**
 * remote address
 */

exports.token('remote-addr', function(req){
  if (req.ip) return req.ip;
  if (req._remoteAddress) return req._remoteAddress;
  var sock = req.socket;
  if (sock.socket) return sock.socket.remoteAddress;
  return sock.remoteAddress;
});

/**
 * HTTP version
 */

exports.token('http-version', function(req){
  return req.httpVersionMajor + '.' + req.httpVersionMinor;
});

/**
 * UA string
 */

exports.token('user-agent', function(req){
  return req.headers['user-agent'];
});

/**
 * request header
 */

exports.token('req', function(req, res, field){
  return req.headers[field.toLowerCase()];
});

/**
 * response header
 */

exports.token('res', function(req, res, field){
  return (res._headers || {})[field.toLowerCase()];
});

<|endoftext|>


<commit_before>/**
 * @fileOverview A class mixing Model and EventEmitter.
 */
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var _ = require('underscore');

var Model = require('./model');

/**
 * Extends both the Model base class and EventEmitter.
 *
 * @constructor
 * @extends {Kickq.Model}
 */
var EventModel = module.exports = function() {
  Model.call(this);
  EventEmitter.call(this);
};
util.inherits(EventModel, Model);
_.extend(EventModel.prototype, EventEmitter.prototype);
<commit_msg>update event model<commit_after>/**
 * @fileOverview A class mixing Model and EventEmitter.
 */
var EventEmitter = require('events').EventEmitter;

var cip = require('cip');

var CeventEmitter = cip.cast(EventEmitter);

var Model = require('./model');

/**
 * Extends both the Model base class and EventEmitter.
 *
 * @constructor
 * @extends {Kickq.Model}
 */
var EventModel = module.exports = Model.extend();

EventModel.mixin(CeventEmitter);
<|endoftext|>


<commit_before>var async = require('async');
var _ = require('underscore');
var path = require('path');
module.exports = function(app){
    var njax_broadcast = {
        broadcast:function(accounts, event, data, application_id){
            if(!application_id){
                //Assume it is the core app
                application_id = app.njax.config.core.app;
            }

            if(event.substr(0, application_id.length) != application_id){
                event = application_id + '.' + event;
            }
            if(accounts && !_.isArray(accounts)){
                accounts = [accounts];
            }
            var application = null;
            async.series([
                function(cb){
                    //Load the app
                    //TODO: Cache core app
                    return app.model.Application.findOne({ namespace: application_id}).exec(function(err, _application){
                        if(err) throw err;//TODO: Possibly fail silently
                        application = _application;
                        return cb();
                    });
                },
                function(cb){
					var eventData = {
						"entity_type":data._entity_type,
						"event_namespace":event,
						"entity_url":data._url,
						"entity_id":data._id || (data[data._entity_type] && data[data._entity_type]._id),
						"data":data,
						"application": application._id
					}

                    var eventObj = app.model.Event(eventData);
                    eventObj.accounts = [];
                    for(var i in accounts){
                        if(accounts[i].toObject){
                            eventObj.accounts.push(accounts[i].toObject());
                        }else if(_.isString(accounts[i])){
                            eventObj.accounts.push({
                                email:accounts[i]
                            });
                        }
                    }
                    return eventObj.save(function(err){
                        if(err) throw err;//TODO: Possibly fail quietly
                        return cb();
                    });
                },
                function(cb){

					if (accounts) {
						async.eachSeries(
							accounts,
							njax_broadcast.sendToAccounts(event, data),
							function(errs){
								return cb();
							});
					}
					else {
						njax_broadcast.sendToApplications(event, data, function(errs){
							return cb();
						});
					}

                },
				function(cb){
					console.log("Cache Refresh Begin:" + event);
					return app.njax.cache.refresh(event, data, function(err, cache){
						if(err){
							console.error("Error Refreshing Cache After '" + event + " Broacast:");
							console.error(err);
						}
						console.log("Cache Refresh Success", cache);
						return cb();
					});
				}
            ],
            function(){
                console.log("Successfully Finished Broadcast");
                //This never actually gets hit
            });
        },
        sendToAccounts:function(event, data){
            return function(account, cb){

                //Add event and account to data
                var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

                if(!account.toObject){
                    if(_.isString(account) && re.test(account)){

                    }else{
                        console.error("Missing Account Object:", account);
                        throw new Error("Invalid Account object passed in")
                    }
                    data._email = account;
                }else{
                    data._account = account;//.toObject();//TODO: Add validation
                }
                data._event = event;
                //Load default apps?
                var super_applications = app.njax.cache.super_applications;//TODO: Load Superlevel apps -> Better yet load these when you kick off NJax and have them ready
                //Load Apps by account
                var applications = super_applications;
                async.series([
                    function(cb){
                        if(!_.isObject(account)){
                            return cb();
                        }
                        account.authed_apps.get(function(err, _applications){//TODO: Eventually check permissions vs the event
                            if(err) throw err;
                            applications = _.union(_applications, applications);
                            console.log("Broadcasting '" + event + "' to applications. Count(" + applications.length + ")");
                            return cb();
                        });
                    },
                    function(cb){
                        async.each(
                            applications,
                            njax_broadcast.spawnChildren(data),
                            function(){
                                return cb();
                            }
                        );
                    }
                ],
                function(){
                    //end async
                    return cb();
                });
            }
        },
		sendToApplications:function(event, data, cb){
             data._event = event;
             //Load default apps?
             var super_applications = app.njax.cache.super_applications;//TODO: Load Superlevel apps -> Better yet load these when you kick off NJax and have them ready
             async.series([
                 function(cb){
                     async.each(
                         applications,
                         njax_broadcast.spawnChildren(data),
                         function(){
                             return cb();
                         }
                     );
                 }],
				 function(){
					 //end async
					 return cb();
				 });
        },
        spawnChildren:function(data){
            return function(application, cb){
                    //Gather callback uris
                    if(!application.callback_url){
                        return cb();
                    }
                    data._callback_url = application.callback_url;
                    console.log("Spawining: " + application.name + ' - ' + data._callback_url);
                    //Trigger child_process
                    var worker = app.njax.cp.spawn(
                        path.join(
                            app.njax.config.njax_dir,
                            '/lib/modules/broadcast/broadcast_worker'
                        )
                    );

                    worker.on('broadcast_success', function(data){
                        console.log("BroadCastSuccess:", application.name, data);
						worker.kill('SIGHUP');
                        return cb();
                    });
                    worker.on('broadcast_error', function(data){
                        console.error("BroadCastError:", application.name, data);
						worker.kill('SIGHUP');
                        return cb();
                    });
                    worker.send('broadcast', data);

            }
        }
  }
    return _.bind(njax_broadcast.broadcast, app);
}
<commit_msg>fix typo<commit_after>var async = require('async');
var _ = require('underscore');
var path = require('path');
module.exports = function(app){
    var njax_broadcast = {
        broadcast:function(accounts, event, data, application_id){
            if(!application_id){
                //Assume it is the core app
                application_id = app.njax.config.core.app;
            }

            if(event.substr(0, application_id.length) != application_id){
                event = application_id + '.' + event;
            }
            if(accounts && !_.isArray(accounts)){
                accounts = [accounts];
            }
            var application = null;
            async.series([
                function(cb){
                    //Load the app
                    //TODO: Cache core app
                    return app.model.Application.findOne({ namespace: application_id}).exec(function(err, _application){
                        if(err) throw err;//TODO: Possibly fail silently
                        application = _application;
                        return cb();
                    });
                },
                function(cb){
					var eventData = {
						"entity_type":data._entity_type,
						"event_namespace":event,
						"entity_url":data._url,
						"entity_id":data._id || (data[data._entity_type] && data[data._entity_type]._id),
						"data":data,
						"application": application._id
					}

                    var eventObj = app.model.Event(eventData);
                    eventObj.accounts = [];
                    for(var i in accounts){
                        if(accounts[i].toObject){
                            eventObj.accounts.push(accounts[i].toObject());
                        }else if(_.isString(accounts[i])){
                            eventObj.accounts.push({
                                email:accounts[i]
                            });
                        }
                    }
                    return eventObj.save(function(err){
                        if(err) throw err;//TODO: Possibly fail quietly
                        return cb();
                    });
                },
                function(cb){

					if (accounts) {
						async.eachSeries(
							accounts,
							njax_broadcast.sendToAccounts(event, data),
							function(errs){
								return cb();
							});
					}
					else {
						njax_broadcast.sendToApplications(event, data, function(errs){
							return cb();
						});
					}

                },
				function(cb){
					console.log("Cache Refresh Begin:" + event);
					return app.njax.cache.refresh(event, data, function(err, cache){
						if(err){
							console.error("Error Refreshing Cache After '" + event + " Broacast:");
							console.error(err);
						}
						console.log("Cache Refresh Success", cache);
						return cb();
					});
				}
            ],
            function(){
                console.log("Successfully Finished Broadcast");
                //This never actually gets hit
            });
        },
        sendToAccounts:function(event, data){
            return function(account, cb){

                //Add event and account to data
                var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

                if(!account.toObject){
                    if(_.isString(account) && re.test(account)){

                    }else{
                        console.error("Missing Account Object:", account);
                        throw new Error("Invalid Account object passed in")
                    }
                    data._email = account;
                }else{
                    data._account = account;//.toObject();//TODO: Add validation
                }
                data._event = event;
                //Load default apps?
                var super_applications = app.njax.cache.super_applications;//TODO: Load Superlevel apps -> Better yet load these when you kick off NJax and have them ready
                //Load Apps by account
                var applications = super_applications;
                async.series([
                    function(cb){
                        if(!_.isObject(account)){
                            return cb();
                        }
                        account.authed_apps.get(function(err, _applications){//TODO: Eventually check permissions vs the event
                            if(err) throw err;
                            applications = _.union(_applications, applications);
                            console.log("Broadcasting '" + event + "' to applications. Count(" + applications.length + ")");
                            return cb();
                        });
                    },
                    function(cb){
                        async.each(
                            applications,
                            njax_broadcast.spawnChildren(data),
                            function(){
                                return cb();
                            }
                        );
                    }
                ],
                function(){
                    //end async
                    return cb();
                });
            }
        },
		sendToApplications:function(event, data, cb){
             data._event = event;
             //Load default apps?
             var super_applications = app.njax.cache.super_applications;//TODO: Load Superlevel apps -> Better yet load these when you kick off NJax and have them ready
             async.series([
                 function(cb){
                     async.each(
                         super_applications,
                         njax_broadcast.spawnChildren(data),
                         function(){
                             return cb();
                         }
                     );
                 }],
				 function(){
					 //end async
					 return cb();
				 });
        },
        spawnChildren:function(data){
            return function(application, cb){
                    //Gather callback uris
                    if(!application.callback_url){
                        return cb();
                    }
                    data._callback_url = application.callback_url;
                    console.log("Spawining: " + application.name + ' - ' + data._callback_url);
                    //Trigger child_process
                    var worker = app.njax.cp.spawn(
                        path.join(
                            app.njax.config.njax_dir,
                            '/lib/modules/broadcast/broadcast_worker'
                        )
                    );

                    worker.on('broadcast_success', function(data){
                        console.log("BroadCastSuccess:", application.name, data);
						worker.kill('SIGHUP');
                        return cb();
                    });
                    worker.on('broadcast_error', function(data){
                        console.error("BroadCastError:", application.name, data);
						worker.kill('SIGHUP');
                        return cb();
                    });
                    worker.send('broadcast', data);

            }
        }
  }
    return _.bind(njax_broadcast.broadcast, app);
}
<|endoftext|>


<commit_before>var async = require('async');
var _ = require('underscore');
var path = require('path');
module.exports = function(app){
    var njax_broadcast = {
        broadcast:function(accounts, event, data, application_id){
            if(!application_id){
                //Assume it is the core app
                application_id = app.njax.config.core.app;
            }
            //TODO:Should probablly log this somewhere
            if(event.substr(0, application_id.length) != application_id){
                event = application_id + '.' + event;
            }
            if(!_.isArray(accounts)){
                accounts = [accounts];
            }
            var application = null;
            async.series([
                function(cb){
                    //Load the app
                    //TODO: Cache core app
                    return app.model.Application.findOne({ namespace: application_id}).exec(function(err, _application){
                        if(err) throw err;//TODO: Possibly fail silently
                        application = _application;
                        return cb();
                    });
                },
                function(cb){

                    var eventObj = app.model.Event({
                        "event_namespace":event,
                        "entity_url":data._url,
                        "data":data,
                        "application": application._id
                    });
                    eventObj.accounts = [];
                    for(var i in accounts){
                        if(accounts[i].toObject){
                            eventObj.accounts.push(accounts[i].toObject());
                        }else if(_.isString(accounts[i])){
                            eventObj.accounts.push({
                                email:accounts[i]
                            });
                        }
                    }
                    return eventObj.save(function(err){
                        if(err) throw err;//TODO: Possibly fail quietly
                        return cb();
                    });
                },
                function(cb){

                    async.eachSeries(
                        accounts,
                        njax_broadcast.sendToAccounts(event, data),
                        function(errs){
                            return cb();
                        }
                    )

                }
            ],
            function(){
                console.log("Successfully Finished Broadcast");
                //This never actually gets hit
            });
        },
        sendToAccounts:function(event, data){
            return function(account, cb){

                //Add event and account to data
                var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

                if(!account.toObject){
                    if(_.isString(account) && re.test(account)){

                    }else{
                        console.error("Missing Account Object:", account);
                        throw new Error("Invalid Account object passed in")
                    }
                    data._email = account;
                }else{
                    data._account = account;//.toObject();//TODO: Add validation
                }
                data._event = event;
                //Load default apps?
                var super_applications = app.njax.super_applications;//TODO: Load Superlevel apps -> Better yet load these when you kick off NJax and have them ready
                //Load Apps by account
                var applications = super_applications;
                async.series([
                    function(cb){
                        if(!_.isObject(account)){
                            return cb();
                        }
                        account.authed_apps.get(function(err, _applications){//TODO: Eventually check permissions vs the event
                            if(err) throw err;
                            applications = _.union(_applications, applications);
                            console.log("Broadcasting '" + event + "' to applications. Count(" + applications.length + ")");
                            return cb();
                        });
                    },
                    function(cb){
                        async.each(
                            applications,
                            njax_broadcast.spawnChildren(data),
                            function(){
                                return cb();
                            }
                        );
                    }
                ],
                function(){
                    //end async
                    return cb();
                });
            }
        },
        spawnChildren:function(data){
            return function(application, cb){
                    //Gather callback uris
                    if(!application.callback_url){
                        return cb();
                    }
                    data._callback_url = application.callback_url;
                    console.log("Spawining: " + application.name + ' - ' + data._callback_url);
                    //Trigger child_process
                    var worker = app.njax.cp.spawn(
                        path.join(
                            app.njax.config.njax_dir,
                            '/lib/modules/broadcast/broadcast_worker'
                        )
                    );

                    worker.on('broadcast_success', function(data){
                        console.log("BroadCastSuccess:", application.name, data);
                        return cb();
                    });
                    worker.on('broadcast_error', function(data){
                        console.error("BroadCastError:", application.name, data);
                        return cb();
                    });
                    worker.send('broadcast', data);

            }
        }
  }
    return _.bind(njax_broadcast.broadcast, app);
}<commit_msg>Code flying from the fingertips<commit_after>var async = require('async');
var _ = require('underscore');
var path = require('path');
module.exports = function(app){
    var njax_broadcast = {
        broadcast:function(accounts, event, data, application_id){
            if(!application_id){
                //Assume it is the core app
                application_id = app.njax.config.core.app;
            }
            //TODO:Should probablly log this somewhere
            if(event.substr(0, application_id.length) != application_id){
                event = application_id + '.' + event;
            }
            if(!_.isArray(accounts)){
                accounts = [accounts];
            }
            var application = null;
            async.series([
                function(cb){
                    //Load the app
                    //TODO: Cache core app
                    return app.model.Application.findOne({ namespace: application_id}).exec(function(err, _application){
                        if(err) throw err;//TODO: Possibly fail silently
                        application = _application;
                        return cb();
                    });
                },
                function(cb){

                    var eventObj = app.model.Event({
                        "event_namespace":event,
                        "entity_url":data._url,
                        "data":data,
                        "application": application._id
                    });
                    eventObj.accounts = [];
                    for(var i in accounts){
                        if(accounts[i].toObject){
                            eventObj.accounts.push(accounts[i].toObject());
                        }else if(_.isString(accounts[i])){
                            eventObj.accounts.push({
                                email:accounts[i]
                            });
                        }
                    }
                    return eventObj.save(function(err){
                        if(err) throw err;//TODO: Possibly fail quietly
                        return cb();
                    });
                },
                function(cb){

                    async.eachSeries(
                        accounts,
                        njax_broadcast.sendToAccounts(event, data),
                        function(errs){
                            return cb();
                        }
                    )

                }
            ],
            function(){
                console.log("Successfully Finished Broadcast");
                //This never actually gets hit
            });
        },
        sendToAccounts:function(event, data){
            return function(account, cb){

                //Add event and account to data
                var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

                if(!account.toObject){
                    if(_.isString(account) && re.test(account)){

                    }else{
                        console.error("Missing Account Object:", account);
                        throw new Error("Invalid Account object passed in")
                    }
                    data._email = account;
                }else{
                    data._account = account;//.toObject();//TODO: Add validation
                }
                data._event = event;
                //Load default apps?
                var super_applications = app.njax.super_applications;//TODO: Load Superlevel apps -> Better yet load these when you kick off NJax and have them ready
                //Load Apps by account
                var applications = super_applications;
                async.series([
                    function(cb){
                        if(!_.isObject(account)){
                            return cb();
                        }
                        account.authed_apps.get(function(err, _applications){//TODO: Eventually check permissions vs the event
                            if(err) throw err;
                            applications = _.union(_applications, applications);
                            console.log("Broadcasting '" + event + "' to applications. Count(" + applications.length + ")");
                            return cb();
                        });
                    },
                    function(cb){
                        async.each(
                            applications,
                            njax_broadcast.spawnChildren(data),
                            function(){
                                return cb();
                            }
                        );
                    }
                ],
                function(){
                    //end async
                    return cb();
                });
            }
        },
        spawnChildren:function(data){
            return function(application, cb){
                    //Gather callback uris
                    if(!application.callback_url){
                        return cb();
                    }
                    data._callback_url = application.callback_url;
                    console.log("Spawining: " + application.name + ' - ' + data._callback_url);
                    //Trigger child_process
                    var worker = app.njax.cp.spawn(
                        path.join(
                            app.njax.config.njax_dir,
                            '/lib/modules/broadcast/broadcast_worker'
                        )
                    );

                    worker.on('broadcast_success', function(data){
                        console.log("BroadCastSuccess:", application.name, data);
						worker.kill('SIGHUP');
                        return cb();
                    });
                    worker.on('broadcast_error', function(data){
                        console.error("BroadCastError:", application.name, data);
						worker.kill('SIGHUP');
                        return cb();
                    });
                    worker.send('broadcast', data);

            }
        }
  }
    return _.bind(njax_broadcast.broadcast, app);
}<|endoftext|>


<commit_before>// node.js libraries
var http = require('http'),
    https = require('https'),
    sys = require('sys'),
    fs = require('fs'),
    events = require('events'),
    Buffer = require('buffer').Buffer,
    url = require('url'),
    path = require('path');

// Local node-get libraries
var encodings = require('./encodings');

var default_headers = {
    'Accept-Encoding': 'none',
    'Connection': 'close',
    'User-Agent': 'curl'
};

// Get a Get object. Takes an argument, that is either
// a plain string representing the URI, or an object:
//
//     {
//       // required
//       uri: "string of uri",
//
//       // optional, default in source
//       headers: { object of headers to send with the request }
//
//       // optional, default 10
//       max_redirs: 5 // maximum number of redirects to follow
//
//       no_proxy: true // prevent automatic proxy usage when HTTP_PROXY is set.
//     }
function Get(options) {
    // Allow calling without new keyword.
    if (!(this instanceof Get)) {
        return new Get(options);
    }

    if (typeof options == 'string') {
        this.uri = options;
        this.headers = default_headers;
        if (process.env.HTTP_PROXY) {
            this.proxy = url.parse(process.env.HTTP_PROXY);
        } else {
            this.proxy = {};
        }
    } else {
        if (!options.uri) {
            throw Error('uri option required in get constructor');
        }
        this.uri = options.uri;
        this.max_redirs = options.max_redirs || 10;
        this.encoding = options.encoding;
        this.headers = options.headers || default_headers;
        if (!this.no_proxy && process.env.HTTP_PROXY) {
            this.proxy = url.parse(process.env.HTTP_PROXY);
        } else {
            this.proxy = {};
        }
    }
}

sys.inherits(Get, events.EventEmitter);

// Create a HTTP request. Just sanity-checks URLs and
// chooses an HTTP or HTTPS request.
//
// - @return {http.ClientRequest}
Get.prototype.request = function(callback) {
    // TODO: handle non http/https protocols
    this.uri_o = url.parse(this.uri);

    // Validate the URI at this step so that invalid
    // redirects are also caught.
    if (!(this.uri_o.protocol &&
        (this.uri_o.protocol == 'http:' || this.uri_o.protocol == 'https:') &&
        this.uri_o.hostname)) {
        return callback.call(this, new Error('Invalid URL'));
    }

    // TODO: should pronode-getxies support HTTPS?
    if (this.uri_o.protocol == 'https:') {
        return https.request({
            host: this.uri_o.hostname,
            port: 443,
            path: this.proxy.hostname ?
                 this.uri :
                 ((this.uri_o.pathname || '') +
                 (this.uri_o.search || '') +
                 (this.uri_o.hash || '')) || '/'
        }, callback);
    } else {
        return http.request({
            port: this.proxy.port || this.uri_o.port || 80,
            host: this.proxy.hostname || this.uri_o.hostname,
            path: this.proxy.hostname ?
                 this.uri :
                 ((this.uri_o.pathname || '') +
                 (this.uri_o.search || '') +
                 (this.uri_o.hash || '')) || '/'
        }, callback);
    }
};

// Innermost API function of Get
//
// - @param {Function} callback
// - @param {Number} times number of times re-called.
Get.prototype.perform = function(callback, times) {
    if (times > this.max_redirs) {
        return callback(new Error('Redirect limit of ' +
            this.max_redirs +
            ' reached'));
    }

    times = times || 1;

    var clientrequest = this.request(function(response) {
        if (response.statusCode >= 300 &&
            response.statusCode < 400 &&
            response.headers.location) {
            // Redirection
            // -----------
            // Servers can send a full redirect location
            // or a short form, like a hyperlink. Handle both.
            if (url.parse(response.headers.location).protocol) {
                this.uri = response.headers.location;
            } else {
                this.uri = url.resolve(this.uri, response.headers.location);
            }
            this.perform(callback, times + 1);
            return;
        } else if (response.statusCode >= 400) {
            // failure
            var err = new Error('Server returned HTTP ' + response.statusCode);
            err.status = response.statusCode;
            callback.call(this, err, response);
        } else {
            // success
            callback.call(this, null, response);
        }
    }.bind(this));

    // The client can fail if the url is invalid
    if (!clientrequest) return;

    // Handle DNS-level errors, like ECONNREFUSED
    clientrequest.on('error', function(err) {
        callback.call(this, err);
    }.bind(this));

    // TODO: fix when/if gzip is supported.
    // If a proxy is defined, ask for the full requested URL,
    // otherwise construct the URL without a hostname and protocol.
    /*
    var request = c.request('GET',
        this.proxy.hostname ?
         this.uri :
         ((this.uri_o.pathname || '') +
         (this.uri_o.search || '') +
         (this.uri_o.hash || '')) || '/', {
            host: this.uri_o.hostname
        }
    );
    */
    clientrequest.end();
};

// Stream a file to disk
// ---------------------
// - @param {String} filename.
// - @param {Function} callback.
Get.prototype.toDisk = function(filename, callback) {
    // TODO: catch all errors
    this.perform(function(err, response) {
        if (err) return callback(err);

        // Don't set an encoding. Using an encoding messes up binary files.
        // Pump contents from the response stream into a new writestream.
        var file = fs.createWriteStream(filename);
        file.on('error', callback);
        file.on('close', function() { callback(null, filename, response.headers); });
        response.pipe(file);
    });
};

// Get the contents of a URL as a string
//
// - @param {Function} callback.
Get.prototype.asString = function(callback) {
    // TODO: catch all errors
    this.perform(function(err, response) {
        if (err) return callback(err);

        switch ((response.headers['content-type'] || '').toLowerCase()) {
            case 'binary/octet-stream':
            case 'application/octet-stream':
                return callback(new Error("Can't download binary file as string"));
            default:
                // TODO: respect Content-Transfer-Encoding header
                response.setEncoding(this.guessEncoding(this.uri));
        }

        // Fill an array with chunks of data,
        // and then join it into a string before calling `callback`
        var out = [];
        response.on('data', function(chunk) {
            out.push(chunk);
        });
        response.on('error', function(err) {
            callback(err);
        });
        response.on('end', function() {
            callback(null, out.join(''), response.headers);
        });
        response.on('close', function() {
            // TODO: should this be this way?
            callback(null, out.join(''), response.headers);
        });
    });
};

// Get the contents of a URL as a buffer
//
// - @param {Function} callback.
Get.prototype.asBuffer = function(callback) {
    this.perform(function(err, response) {
        if (err) return callback(err);

        // Don't set an encoding.

        // Fill an array with chunks of data,
        // and then join it into a buffer before calling `callback`
        var out = [];
        response.on('data', function(chunk) {
            out.push(chunk);
        });
        response.on('error', function(err) {
            callback(err);
        });
        response.on('end', function() {
            for (var length = 0, i = 0; i < out.length; i++) {
                length += out[i].length;
            }
            var result = new Buffer(length);
            for (var pos = 0, j = 0; j < out.length; j++) {
                out[j].copy(result, pos);
                pos += out[j].length;
            }
            callback(null, result, response.headers);
        });
    });
};

Get.prototype.guessEncoding = function(location) {
    // The 'most reliable' measure is probably the end of files, so
    // start off with extname.
    if (this.encoding) return this.encoding;
    var ext = path.extname(location).toLowerCase();
    if (encodings.ext[ext]) return encodings.ext[ext];
};

module.exports = Get;
<commit_msg>Less dead code<commit_after>// node.js libraries
var http = require('http'),
    https = require('https'),
    sys = require('sys'),
    fs = require('fs'),
    events = require('events'),
    Buffer = require('buffer').Buffer,
    url = require('url'),
    path = require('path');

// Local node-get libraries
var encodings = require('./encodings');

var default_headers = {
    'Accept-Encoding': 'none',
    'Connection': 'close',
    'User-Agent': 'curl'
};

// Get a Get object. Takes an argument, that is either
// a plain string representing the URI, or an object:
//
//     {
//       // required
//       uri: "string of uri",
//
//       // optional, default in source
//       headers: { object of headers to send with the request }
//
//       // optional, default 10
//       max_redirs: 5 // maximum number of redirects to follow
//
//       no_proxy: true // prevent automatic proxy usage when HTTP_PROXY is set.
//     }
function Get(options) {
    // Allow calling without new keyword.
    if (!(this instanceof Get)) {
        return new Get(options);
    }

    if (typeof options == 'string') {
        this.uri = options;
        this.headers = default_headers;
        if (process.env.HTTP_PROXY) {
            this.proxy = url.parse(process.env.HTTP_PROXY);
        } else {
            this.proxy = {};
        }
    } else {
        if (!options.uri) {
            throw Error('uri option required in get constructor');
        }
        this.uri = options.uri;
        this.max_redirs = options.max_redirs || 10;
        this.encoding = options.encoding;
        this.headers = options.headers || default_headers;
        if (!this.no_proxy && process.env.HTTP_PROXY) {
            this.proxy = url.parse(process.env.HTTP_PROXY);
        } else {
            this.proxy = {};
        }
    }
}

sys.inherits(Get, events.EventEmitter);

// Create a HTTP request. Just sanity-checks URLs and
// chooses an HTTP or HTTPS request.
//
// - @return {http.ClientRequest}
Get.prototype.request = function(callback) {
    // TODO: handle non http/https protocols
    this.uri_o = url.parse(this.uri);

    // Validate the URI at this step so that invalid
    // redirects are also caught.
    if (!(this.uri_o.protocol &&
        (this.uri_o.protocol == 'http:' || this.uri_o.protocol == 'https:') &&
        this.uri_o.hostname)) {
        return callback.call(this, new Error('Invalid URL'));
    }

    // TODO: should pronode-getxies support HTTPS?
    if (this.uri_o.protocol == 'https:') {
        return https.request({
            host: this.uri_o.hostname,
            port: 443,
            path: this.proxy.hostname ?
                 this.uri :
                 ((this.uri_o.pathname || '') +
                 (this.uri_o.search || '') +
                 (this.uri_o.hash || '')) || '/'
        }, callback);
    } else {
        return http.request({
            port: this.proxy.port || this.uri_o.port || 80,
            host: this.proxy.hostname || this.uri_o.hostname,
            path: this.proxy.hostname ?
                 this.uri :
                 ((this.uri_o.pathname || '') +
                 (this.uri_o.search || '') +
                 (this.uri_o.hash || '')) || '/'
        }, callback);
    }
};

// Innermost API function of Get
//
// - @param {Function} callback
// - @param {Number} times number of times re-called.
Get.prototype.perform = function(callback, times) {
    if (times > this.max_redirs) {
        return callback(new Error('Redirect limit of ' +
            this.max_redirs +
            ' reached'));
    }

    times = times || 1;

    var clientrequest = this.request(function(response) {
        if (response.statusCode >= 300 &&
            response.statusCode < 400 &&
            response.headers.location) {
            // Redirection
            // -----------
            // Servers can send a full redirect location
            // or a short form, like a hyperlink. Handle both.
            if (url.parse(response.headers.location).protocol) {
                this.uri = response.headers.location;
            } else {
                this.uri = url.resolve(this.uri, response.headers.location);
            }
            this.perform(callback, times + 1);
            return;
        } else if (response.statusCode >= 400) {
            // failure
            var err = new Error('Server returned HTTP ' + response.statusCode);
            err.status = response.statusCode;
            callback.call(this, err, response);
        } else {
            // success
            callback.call(this, null, response);
        }
    }.bind(this));

    // The client can fail if the url is invalid
    if (!clientrequest) return;

    // Handle DNS-level errors, like ECONNREFUSED
    clientrequest.on('error', function(err) {
        callback.call(this, err);
    }.bind(this));

    // TODO: fix when/if gzip is supported.
    // If a proxy is defined, ask for the full requested URL,
    // otherwise construct the URL without a hostname and protocol.
    clientrequest.end();
};

// Stream a file to disk
// ---------------------
// - @param {String} filename.
// - @param {Function} callback.
Get.prototype.toDisk = function(filename, callback) {
    // TODO: catch all errors
    this.perform(function(err, response) {
        if (err) return callback(err);

        // Don't set an encoding. Using an encoding messes up binary files.
        // Pump contents from the response stream into a new writestream.
        var file = fs.createWriteStream(filename);
        file.on('error', callback);
        file.on('close', function() { callback(null, filename, response.headers); });
        response.pipe(file);
    });
};

// Get the contents of a URL as a string
//
// - @param {Function} callback.
Get.prototype.asString = function(callback) {
    // TODO: catch all errors
    this.perform(function(err, response) {
        if (err) return callback(err);

        switch ((response.headers['content-type'] || '').toLowerCase()) {
            case 'binary/octet-stream':
            case 'application/octet-stream':
                return callback(new Error("Can't download binary file as string"));
            default:
                // TODO: respect Content-Transfer-Encoding header
                response.setEncoding(this.guessEncoding(this.uri));
        }

        // Fill an array with chunks of data,
        // and then join it into a string before calling `callback`
        var out = [];
        response.on('data', function(chunk) {
            out.push(chunk);
        });
        response.on('error', function(err) {
            callback(err);
        });
        response.on('end', function() {
            callback(null, out.join(''), response.headers);
        });
        response.on('close', function() {
            // TODO: should this be this way?
            callback(null, out.join(''), response.headers);
        });
    });
};

// Get the contents of a URL as a buffer
//
// - @param {Function} callback.
Get.prototype.asBuffer = function(callback) {
    this.perform(function(err, response) {
        if (err) return callback(err);

        // Don't set an encoding.

        // Fill an array with chunks of data,
        // and then join it into a buffer before calling `callback`
        var out = [];
        response.on('data', function(chunk) {
            out.push(chunk);
        });
        response.on('error', function(err) {
            callback(err);
        });
        response.on('end', function() {
            for (var length = 0, i = 0; i < out.length; i++) {
                length += out[i].length;
            }
            var result = new Buffer(length);
            for (var pos = 0, j = 0; j < out.length; j++) {
                out[j].copy(result, pos);
                pos += out[j].length;
            }
            callback(null, result, response.headers);
        });
    });
};

Get.prototype.guessEncoding = function(location) {
    // The 'most reliable' measure is probably the end of files, so
    // start off with extname.
    if (this.encoding) return this.encoding;
    var ext = path.extname(location).toLowerCase();
    if (encodings.ext[ext]) return encodings.ext[ext];
};

module.exports = Get;
<|endoftext|>


<commit_before>var sys = require('sys'),
    http = require('http'),
    fs = require('fs'),
    url = require('url'),
    events = require('events');

var DEFAULT_PORT = 8000;

function main(argv) {
  new HttpServer({
    'GET': createServlet(StaticServlet),
    'HEAD': createServlet(StaticServlet)
  }).start(Number(argv[2]) || DEFAULT_PORT);
}

function escapeHtml(value) {
  return value.toString().
    replace('<', '<').
    replace('>', '&gt').
    replace('"', '"');
}

function createServlet(Class) {
  var servlet = new Class();
  return servlet.handleRequest.bind(servlet);
}

/**
 * An Http server implementation that uses a map of methods to decide
 * action routing.
 *
 * @param {Object} Map of method => Handler function
 */
function HttpServer(handlers) {
  this.handlers = handlers;
  this.server = http.createServer(this.handleRequest_.bind(this));
}

HttpServer.prototype.start = function(port) {
  this.port = port;
  this.server.listen(port);
  sys.puts('Http Server running at http://127.0.0.1:' + port + '/');
};

HttpServer.prototype.parseUrl_ = function(urlString) {
  var parsed = url.parse(urlString);
  parsed.pathname = url.resolve('/', parsed.pathname);
  return url.parse(url.format(parsed), true);
};

HttpServer.prototype.handleRequest_ = function(req, res) {
  var logEntry = req.method + ' ' + req.url;
  if (req.headers['user-agent']) {
    logEntry += ' ' + req.headers['user-agent'];
  }
  sys.puts(logEntry);
  req.url = this.parseUrl_(req.url);
  var handler = this.handlers[req.method];
  if (!handler) {
    res.writeHead(501);
    res.end();
  } else {
    handler.call(this, req, res);
  }
};

/**
 * Handles static content.
 */
function StaticServlet() {}

StaticServlet.MimeMap = {
  'txt': 'text/plain',
  'html': 'text/html',
  'css': 'text/css',
  'xml': 'application/xml',
  'json': 'application/json',
  'js': 'application/javascript',
  'jpg': 'image/jpeg',
  'jpeg': 'image/jpeg',
  'gif': 'image/gif',
  'png': 'image/png',
  'manifest': 'text/cache-manifest'
};

StaticServlet.prototype.handleRequest = function(req, res) {
  var self = this;
  var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){
    return String.fromCharCode(parseInt(hex, 16));
  });
  var parts = path.split('/');
  if (parts[parts.length-1].charAt(0) === '.')
    return self.sendForbidden_(req, res, path);

  // docs rewriting
  var REWRITE = /\/(guide|api|cookbook|misc|tutorial)\/.*$/,
      IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
      match;

  if (!IGNORED.test(path) && (match = path.match(REWRITE))) {
      path = path.replace(match[0], '/index.html');
      sys.puts('Rewrite to ' + path);
  }
  // end of docs rewriting

  fs.stat(path, function(err, stat) {
    if (err)
      return self.sendMissing_(req, res, path);
    if (stat.isDirectory())
      return self.sendDirectory_(req, res, path);
    return self.sendFile_(req, res, path);
  });
};

StaticServlet.prototype.sendError_ = function(req, res, error) {
  res.writeHead(500, {
      'Content-Type': 'text/html'
  });
  res.write('<!doctype html>\n');
  res.write('<title>Internal Server Error</title>\n');
  res.write('
Internal Server Error
');
  res.write('
' + escapeHtml(sys.inspect(error)) + '
');
  sys.puts('500 Internal Server Error');
  sys.puts(sys.inspect(error));
};

StaticServlet.prototype.sendMissing_ = function(req, res, path) {
  path = path.substring(1);
  res.writeHead(404, {
      'Content-Type': 'text/html'
  });
  res.write('<!doctype html>\n');
  res.write('<title>404 Not Found</title>\n');
  res.write('
Not Found
');
  res.write(
    '
The requested URL ' +
    escapeHtml(path) +
    ' was not found on this server.
'
  );
  res.end();
  sys.puts('404 Not Found: ' + path);
};

StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
  path = path.substring(1);
  res.writeHead(403, {
      'Content-Type': 'text/html'
  });
  res.write('<!doctype html>\n');
  res.write('<title>403 Forbidden</title>\n');
  res.write('
Forbidden
');
  res.write(
    '
You do not have permission to access ' +
    escapeHtml(path) + ' on this server.
'
  );
  res.end();
  sys.puts('403 Forbidden: ' + path);
};

StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
  res.writeHead(301, {
      'Content-Type': 'text/html',
      'Location': redirectUrl
  });
  res.write('<!doctype html>\n');
  res.write('<title>301 Moved Permanently</title>\n');
  res.write('
Moved Permanently
');
  res.write(
    '
The document has moved here.
'
  );
  res.end();
  sys.puts('401 Moved Permanently: ' + redirectUrl);
};

StaticServlet.prototype.sendFile_ = function(req, res, path) {
  var self = this;
  var file = fs.createReadStream(path);
  res.writeHead(200, {
    'Content-Type': StaticServlet.
      MimeMap[path.split('.').pop()] || 'text/plain'
  });
  if (req.method === 'HEAD') {
    res.end();
  } else {
    file.on('data', res.write.bind(res));
    file.on('close', function() {
      res.end();
    });
    file.on('error', function(error) {
      self.sendError_(req, res, error);
    });
  }
};

StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
  var self = this;
  if (path.match(/[^\/]$/)) {
    req.url.pathname += '/';
    var redirectUrl = url.format(url.parse(url.format(req.url)));
    return self.sendRedirect_(req, res, redirectUrl);
  }
  fs.readdir(path, function(err, files) {
    if (err)
      return self.sendError_(req, res, error);

    if (!files.length)
      return self.writeDirectoryIndex_(req, res, path, []);

    var remaining = files.length;
    files.forEach(function(fileName, index) {
      fs.stat(path + '/' + fileName, function(err, stat) {
        if (err)
          return self.sendError_(req, res, err);
        if (stat.isDirectory()) {
          files[index] = fileName + '/';
        }
        if (!(--remaining))
          return self.writeDirectoryIndex_(req, res, path, files);
      });
    });
  });
};

StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
  path = path.substring(1);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  if (req.method === 'HEAD') {
    res.end();
    return;
  }
  res.write('<!doctype html>\n');
  res.write('<title>' + escapeHtml(path) + '</title>\n');
  res.write('\n');
  res.write('
Directory: ' + escapeHtml(path) + '
');
  res.write('
');
  files.forEach(function(fileName) {
    if (fileName.charAt(0) !== '.') {
      res.write('
' +
        escapeHtml(fileName) + '
');
    }
  });
  res.write('
');
  res.end();
};

// Must be last,
main(process.argv);
<commit_msg>feat(nodeserver): if index.html exists, serve it instead of directory listing<commit_after>var sys = require('sys'),
    http = require('http'),
    fs = require('fs'),
    url = require('url'),
    events = require('events');

var DEFAULT_PORT = 8000;

function main(argv) {
  new HttpServer({
    'GET': createServlet(StaticServlet),
    'HEAD': createServlet(StaticServlet)
  }).start(Number(argv[2]) || DEFAULT_PORT);
}

function escapeHtml(value) {
  return value.toString().
    replace('<', '<').
    replace('>', '&gt').
    replace('"', '"');
}

function createServlet(Class) {
  var servlet = new Class();
  return servlet.handleRequest.bind(servlet);
}

/**
 * An Http server implementation that uses a map of methods to decide
 * action routing.
 *
 * @param {Object} Map of method => Handler function
 */
function HttpServer(handlers) {
  this.handlers = handlers;
  this.server = http.createServer(this.handleRequest_.bind(this));
}

HttpServer.prototype.start = function(port) {
  this.port = port;
  this.server.listen(port);
  sys.puts('Http Server running at http://127.0.0.1:' + port + '/');
};

HttpServer.prototype.parseUrl_ = function(urlString) {
  var parsed = url.parse(urlString);
  parsed.pathname = url.resolve('/', parsed.pathname);
  return url.parse(url.format(parsed), true);
};

HttpServer.prototype.handleRequest_ = function(req, res) {
  var logEntry = req.method + ' ' + req.url;
  if (req.headers['user-agent']) {
    logEntry += ' ' + req.headers['user-agent'];
  }
  sys.puts(logEntry);
  req.url = this.parseUrl_(req.url);
  var handler = this.handlers[req.method];
  if (!handler) {
    res.writeHead(501);
    res.end();
  } else {
    handler.call(this, req, res);
  }
};

/**
 * Handles static content.
 */
function StaticServlet() {}

StaticServlet.MimeMap = {
  'txt': 'text/plain',
  'html': 'text/html',
  'css': 'text/css',
  'xml': 'application/xml',
  'json': 'application/json',
  'js': 'application/javascript',
  'jpg': 'image/jpeg',
  'jpeg': 'image/jpeg',
  'gif': 'image/gif',
  'png': 'image/png',
  'manifest': 'text/cache-manifest'
};

StaticServlet.prototype.handleRequest = function(req, res) {
  var self = this;
  var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){
    return String.fromCharCode(parseInt(hex, 16));
  });
  var parts = path.split('/');
  if (parts[parts.length-1].charAt(0) === '.')
    return self.sendForbidden_(req, res, path);

  // docs rewriting
  var REWRITE = /\/(guide|api|cookbook|misc|tutorial)\/.*$/,
      IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
      match;

  if (!IGNORED.test(path) && (match = path.match(REWRITE))) {
      path = path.replace(match[0], '/index.html');
      sys.puts('Rewrite to ' + path);
  }
  // end of docs rewriting

  fs.stat(path, function(err, stat) {
    if (err)
      return self.sendMissing_(req, res, path);
    if (stat.isDirectory())
      return fs.stat(path + 'index.html', function(err, stat) {
        // send index.html if exists
        if (!err)
          return self.sendFile_(req, res, path + 'index.html');

        // list files otherwise
        return self.sendDirectory_(req, res, path);
      });

    return self.sendFile_(req, res, path);
  });
};

StaticServlet.prototype.sendError_ = function(req, res, error) {
  res.writeHead(500, {
      'Content-Type': 'text/html'
  });
  res.write('<!doctype html>\n');
  res.write('<title>Internal Server Error</title>\n');
  res.write('
Internal Server Error
');
  res.write('
' + escapeHtml(sys.inspect(error)) + '
');
  sys.puts('500 Internal Server Error');
  sys.puts(sys.inspect(error));
};

StaticServlet.prototype.sendMissing_ = function(req, res, path) {
  path = path.substring(1);
  res.writeHead(404, {
      'Content-Type': 'text/html'
  });
  res.write('<!doctype html>\n');
  res.write('<title>404 Not Found</title>\n');
  res.write('
Not Found
');
  res.write(
    '
The requested URL ' +
    escapeHtml(path) +
    ' was not found on this server.
'
  );
  res.end();
  sys.puts('404 Not Found: ' + path);
};

StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
  path = path.substring(1);
  res.writeHead(403, {
      'Content-Type': 'text/html'
  });
  res.write('<!doctype html>\n');
  res.write('<title>403 Forbidden</title>\n');
  res.write('
Forbidden
');
  res.write(
    '
You do not have permission to access ' +
    escapeHtml(path) + ' on this server.
'
  );
  res.end();
  sys.puts('403 Forbidden: ' + path);
};

StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
  res.writeHead(301, {
      'Content-Type': 'text/html',
      'Location': redirectUrl
  });
  res.write('<!doctype html>\n');
  res.write('<title>301 Moved Permanently</title>\n');
  res.write('
Moved Permanently
');
  res.write(
    '
The document has moved here.
'
  );
  res.end();
  sys.puts('401 Moved Permanently: ' + redirectUrl);
};

StaticServlet.prototype.sendFile_ = function(req, res, path) {
  var self = this;
  var file = fs.createReadStream(path);
  res.writeHead(200, {
    'Content-Type': StaticServlet.
      MimeMap[path.split('.').pop()] || 'text/plain'
  });
  if (req.method === 'HEAD') {
    res.end();
  } else {
    file.on('data', res.write.bind(res));
    file.on('close', function() {
      res.end();
    });
    file.on('error', function(error) {
      self.sendError_(req, res, error);
    });
  }
};

StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
  var self = this;
  if (path.match(/[^\/]$/)) {
    req.url.pathname += '/';
    var redirectUrl = url.format(url.parse(url.format(req.url)));
    return self.sendRedirect_(req, res, redirectUrl);
  }
  fs.readdir(path, function(err, files) {
    if (err)
      return self.sendError_(req, res, error);

    if (!files.length)
      return self.writeDirectoryIndex_(req, res, path, []);

    var remaining = files.length;
    files.forEach(function(fileName, index) {
      fs.stat(path + '/' + fileName, function(err, stat) {
        if (err)
          return self.sendError_(req, res, err);
        if (stat.isDirectory()) {
          files[index] = fileName + '/';
        }
        if (!(--remaining))
          return self.writeDirectoryIndex_(req, res, path, files);
      });
    });
  });
};

StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
  path = path.substring(1);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  if (req.method === 'HEAD') {
    res.end();
    return;
  }
  res.write('<!doctype html>\n');
  res.write('<title>' + escapeHtml(path) + '</title>\n');
  res.write('\n');
  res.write('
Directory: ' + escapeHtml(path) + '
');
  res.write('
');
  files.forEach(function(fileName) {
    if (fileName.charAt(0) !== '.') {
      res.write('
' +
        escapeHtml(fileName) + '
');
    }
  });
  res.write('
');
  res.end();
};

// Must be last,
main(process.argv);
<|endoftext|>


<commit_before>'use strict';

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

module.exports = function(files, key, config, schema) {
  merge(config, schema);

  files = files || config[key];
  if (schema.checked[key]) {
    return files;
  }

  files = utils.arrayify(files).filter(Boolean);
  files = utils.union([], files, config.files);
  var opts = utils.merge({}, schema.options);

  var len = files.length;
  var idx = -1;
  var res = Array.isArray(opts.files) ? opts.files : [];

  while (++idx < len) {
    var file = files[idx];
    if (utils.exists(path.resolve(process.cwd(), file))) {
      utils.union(res, utils.stripSlash(file));
    }
  }

  // commond directories
  addPath(res, 'lib', opts, config);
  addPath(res, 'dist', opts, config);
  addPath(res, 'bin', opts, config);

  // common files
  addPath(res, 'README.md', opts, config);
  addPath(res, 'LICENSE', opts, config);
  addPath(res, 'index.js', opts, config);
  addPath(res, 'utils.js', opts, config);
  addPath(res, 'cli.js', opts, config);

  if (/^(generate|verb|assemble|updater)-/.test(config.name)) {
    addPath(res, 'updatefile.js', opts, config);
    addPath(res, 'generator.js', opts, config);
    addPath(res, 'templates', opts, config);
  }

  if (res.length) {
    res.sort();
    config[key] = res;
    schema.update('bin', config);
    schema.checked[key] = true;
    return res;
  }

  // don't use `omit`, so the key can be re-added if needed
  delete config[key];
  return;
};

function addPath(files, file, opts, config) {
  if (Array.isArray(opts.files) && opts.files.indexOf(file) === -1) {
    return;
  }
  if (files.indexOf(file) !== -1) {
    return;
  }
  if (utils.exists(path.resolve(process.cwd(), file))) {
    utils.union(files, file);
  }
}
<commit_msg>remove readme and license from array<commit_after>'use strict';

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

module.exports = function(files, key, config, schema) {
  merge(config, schema);

  files = files || config[key];
  if (schema.checked[key]) {
    return files;
  }

  files = utils.arrayify(files).filter(Boolean);
  files = utils.union([], files, config.files);
  var opts = utils.merge({}, schema.options);

  var len = files.length;
  var idx = -1;
  var res = Array.isArray(opts.files) ? opts.files : [];

  while (++idx < len) {
    var file = files[idx];
    if (utils.exists(path.resolve(process.cwd(), file))) {
      utils.union(res, utils.stripSlash(file));
    }
  }

  // commond directories
  addPath(res, 'lib', opts, config);
  addPath(res, 'dist', opts, config);
  addPath(res, 'bin', opts, config);

  // common files
  addPath(res, 'index.js', opts, config);
  addPath(res, 'utils.js', opts, config);
  addPath(res, 'cli.js', opts, config);

  if (/^(generate|verb|assemble|updater)-/.test(config.name)) {
    addPath(res, 'updatefile.js', opts, config);
    addPath(res, 'generator.js', opts, config);
    addPath(res, 'templates', opts, config);
  }

  if (res.length) {
    res.sort();
    config[key] = res;
    schema.update('bin', config);
    schema.checked[key] = true;
    return res;
  }

  // don't use `omit`, so the key can be re-added if needed
  delete config[key];
  return;
};

function addPath(files, file, opts, config) {
  if (Array.isArray(opts.files) && opts.files.indexOf(file) === -1) {
    return;
  }
  if (files.indexOf(file) !== -1) {
    return;
  }
  if (utils.exists(path.resolve(process.cwd(), file))) {
    utils.union(files, file);
  }
}
<|endoftext|>


<commit_before>(function(root) {

// Import
// ========

var _,
    errors,
    util,
    Chronicle,
    Compound;

if (typeof exports !== 'undefined') {
  _ = require('underscore');
  errors   = require('../util/errors');
  util   = require('../util/util');
  Chronicle = require('../../chronicle');
  Compound = require('./compound');
} else {
  _ = root._;
  errors = root.Substance.errors;
  util = root.Substance.util;
  Chronicle = root.Substance.Chronicle;
  Compound = Chronicle.ot.Compound;
}

var INS = "+";
var DEL = "-";

var TextOperation = function(options) {

  // if this operation should be created using an array
  if (_.isArray(options)) {
    options = {
      type: options[0],
      pos: options[1],
      str: options[2]
    };
  }

  if (options.type === undefined || options.pos === undefined || options.str === undefined) {
    throw new errors.ChronicleError("Illegal argument: insufficient data.");
  }

  // '+' or '-'
  this.type = options.type;

  // the position where to apply the operation
  this.pos = options.pos;

  // the string to delete or insert
  this.str = options.str;

  // sanity checks
  if(!this.isInsert() && !this.isDelete()) {
    throw new errors.ChronicleError("Illegal type.");
  }
  if (!_.isString(this.str)) {
    throw new errors.ChronicleError("Illegal argument: expecting string.");
  }
  if (!_.isNumber(this.pos) && this.pos < 0) {
    throw new errors.ChronicleError("Illegal argument: expecting positive number as pos.");
  }
};

TextOperation.fromJSON = function(data) {

  if (data.type === Compound.TYPE) {
    var ops = [];
    for (var idx = 0; idx < data.ops.length; idx++) {
      ops.push(TextOperation.fromJSON(data.ops[idx]));
    }
    return TextOperation.Compound(ops);

  } else {
    return new TextOperation(data);
  }
};

TextOperation.__prototype__ = function() {

  this.clone = function() {
    return new TextOperation(this);
  };

  this.isNOP = function() {
    return this.str.length === 0;
  };

  this.isInsert = function() {
    return this.type === INS;
  };

  this.isDelete = function() {
    return this.type === DEL;
  };

  this.length = function() {
    return this.str.length;
  };

  this.apply = function(str) {
    if (this.isEmpty()) return str;

    if (this.isInsert()) {
      if (str.length < this.pos) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      return str.slice(0, this.pos) + this.str + str.slice(this.pos);
    }

    else {
      if (str.length < this.pos + this.str.length) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      var del = str.slice(this.pos, this.pos + this.str.length);

      if (del !== this.str) {
        throw new errors.ChronicleError("Provided string is incompatible: expected " + this.str + " at position " + this.pos + ", found " + del);
      }

      return str.slice(0, this.pos) + str.slice(this.pos + this.length());
    }
  };

  this.invert = function() {
    var data = {
      type: this.isInsert() ? '-' : '+',
      pos: this.pos,
      str: this.str
    };
    return new TextOperation(data);
  };

  this.hasConflict = function(other) {
    return TextOperation.hasConflict(this, other);
  };

  this.isEmpty = function() {
    return this.str.length === 0;
  };

  this.toJSON = function() {
    return {
      type: this.type,
      pos: this.pos,
      str: this.str
    };
  };

};
TextOperation.prototype = new TextOperation.__prototype__();

var hasConflict = function(a, b) {

  // Insert vs Insert:
  //
  // Insertions are conflicting iff their insert position is the same.

  if (a.type === INS && b.type === INS)  return (a.pos === b.pos);

  // Delete vs Delete:
  //
  // Deletions are conflicting if their ranges overlap.

  if (a.type === DEL && b.type === DEL) {
    // to have no conflict, either `a` should be after `b` or `b` after `a`, otherwise.
    return !(a.pos >= b.pos + b.str.length || b.pos >= a.pos + a.str.length);
  }

  // Delete vs Insert:
  //
  // A deletion and an insertion are conflicting if the insert position is within the deleted range.

  var del, ins;
  if (a.type === DEL) {
    del = a; ins = b;
  } else {
    del = b; ins = a;
  }

  return (ins.pos >= del.pos && ins.pos < del.pos + del.str.length);
};

// Transforms two Insertions
// --------

function transform_insert_insert(a, b, first) {

  if (a.pos === b.pos) {
    if (first) {
      b.pos += a.str.length;
    } else {
      a.pos += b.str.length;
    }
  }

  else if (a.pos < b.pos) {
    b.pos += a.str.length;
  }

  else {
    a.pos += b.str.length;
  }

}

// Transform two Deletions
// --------
//

function transform_delete_delete(a, b, first) {

  // reduce to a normalized case
  if (a.pos > b.pos) {
    return transform_delete_delete(b, a, !first);
  }

  if (a.pos === b.pos && a.str.length > b.str.length) {
    return transform_delete_delete(b, a, !first);
  }

  // take out overlapping parts
  if (b.pos < a.pos + a.str.length) {
    var s = b.pos - a.pos;
    var s1 = a.str.length - s;
    var s2 = s + b.str.length;

    a.str = a.str.slice(0, s) + a.str.slice(s2);
    b.str = b.str.slice(s1);
    b.pos -= s;
  } else {
    b.pos -= a.str.length;
  }

}

// Transform Insert and Deletion
// --------
//

function transform_insert_delete(a, b) {

  if (a.type === DEL) {
    return transform_insert_delete(b, a);
  }

  // we can assume, that a is an insertion and b is a deletion

  // a is before b
  if (a.pos <= b.pos) {
    b.pos += a.str.length;
  }

  // a is after b
  else if (a.pos >= b.pos + b.str.length) {
    a.pos -= b.str.length;
  }

  // Note: this is a conflict case the user should be noticed about
  // If applied still, the deletion takes precedence
  // a.pos > b.pos && <= b.pos + b.length()
  else {
    var s = a.pos - b.pos;
    b.str = b.str.slice(0, s) + a.str + b.str.slice(s);
    a.str = "";
  }

}

var transform0 = function(a, b, options) {

  options = options || {};

  if (options.check && hasConflict(a, b)) {
    throw Chronicle.mergeConflict(a, b);
  }

  if (!options.inplace) {
    a = util.clone(a);
    b = util.clone(b);
  }

  if (a.type === INS && b.type === INS)  {
    transform_insert_insert(a, b, true);
  }
  else if (a.type === DEL && b.type === DEL) {
    transform_delete_delete(a, b, true);
  }
  else {
    transform_insert_delete(a,b);
  }

  return [a, b];
};

var __apply__ = function(op, array) {
  if (_.isArray(op)) {
    op = new TextOperation(op);
  }
  else if (!(op instanceof TextOperation)) {
    op = TextOperation.fromJSON(op);
  }
  return op.apply(array);
};

TextOperation.transform = Compound.createTransform(transform0);
TextOperation.apply = __apply__;

TextOperation.Insert = function(pos, str) {
  return new TextOperation(["+", pos, str]);
};

TextOperation.Delete = function(pos, str) {
  return new TextOperation(["-", pos, str]);
};

TextOperation.Compound = function(ops) {
  // do not create a Compound if not necessary
  if (ops.length === 1) return ops[0];
  else return new Compound(ops);
};

// Converts from a given a sequnence in the format of Tim's lib
// which is an array of numbers and strings.
// 1. positive number: retain a number of characters
// 2. negative number: delete a string with the given length at the current position
// 3. string: insert the given string at the current position

TextOperation.fromOT = function(str, ops) {

  var atomicOps = []; // atomic ops

  // iterating through the sequence and bookkeeping the position
  // in the source and destination str
  var srcPos = 0,
      dstPos = 0;

  if (!_.isArray(ops)) {
    ops = arguments.slice(1);
  }

  _.each(ops, function(op) {
    if (_.isString(op)) { // insert chars
      atomicOps.push(TextOperation.Insert(dstPos, op));
      dstPos += op.length;
    } else if (op<0) { // delete n chars
      var n = -op;
      atomicOps.push(TextOperation.Delete(dstPos, str.slice(srcPos, srcPos+n)));
      srcPos += n;
    } else { // skip n chars
      srcPos += op;
      dstPos += op;
    }
  });

  return TextOperation.Compound(atomicOps);
};

// A helper class to model Text selections and to provide an easy way
// to bookkeep changes by other applied TextOperations
var Range = function(range) {

  this.start = range.start;
  this.end = range.end;

  // control the behaviour when characters are inserted at the boundary
  // if set to true the range will expand
  // this.expand = range.expand || false;
};

// Transforms a given range tuple (offset, length) in-place.
// --------
//

var range_transform = function(range, textOp, expand) {

  // handle compound operations
  if (textOp.type === Compound.TYPE) {
    for (var idx = 0; idx < textOp.ops.length; idx++) {
      var op = textOp.ops[idx];
      range_transform(range, op);
    }
    return;
  }

  var start = range.start;
  var end = start + range.length;

  // Delete
  if (textOp.type === DEL) {
    var pos1 = textOp.pos;
    var pos2 = textOp.pos+textOp.str.length;

    if (pos1 <= start) {
      start -= Math.min(pos2-pos1, start-pos1);
    }
    if (pos1 <= end) {
      end -= Math.min(pos2-pos1, end-pos1);
    }

  } else if (textOp.type === INS) {
    var pos = textOp.pos;
    var l = textOp.str.length;

    if ( (pos < start) ||
         (pos === start && !expand) ) {
      start += l;
    }

    if ( (pos < end) ||
         (pos === end && expand) ) {
      end += l;
    }
  }

  range.start = start;
  range.length = end - start;
};

Range.__prototype__ = function() {

  this.clone = function() {
    return new Range(this);
  };

  this.toJSON = function() {
    var result = {
      start: this.start,
      length: this.length
    };
    // if (this.expand) result.expand = true;
    return result;
  };

  this.transform = function(textOp, expand) {
    range_transform(this.range, textOp, expand);
  };

};
Range.prototype = new Range.__prototype__();

Range.transform = function(range, op, expand) {
  range_transform(range, op, expand);
};

Range.fromJSON = function(data) {
  return new Range(data);
};

TextOperation.Range = Range;

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = TextOperation;
} else {
  Chronicle.ot = Chronicle.ot || {};
  Chronicle.ot.TextOperation = TextOperation;
}

})(this);
<commit_msg>Smarter Range constructor.<commit_after>(function(root) {

// Import
// ========

var _,
    errors,
    util,
    Chronicle,
    Compound;

if (typeof exports !== 'undefined') {
  _ = require('underscore');
  errors   = require('../util/errors');
  util   = require('../util/util');
  Chronicle = require('../../chronicle');
  Compound = require('./compound');
} else {
  _ = root._;
  errors = root.Substance.errors;
  util = root.Substance.util;
  Chronicle = root.Substance.Chronicle;
  Compound = Chronicle.ot.Compound;
}

var INS = "+";
var DEL = "-";

var TextOperation = function(options) {

  // if this operation should be created using an array
  if (_.isArray(options)) {
    options = {
      type: options[0],
      pos: options[1],
      str: options[2]
    };
  }

  if (options.type === undefined || options.pos === undefined || options.str === undefined) {
    throw new errors.ChronicleError("Illegal argument: insufficient data.");
  }

  // '+' or '-'
  this.type = options.type;

  // the position where to apply the operation
  this.pos = options.pos;

  // the string to delete or insert
  this.str = options.str;

  // sanity checks
  if(!this.isInsert() && !this.isDelete()) {
    throw new errors.ChronicleError("Illegal type.");
  }
  if (!_.isString(this.str)) {
    throw new errors.ChronicleError("Illegal argument: expecting string.");
  }
  if (!_.isNumber(this.pos) && this.pos < 0) {
    throw new errors.ChronicleError("Illegal argument: expecting positive number as pos.");
  }
};

TextOperation.fromJSON = function(data) {

  if (data.type === Compound.TYPE) {
    var ops = [];
    for (var idx = 0; idx < data.ops.length; idx++) {
      ops.push(TextOperation.fromJSON(data.ops[idx]));
    }
    return TextOperation.Compound(ops);

  } else {
    return new TextOperation(data);
  }
};

TextOperation.__prototype__ = function() {

  this.clone = function() {
    return new TextOperation(this);
  };

  this.isNOP = function() {
    return this.str.length === 0;
  };

  this.isInsert = function() {
    return this.type === INS;
  };

  this.isDelete = function() {
    return this.type === DEL;
  };

  this.length = function() {
    return this.str.length;
  };

  this.apply = function(str) {
    if (this.isEmpty()) return str;

    if (this.isInsert()) {
      if (str.length < this.pos) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      return str.slice(0, this.pos) + this.str + str.slice(this.pos);
    }

    else {
      if (str.length < this.pos + this.str.length) {
        throw new errors.ChronicleError("Provided string is too short.");
      }
      var del = str.slice(this.pos, this.pos + this.str.length);

      if (del !== this.str) {
        throw new errors.ChronicleError("Provided string is incompatible: expected " + this.str + " at position " + this.pos + ", found " + del);
      }

      return str.slice(0, this.pos) + str.slice(this.pos + this.length());
    }
  };

  this.invert = function() {
    var data = {
      type: this.isInsert() ? '-' : '+',
      pos: this.pos,
      str: this.str
    };
    return new TextOperation(data);
  };

  this.hasConflict = function(other) {
    return TextOperation.hasConflict(this, other);
  };

  this.isEmpty = function() {
    return this.str.length === 0;
  };

  this.toJSON = function() {
    return {
      type: this.type,
      pos: this.pos,
      str: this.str
    };
  };

};
TextOperation.prototype = new TextOperation.__prototype__();

var hasConflict = function(a, b) {

  // Insert vs Insert:
  //
  // Insertions are conflicting iff their insert position is the same.

  if (a.type === INS && b.type === INS)  return (a.pos === b.pos);

  // Delete vs Delete:
  //
  // Deletions are conflicting if their ranges overlap.

  if (a.type === DEL && b.type === DEL) {
    // to have no conflict, either `a` should be after `b` or `b` after `a`, otherwise.
    return !(a.pos >= b.pos + b.str.length || b.pos >= a.pos + a.str.length);
  }

  // Delete vs Insert:
  //
  // A deletion and an insertion are conflicting if the insert position is within the deleted range.

  var del, ins;
  if (a.type === DEL) {
    del = a; ins = b;
  } else {
    del = b; ins = a;
  }

  return (ins.pos >= del.pos && ins.pos < del.pos + del.str.length);
};

// Transforms two Insertions
// --------

function transform_insert_insert(a, b, first) {

  if (a.pos === b.pos) {
    if (first) {
      b.pos += a.str.length;
    } else {
      a.pos += b.str.length;
    }
  }

  else if (a.pos < b.pos) {
    b.pos += a.str.length;
  }

  else {
    a.pos += b.str.length;
  }

}

// Transform two Deletions
// --------
//

function transform_delete_delete(a, b, first) {

  // reduce to a normalized case
  if (a.pos > b.pos) {
    return transform_delete_delete(b, a, !first);
  }

  if (a.pos === b.pos && a.str.length > b.str.length) {
    return transform_delete_delete(b, a, !first);
  }

  // take out overlapping parts
  if (b.pos < a.pos + a.str.length) {
    var s = b.pos - a.pos;
    var s1 = a.str.length - s;
    var s2 = s + b.str.length;

    a.str = a.str.slice(0, s) + a.str.slice(s2);
    b.str = b.str.slice(s1);
    b.pos -= s;
  } else {
    b.pos -= a.str.length;
  }

}

// Transform Insert and Deletion
// --------
//

function transform_insert_delete(a, b) {

  if (a.type === DEL) {
    return transform_insert_delete(b, a);
  }

  // we can assume, that a is an insertion and b is a deletion

  // a is before b
  if (a.pos <= b.pos) {
    b.pos += a.str.length;
  }

  // a is after b
  else if (a.pos >= b.pos + b.str.length) {
    a.pos -= b.str.length;
  }

  // Note: this is a conflict case the user should be noticed about
  // If applied still, the deletion takes precedence
  // a.pos > b.pos && <= b.pos + b.length()
  else {
    var s = a.pos - b.pos;
    b.str = b.str.slice(0, s) + a.str + b.str.slice(s);
    a.str = "";
  }

}

var transform0 = function(a, b, options) {

  options = options || {};

  if (options.check && hasConflict(a, b)) {
    throw Chronicle.mergeConflict(a, b);
  }

  if (!options.inplace) {
    a = util.clone(a);
    b = util.clone(b);
  }

  if (a.type === INS && b.type === INS)  {
    transform_insert_insert(a, b, true);
  }
  else if (a.type === DEL && b.type === DEL) {
    transform_delete_delete(a, b, true);
  }
  else {
    transform_insert_delete(a,b);
  }

  return [a, b];
};

var __apply__ = function(op, array) {
  if (_.isArray(op)) {
    op = new TextOperation(op);
  }
  else if (!(op instanceof TextOperation)) {
    op = TextOperation.fromJSON(op);
  }
  return op.apply(array);
};

TextOperation.transform = Compound.createTransform(transform0);
TextOperation.apply = __apply__;

TextOperation.Insert = function(pos, str) {
  return new TextOperation(["+", pos, str]);
};

TextOperation.Delete = function(pos, str) {
  return new TextOperation(["-", pos, str]);
};

TextOperation.Compound = function(ops) {
  // do not create a Compound if not necessary
  if (ops.length === 1) return ops[0];
  else return new Compound(ops);
};

// Converts from a given a sequnence in the format of Tim's lib
// which is an array of numbers and strings.
// 1. positive number: retain a number of characters
// 2. negative number: delete a string with the given length at the current position
// 3. string: insert the given string at the current position

TextOperation.fromOT = function(str, ops) {

  var atomicOps = []; // atomic ops

  // iterating through the sequence and bookkeeping the position
  // in the source and destination str
  var srcPos = 0,
      dstPos = 0;

  if (!_.isArray(ops)) {
    ops = arguments.slice(1);
  }

  _.each(ops, function(op) {
    if (_.isString(op)) { // insert chars
      atomicOps.push(TextOperation.Insert(dstPos, op));
      dstPos += op.length;
    } else if (op<0) { // delete n chars
      var n = -op;
      atomicOps.push(TextOperation.Delete(dstPos, str.slice(srcPos, srcPos+n)));
      srcPos += n;
    } else { // skip n chars
      srcPos += op;
      dstPos += op;
    }
  });

  return TextOperation.Compound(atomicOps);
};

// A helper class to model Text selections and to provide an easy way
// to bookkeep changes by other applied TextOperations
var Range = function(range) {
  if (_.isArray(range)) {
    this.start = range[0];
    this.length = range[1];
  } else {
    this.start = range.start;
    this.length = range.length;
  }
};

// Transforms a given range tuple (offset, length) in-place.
// --------
//

var range_transform = function(range, textOp, expand) {

  // handle compound operations
  if (textOp.type === Compound.TYPE) {
    for (var idx = 0; idx < textOp.ops.length; idx++) {
      var op = textOp.ops[idx];
      range_transform(range, op);
    }
    return;
  }

  var start = range.start;
  var end = start + range.length;

  // Delete
  if (textOp.type === DEL) {
    var pos1 = textOp.pos;
    var pos2 = textOp.pos+textOp.str.length;

    if (pos1 <= start) {
      start -= Math.min(pos2-pos1, start-pos1);
    }
    if (pos1 <= end) {
      end -= Math.min(pos2-pos1, end-pos1);
    }

  } else if (textOp.type === INS) {
    var pos = textOp.pos;
    var l = textOp.str.length;

    if ( (pos < start) ||
         (pos === start && !expand) ) {
      start += l;
    }

    if ( (pos < end) ||
         (pos === end && expand) ) {
      end += l;
    }
  }

  range.start = start;
  range.length = end - start;
};

Range.__prototype__ = function() {

  this.clone = function() {
    return new Range(this);
  };

  this.toJSON = function() {
    var result = {
      start: this.start,
      length: this.length
    };
    // if (this.expand) result.expand = true;
    return result;
  };

  this.transform = function(textOp, expand) {
    range_transform(this.range, textOp, expand);
  };

};
Range.prototype = new Range.__prototype__();

Range.transform = function(range, op, expand) {
  range_transform(range, op, expand);
};

Range.fromJSON = function(data) {
  return new Range(data);
};

TextOperation.Range = Range;

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = TextOperation;
} else {
  Chronicle.ot = Chronicle.ot || {};
  Chronicle.ot.TextOperation = TextOperation;
}

})(this);
<|endoftext|>