<commit_before>
/*!
 * Express - proto
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var connect = require('connect')
  , Router = require('./router')
  , toArray = require('./utils').toArray
  , methods = Router.methods.concat('del', 'all')
  , res = require('./response')
  , union = require('./utils').union
  , url = require('url')
  , utils = connect.utils
  , path = require('path')
  , extname = path.extname
  , join = path.join
  , fs = require('fs')
  , qs = require('qs');

/**
 * Expose `HTTPServer`.
 */

var app = exports = module.exports = {};

/**
 * Initialize the server.
 *
 * @param {Array} middleware
 * @api private
 */

app.init = function(middleware){
  var self = this;
  this.settings = {};
  this.engines = {};
  this.redirects = {};
  this.isCallbacks = {};

  this.set('home', '/');
  this.set('env', process.env.NODE_ENV || 'development');
  this.use(connect.query());

  /**
   * Assign several locals with the given `obj`,
   * or return the locals.
   *
   * @param {Object} obj
   * @return {Object|HTTPServer}
   * @api public
   */

  this.locals = function(obj){
    for (var key in obj) {
      self.locals[key] = obj[key];
    }
    return self;
  };

  // expose objects to each other
  this.use(function(req, res, next){
    var charset;
    req.query = req.query || {};
    res.setHeader('X-Powered-By', 'Express');
    req.app = res.app = self;
    req.res = res;
    res.req = req;
    req.next = next;

    // charset
    if (charset = self.set('charset')) res.charset = charset;

    /**
     * Assign several locals with the given `obj`,
     * or return the locals.
     *
     * @param {Object} obj
     * @return {Object|ServerResponse}
     * @api public
     */

    res.locals = function(obj){
      for (var key in obj) {
        res.locals[key] = obj[key];
      }
      return res;
    };

    next();
  });

  // apply middleware
  if (middleware) middleware.forEach(self.use.bind(self));

  // router
  this.routes = new Router(this);
  this.__defineGetter__('router', function(){
    this.__usedRouter = true;
    return self.routes.middleware;
  });

  // default locals
  this.locals.settings = this.settings;

  // default development configuration
  this.configure('development', function(){
    this.enable('hints');
  });

  // default production configuration
  this.configure('production', function(){
    this.enable('view cache');
  });

  // route manipulation methods
  methods.forEach(function(method){
    self.lookup[method] = function(path){
      return self.routes.lookup(method, path);
    };

    self.match[method] = function(path){
      return self.routes.match(method, path);
    };

    self.remove[method] = function(path){
      return self.routes.lookup(method, path).remove();
    };
  });

  // del -> delete
  self.lookup.del = self.lookup.delete;
  self.match.del = self.match.delete;
  self.remove.del = self.remove.delete;
};

/**
 * Remove routes matching the given `path`.
 *
 * @param {Route} path
 * @return {Boolean}
 * @api public
 */

app.remove = function(path){
  return this.routes.lookup('all', path).remove();
};

/**
 * Lookup routes defined with a path
 * equivalent to `path`.
 *
 * @param {Stirng} path
 * @return {Array}
 * @api public
 */

app.lookup = function(path){
  return this.routes.lookup('all', path);
};

/**
 * Lookup routes matching the given `url`.
 *
 * @param {Stirng} url
 * @return {Array}
 * @api public
 */

app.match = function(url){
  return this.routes.match('all', url);
};

/**
 * Proxy `connect.HTTPServer#use()` to apply settings to
 * mounted applications.
 *
 * @param {String|Function|Server} route
 * @param {Function|Server} middleware
 * @return {Server} for chaining
 * @api public
 */

app.use = function(route, middleware){
  var app, home, handle;

  if ('string' != typeof route) {
    middleware = route, route = '/';
  }

  // express app
  if (middleware.handle && middleware.set) app = middleware;

  // restore .app property on req and res
  if (app) {
    app.route = route;
    middleware = function(req, res, next) {
      var orig = req.app;
      app.handle(req, res, function(err){
        req.app = res.app = orig;
        next(err);
      });
    };
  }

  connect.proto.use.call(this, route, middleware);

  // mounted an app, invoke the hook
  // and adjust some settings
  if (app) {
    base = this.set('basepath') || this.route;
    if ('/' == base) base = '';
    base = base + (app.set('basepath') || app.route);
    app.set('basepath', base);
    app.parent = this;
    if (app.__mounted) app.__mounted.call(app, this);
  }

  return this;
};

/**
 * Assign a callback `fn` which is called
 * when this `Server` is passed to `Server#use()`.
 *
 * Examples:
 *
 *    var app = express.createServer()
 *      , blog = express.createServer();
 *
 *    blog.mounted(function(parent){
 *      // parent is app
 *      // "this" is blog
 *    });
 *
 *    app.use(blog);
 *
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.mounted = function(fn){
  this.__mounted = fn;
  return this;
};

/**
 * Register the given template engine callback `fn`
 * as `ext`. For example we may wish to map ".html"
 * files to ejs rather than using the ".ejs" extension.
 *
 *    app.register('.html', require('ejs').render);
 *
 * or
 *
 *    app.register('html', require('ejs').render);
 *
 * @param {String} ext
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.register = function(ext, fn){
  if ('.' != ext[0]) ext = '.' + ext;
  this.engines[ext] = fn;
  return this;
};

/**
 * Map the given param placeholder `name`(s) to the given callback `fn`.
 * Register the given view helpers `obj`. This method
 * can be called several times to apply additional helpers.
 *
 * @param {Object} obj
 * @return {Server} for chaining
 * @api public
 */

app.locals = function(obj){
  utils.merge(this._locals, obj);
  return this;
};

/**
 * Map the given param placeholder `name`(s) to the given callback(s).
 *
 * Param mapping is used to provide pre-conditions to routes
 * which us normalized placeholders. This callback has the same
 * signature as regular middleware, for example below when ":userId"
 * is used this function will be invoked in an attempt to load the user.
 *
 *      app.param('userId', function(req, res, next, id){
 *        User.find(id, function(err, user){
 *          if (err) {
 *            next(err);
 *          } else if (user) {
 *            req.user = user;
 *            next();
 *          } else {
 *            next(new Error('failed to load user'));
 *          }
 *        });
 *      });
 *
 * Passing a single function allows you to map logic
 * to the values passed to `app.param()`, for example
 * this is useful to provide coercion support in a concise manner.
 *
 * The following example maps regular expressions to param values
 * ensuring that they match, otherwise passing control to the next
 * route:
 *
 *      app.param(function(name, regexp){
 *        if (regexp instanceof RegExp) {
 *          return function(req, res, next, val){
 *            var captures;
 *            if (captures = regexp.exec(String(val))) {
 *              req.params[name] = captures;
 *              next();
 *            } else {
 *              next('route');
 *            }
 *          }
 *        }
 *      });
 *
 * We can now use it as shown below, where "/commit/:commit" expects
 * that the value for ":commit" is at 5 or more digits. The capture
 * groups are then available as `req.params.commit` as we defined
 * in the function above.
 *
 *    app.param('commit', /^\d{5,}$/);
 *
 * For more of this useful functionality take a look
 * at [express-params](http://github.com/visionmedia/express-params).
 *
 * @param {String|Array|Function} name
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.param = function(name, fn){
  var self = this
    , fns = [].slice.call(arguments, 1);

  // array
  if (Array.isArray(name)) {
    name.forEach(function(name){
      fns.forEach(function(fn){
        self.param(name, fn);
      });
    });
  // param logic
  } else if ('function' == typeof name) {
    this.routes.param(name);
  // single
  } else {
    if (':' == name[0]) name = name.substr(1);
    fns.forEach(function(fn){
      self.routes.param(name, fn);
    });
  }

  return this;
};

/**
 * Register the given callback `fn` for the given `type`.
 *
 * @param {String} type
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.is = function(type, fn){
  if (!fn) return this.isCallbacks[type];
  this.isCallbacks[type] = fn;
  return this;
};

/**
 * Assign `setting` to `val`, or return `setting`'s value.
 * Mounted servers inherit their parent server's settings.
 *
 * @param {String} setting
 * @param {String} val
 * @return {Server|Mixed} for chaining, or the setting value
 * @api public
 */

app.set = function(setting, val){
  if (val === undefined) {
    if (this.settings.hasOwnProperty(setting)) {
      return this.settings[setting];
    } else if (this.parent) {
      return this.parent.set(setting);
    }
  } else {
    this.settings[setting] = val;
    return this;
  }
};

/**
 * Check if `setting` is enabled.
 *
 * @param {String} setting
 * @return {Boolean}
 * @api public
 */

app.enabled = function(setting){
  return !!this.set(setting);
};

/**
 * Check if `setting` is disabled.
 *
 * @param {String} setting
 * @return {Boolean}
 * @api public
 */

app.disabled = function(setting){
  return !this.set(setting);
};

/**
 * Enable `setting`.
 *
 * @param {String} setting
 * @return {Server} for chaining
 * @api public
 */

app.enable = function(setting){
  return this.set(setting, true);
};

/**
 * Disable `setting`.
 *
 * @param {String} setting
 * @return {Server} for chaining
 * @api public
 */

app.disable = function(setting){
  return this.set(setting, false);
};

/**
 * Redirect `key` to `url`.
 *
 * @param {String} key
 * @param {String} url
 * @return {Server} for chaining
 * @api public
 */

app.redirect = function(key, url){
  this.redirects[key] = url;
  return this;
};

/**
 * Configure callback for zero or more envs,
 * when no env is specified that callback will
 * be invoked for all environments. Any combination
 * can be used multiple times, in any order desired.
 *
 * Examples:
 *
 *    app.configure(function(){
 *      // executed for all envs
 *    });
 *
 *    app.configure('stage', function(){
 *      // executed staging env
 *    });
 *
 *    app.configure('stage', 'production', function(){
 *      // executed for stage and production
 *    });
 *
 * @param {String} env...
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.configure = function(env, fn){
  var envs = 'all'
    , args = toArray(arguments);
  fn = args.pop();
  if (args.length) envs = args;
  if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
  return this;
};

/**
 * Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
 */

methods.forEach(function(method){
  app[method] = function(path){
    if (1 == arguments.length) return this.routes.lookup(method, path);
    var args = [method].concat(toArray(arguments));
    if (!this.__usedRouter) this.use(this.router);
    return this.routes._route.apply(this.routes, args);
  }
});

/**
 * Special-cased "all" method, applying the given route `path`,
 * middleware, and callback to _every_ HTTP method.
 *
 * @param {String} path
 * @param {Function} ...
 * @return {Server} for chaining
 * @api public
 */

app.all = function(path){
  var args = arguments;
  if (1 == args.length) return this.routes.lookup('all', path);
  methods.forEach(function(method){
    if ('all' == method) return;
    app[method].apply(this, args);
  }, this);
  return this;
};

// del -> delete alias

app.del = app.delete;

/**
 * Render the given `view` name with `opts`
 * and a callback accepting an error and the
 * rendered template string.
 *
 * @param {String} view
 * @param {String|Function} opts or fn
 * @param {Function} fn
 * @api public
 */

app.render = function(view, opts, fn){
  var self = this
    , options = {}
    , engines = this.engines
    , root = this.set('views') || process.cwd() + '/views';

  // support callback function as second arg
  if ('function' == typeof opts) {
    fn = opts, opts = null;
  }

  // merge app.locals
  union(options, this.locals);

  // merge render() options
  if (opts) utils.merge(options, opts);

  // join "view engine" if necessary
  var ext = extname(view);
  if (!ext) view += '.' + (ext = this.set('view engine'));

  // pass .cache to the engine
  options.cache = this.enabled('view cache');

  // when no extension nor "view engine" is given
  if (!ext) return fn(new Error('failed to find view "' + view + '"'));

  // render
  try {
    var engine = engines[ext] = engines[ext] || require(ext);
    options.filename = join(root, view);
    view = fs.readFileSync(options.filename, 'utf8');
    engine.render(view, options, fn);
  } catch (err) {
    fn(err);
  }
};
<commit_msg>misc refactoring<commit_after>
/*!
 * Express - proto
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var connect = require('connect')
  , Router = require('./router')
  , toArray = require('./utils').toArray
  , methods = Router.methods.concat('del', 'all')
  , res = require('./response')
  , union = require('./utils').union
  , url = require('url')
  , utils = connect.utils
  , path = require('path')
  , extname = path.extname
  , join = path.join
  , fs = require('fs')
  , qs = require('qs');

/**
 * Expose `HTTPServer`.
 */

var app = exports = module.exports = {};

/**
 * Initialize the server.
 *
 * @param {Array} middleware
 * @api private
 */

app.init = function(middleware){
  var self = this;
  this.settings = {};
  this.engines = {};
  this.redirects = {};
  this.isCallbacks = {};

  this.set('home', '/');
  this.set('env', process.env.NODE_ENV || 'development');
  this.use(connect.query());

  /**
   * Assign several locals with the given `obj`,
   * or return the locals.
   *
   * @param {Object} obj
   * @return {Object|HTTPServer}
   * @api public
   */

  this.locals = function(obj){
    for (var key in obj) {
      self.locals[key] = obj[key];
    }
    return self;
  };

  // expose objects to each other
  this.use(function(req, res, next){
    var charset;
    req.query = req.query || {};
    res.setHeader('X-Powered-By', 'Express');
    req.app = res.app = self;
    req.res = res;
    res.req = req;
    req.next = next;

    // charset
    if (charset = self.set('charset')) res.charset = charset;

    /**
     * Assign several locals with the given `obj`,
     * or return the locals.
     *
     * @param {Object} obj
     * @return {Object|ServerResponse}
     * @api public
     */

    res.locals = function(obj){
      for (var key in obj) {
        res.locals[key] = obj[key];
      }
      return res;
    };

    next();
  });

  // apply middleware
  if (middleware) middleware.forEach(self.use.bind(self));

  // router
  this.routes = new Router(this);
  this.__defineGetter__('router', function(){
    this.__usedRouter = true;
    return self.routes.middleware;
  });

  // default locals
  this.locals.settings = this.settings;

  // default development configuration
  this.configure('development', function(){
    this.enable('hints');
  });

  // default production configuration
  this.configure('production', function(){
    this.enable('view cache');
  });

  // route manipulation methods
  methods.forEach(function(method){
    self.lookup[method] = function(path){
      return self.routes.lookup(method, path);
    };

    self.match[method] = function(path){
      return self.routes.match(method, path);
    };

    self.remove[method] = function(path){
      return self.routes.lookup(method, path).remove();
    };
  });

  // del -> delete
  self.lookup.del = self.lookup.delete;
  self.match.del = self.match.delete;
  self.remove.del = self.remove.delete;
};

/**
 * Remove routes matching the given `path`.
 *
 * @param {Route} path
 * @return {Boolean}
 * @api public
 */

app.remove = function(path){
  return this.routes.lookup('all', path).remove();
};

/**
 * Lookup routes defined with a path
 * equivalent to `path`.
 *
 * @param {Stirng} path
 * @return {Array}
 * @api public
 */

app.lookup = function(path){
  return this.routes.lookup('all', path);
};

/**
 * Lookup routes matching the given `url`.
 *
 * @param {Stirng} url
 * @return {Array}
 * @api public
 */

app.match = function(url){
  return this.routes.match('all', url);
};

/**
 * Proxy `connect.HTTPServer#use()` to apply settings to
 * mounted applications.
 *
 * @param {String|Function|Server} route
 * @param {Function|Server} middleware
 * @return {Server} for chaining
 * @api public
 */

app.use = function(route, middleware){
  var app, home, handle;

  if ('string' != typeof route) {
    middleware = route, route = '/';
  }

  // express app
  if (middleware.handle && middleware.set) app = middleware;

  // restore .app property on req and res
  if (app) {
    app.route = route;
    middleware = function(req, res, next) {
      var orig = req.app;
      app.handle(req, res, function(err){
        req.app = res.app = orig;
        next(err);
      });
    };
  }

  connect.proto.use.call(this, route, middleware);

  // mounted an app, invoke the hook
  // and adjust some settings
  if (app) {
    base = this.set('basepath') || this.route;
    if ('/' == base) base = '';
    base = base + (app.set('basepath') || app.route);
    app.set('basepath', base);
    app.parent = this;
    if (app.__mounted) app.__mounted.call(app, this);
  }

  return this;
};

/**
 * Assign a callback `fn` which is called
 * when this `Server` is passed to `Server#use()`.
 *
 * Examples:
 *
 *    var app = express.createServer()
 *      , blog = express.createServer();
 *
 *    blog.mounted(function(parent){
 *      // parent is app
 *      // "this" is blog
 *    });
 *
 *    app.use(blog);
 *
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.mounted = function(fn){
  this.__mounted = fn;
  return this;
};

/**
 * Register the given template engine callback `fn`
 * as `ext`. For example we may wish to map ".html"
 * files to ejs rather than using the ".ejs" extension.
 *
 *    app.register('.html', require('ejs').render);
 *
 * or
 *
 *    app.register('html', require('ejs').render);
 *
 * @param {String} ext
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.register = function(ext, fn){
  if ('.' != ext[0]) ext = '.' + ext;
  this.engines[ext] = fn;
  return this;
};

/**
 * Map the given param placeholder `name`(s) to the given callback `fn`.
 * Register the given view helpers `obj`. This method
 * can be called several times to apply additional helpers.
 *
 * @param {Object} obj
 * @return {Server} for chaining
 * @api public
 */

app.locals = function(obj){
  utils.merge(this._locals, obj);
  return this;
};

/**
 * Map the given param placeholder `name`(s) to the given callback(s).
 *
 * Param mapping is used to provide pre-conditions to routes
 * which us normalized placeholders. This callback has the same
 * signature as regular middleware, for example below when ":userId"
 * is used this function will be invoked in an attempt to load the user.
 *
 *      app.param('userId', function(req, res, next, id){
 *        User.find(id, function(err, user){
 *          if (err) {
 *            next(err);
 *          } else if (user) {
 *            req.user = user;
 *            next();
 *          } else {
 *            next(new Error('failed to load user'));
 *          }
 *        });
 *      });
 *
 * Passing a single function allows you to map logic
 * to the values passed to `app.param()`, for example
 * this is useful to provide coercion support in a concise manner.
 *
 * The following example maps regular expressions to param values
 * ensuring that they match, otherwise passing control to the next
 * route:
 *
 *      app.param(function(name, regexp){
 *        if (regexp instanceof RegExp) {
 *          return function(req, res, next, val){
 *            var captures;
 *            if (captures = regexp.exec(String(val))) {
 *              req.params[name] = captures;
 *              next();
 *            } else {
 *              next('route');
 *            }
 *          }
 *        }
 *      });
 *
 * We can now use it as shown below, where "/commit/:commit" expects
 * that the value for ":commit" is at 5 or more digits. The capture
 * groups are then available as `req.params.commit` as we defined
 * in the function above.
 *
 *    app.param('commit', /^\d{5,}$/);
 *
 * For more of this useful functionality take a look
 * at [express-params](http://github.com/visionmedia/express-params).
 *
 * @param {String|Array|Function} name
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.param = function(name, fn){
  var self = this
    , fns = [].slice.call(arguments, 1);

  // array
  if (Array.isArray(name)) {
    name.forEach(function(name){
      fns.forEach(function(fn){
        self.param(name, fn);
      });
    });
  // param logic
  } else if ('function' == typeof name) {
    this.routes.param(name);
  // single
  } else {
    if (':' == name[0]) name = name.substr(1);
    fns.forEach(function(fn){
      self.routes.param(name, fn);
    });
  }

  return this;
};

/**
 * Register the given callback `fn` for the given `type`.
 *
 * @param {String} type
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.is = function(type, fn){
  if (!fn) return this.isCallbacks[type];
  this.isCallbacks[type] = fn;
  return this;
};

/**
 * Assign `setting` to `val`, or return `setting`'s value.
 * Mounted servers inherit their parent server's settings.
 *
 * @param {String} setting
 * @param {String} val
 * @return {Server|Mixed} for chaining, or the setting value
 * @api public
 */

app.set = function(setting, val){
  if (val === undefined) {
    if (this.settings.hasOwnProperty(setting)) {
      return this.settings[setting];
    } else if (this.parent) {
      return this.parent.set(setting);
    }
  } else {
    this.settings[setting] = val;
    return this;
  }
};

/**
 * Check if `setting` is enabled.
 *
 * @param {String} setting
 * @return {Boolean}
 * @api public
 */

app.enabled = function(setting){
  return !!this.set(setting);
};

/**
 * Check if `setting` is disabled.
 *
 * @param {String} setting
 * @return {Boolean}
 * @api public
 */

app.disabled = function(setting){
  return !this.set(setting);
};

/**
 * Enable `setting`.
 *
 * @param {String} setting
 * @return {Server} for chaining
 * @api public
 */

app.enable = function(setting){
  return this.set(setting, true);
};

/**
 * Disable `setting`.
 *
 * @param {String} setting
 * @return {Server} for chaining
 * @api public
 */

app.disable = function(setting){
  return this.set(setting, false);
};

/**
 * Redirect `key` to `url`.
 *
 * @param {String} key
 * @param {String} url
 * @return {Server} for chaining
 * @api public
 */

app.redirect = function(key, url){
  this.redirects[key] = url;
  return this;
};

/**
 * Configure callback for zero or more envs,
 * when no env is specified that callback will
 * be invoked for all environments. Any combination
 * can be used multiple times, in any order desired.
 *
 * Examples:
 *
 *    app.configure(function(){
 *      // executed for all envs
 *    });
 *
 *    app.configure('stage', function(){
 *      // executed staging env
 *    });
 *
 *    app.configure('stage', 'production', function(){
 *      // executed for stage and production
 *    });
 *
 * @param {String} env...
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */

app.configure = function(env, fn){
  var envs = 'all'
    , args = toArray(arguments);
  fn = args.pop();
  if (args.length) envs = args;
  if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
  return this;
};

/**
 * Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
 */

methods.forEach(function(method){
  app[method] = function(path){
    if (1 == arguments.length) return this.routes.lookup(method, path);
    var args = [method].concat(toArray(arguments));
    if (!this.__usedRouter) this.use(this.router);
    return this.routes._route.apply(this.routes, args);
  }
});

/**
 * Special-cased "all" method, applying the given route `path`,
 * middleware, and callback to _every_ HTTP method.
 *
 * @param {String} path
 * @param {Function} ...
 * @return {Server} for chaining
 * @api public
 */

app.all = function(path){
  var args = arguments;
  if (1 == args.length) return this.routes.lookup('all', path);
  methods.forEach(function(method){
    if ('all' == method) return;
    app[method].apply(this, args);
  }, this);
  return this;
};

// del -> delete alias

app.del = app.delete;

/**
 * Render the given `view` name with `options`
 * and a callback accepting an error and the
 * rendered template string.
 *
 * @param {String} view
 * @param {String|Function} options or fn
 * @param {Function} fn
 * @api public
 */

app.render = function(view, options, fn){
  var self = this
    , engines = this.engines
    , root = this.set('views') || process.cwd() + '/views';

  // support callback function as second arg
  if ('function' == typeof options) {
    fn = options, options = {};
  }

  // merge app.locals
  union(options, this.locals);

  // join "view engine" if necessary
  var ext = extname(view);
  if (!ext) view += '.' + (ext = this.set('view engine'));

  // pass .cache to the engine
  options.cache = this.enabled('view cache');

  // when no extension nor "view engine" is given
  if (!ext) return fn(new Error('failed to find view "' + view + '"'));

  // render
  try {
    var engine = engines[ext] = engines[ext] || require(ext);
    options.filename = join(root, view);
    view = fs.readFileSync(options.filename, 'utf8');
    engine.render(view, options, fn);
  } catch (err) {
    fn(err);
  }
};
<|endoftext|>


<commit_before>import { createElement as h, Component, options } from '../../src';
import { assign } from '../../src/util';
import { clearLog, getLog } from './logCall';

/** @jsx h */

export function supportsPassiveEvents() {
	let supported = false;
	try {
		let options = {
			get passive() {
				supported = true;
				return undefined;
			}
		};

		window.addEventListener('test', options, options);
		window.removeEventListener('test', options, options);
	}
	catch (err) {
		supported = false;
	}
	return supported;
}

export function supportsDataList() {
	return 'list' in document.createElement('input') &&
		Boolean(document.createElement('datalist') &&
		'HTMLDataListElement' in window);
}

const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;

function encodeEntities(str) {
	return str.replace(/&/g, '&');
}

/**
 * Normalize svg paths spacing. Some browsers insert spaces around letters,
 * others do not.
 * @param {string} str
 * @returns {string}
 */
function normalizePath(str) {
	let len = str.length;
	let out = '';
	for (let i = 0; i < len; i++) {
		const char = str[i];
		if (/[A-Za-z]/.test(char)) {
			if (i==0) out+= char + ' ';
			else out+= (str[i-1]==' ' ? '' : ' ') + char + (i < len -1 ? ' ' : '');
		}
		else if (char=='-' && str[i-1]!==' ') out+= ' ' + char;
		else out += char;
	}

	return out.replace(/\s\s+/g, ' ').replace(/z/g, 'Z');
}

export function serializeHtml(node) {
	let str = '';
	let child = node.firstChild;
	while (child) {
		str += serializeDomTree(child);
		child = child.nextSibling;
	}
	return str;
}

/**
 * Serialize a DOM tree.
 * Uses deterministic sorting where necessary to ensure consistent tests.
 * @param {Element|Node} node	The root node to serialize
 * @returns {string} html
 */
function serializeDomTree(node) {
	if (node.nodeType === 3) {
		return encodeEntities(node.data);
	}
	else if (node.nodeType === 8) {
		return '';
	}
	else if (node.nodeType === 1 || node.nodeType === 9) {
		let str = '<' + node.localName;
		const attrs = [];
		for (let i=0; i<node.attributes.length; i++) {
			attrs.push(node.attributes[i].name);
		}
		attrs.sort();
		for (let i=0; i<attrs.length; i++) {
			const name = attrs[i];
			let value = node.getAttribute(name);
			if (value == null) continue;
			if (!value && name==='class') continue;
			str += ' ' + name;
			value = encodeEntities(value);

			// normalize svg <path d="value">
			if (node.localName==='path' && name==='d') {
				value = normalizePath(value);
			}
			str += '="' + value + '"';
		}
		str += '>';
		if (!VOID_ELEMENTS.test(node.localName)) {
			let child = node.firstChild;
			while (child) {
				str += serializeDomTree(child);
				child = child.nextSibling;
			}
			str += '</' + node.localName + '>';
		}
		return str;
	}
}

/**
 * Normalize event creation in browsers
 * @param {string} name
 * @returns {Event}
 */
export function createEvent(name) {
	// Modern browsers
	if (typeof Event==='function') {
		return new Event(name);
	}

	// IE 11...
	let event = document.createEvent('Event');
	event.initEvent(name, true, true);
	return event;
}

/**
 * Sort a cssText alphabetically.
 * @param {string} cssText
 */
export function sortCss(cssText) {
	return cssText.split(';')
		.filter(Boolean)
		.map(s => s.replace(/^\s+|\s+$/g, '').replace(/(\s*:\s*)/g, ': '))
		.sort((a, b) => {
			// CSS Variables are typically positioned at the start
			if (a[0]==='-') {
				// If both are a variable we just compare them
				if (b[0]==='-') return a.localeCompare(b);
				return -1;
			}
			// b is a css var
			if (b[0]==='-') return 1;

			return a.localeCompare(b);
		})
		.join('; ') + ';';
}

/**
 * Setup the test environment
 * @returns {HTMLDivElement}
 */
export function setupScratch() {
	const scratch = document.createElement('div');
	scratch.id = 'scratch';
	(document.body || document.documentElement).appendChild(scratch);
	return scratch;
}

let oldOptions = null;
export function clearOptions() {
	oldOptions = assign({}, options);
	delete options.vnode;
	delete options.diff;
	delete options.diffed;
	delete options.commit;
	delete options.unmount;
}

/**
 * Teardown test environment and reset preact's internal state
 * @param {HTMLDivElement} scratch
 */
export function teardown(scratch) {
	if (scratch) {
		scratch.parentNode.removeChild(scratch);
	}

	if (oldOptions != null) {
		assign(options, oldOptions);
		oldOptions = null;
	}

	if (Component.__test__drainQueue) {
		// Flush any pending updates leftover by test
		Component.__test__drainQueue();
		delete Component.__test__drainQueue;
	}

	if (typeof Component.__test__previousDebounce !== 'undefined') {
		options.debounceRendering = Component.__test__previousDebounce;
		delete Component.__test__previousDebounce;
	}

	if (getLog().length > 0) {
		clearLog();
	}
}

const Foo = () => 'd';
export const getMixedArray = () => (
	// Make it a function so each test gets a new copy of the array
	[0, 'a', 'b', c, <Foo />, null, undefined, false, ['e', 'f'], 1]
);
export const mixedArrayHTML = '0abcdef1';

/**
 * Reset obj to empty to keep reference
 * @param {object} obj
 */
export function clear(obj) {
	Object.keys(obj).forEach(key => delete obj[key]);
}

/**
 * Hacky normalization of attribute order across browsers.
 * @param {string} html
 */
export function sortAttributes(html) {
	return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => {
		let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 );
		if (~after.indexOf('/')) after = '></'+pre+'>';
		return '<' + pre + list.join('') + after;
	});
}
<commit_msg>Fix core test teardown helper<commit_after>import { createElement as h, options } from '../../src';
import { assign } from '../../src/util';
import { clearLog, getLog } from './logCall';
import { teardown as testUtilTeardown } from 'preact/test-utils';

/** @jsx h */

export function supportsPassiveEvents() {
	let supported = false;
	try {
		let options = {
			get passive() {
				supported = true;
				return undefined;
			}
		};

		window.addEventListener('test', options, options);
		window.removeEventListener('test', options, options);
	}
	catch (err) {
		supported = false;
	}
	return supported;
}

export function supportsDataList() {
	return 'list' in document.createElement('input') &&
		Boolean(document.createElement('datalist') &&
		'HTMLDataListElement' in window);
}

const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;

function encodeEntities(str) {
	return str.replace(/&/g, '&');
}

/**
 * Normalize svg paths spacing. Some browsers insert spaces around letters,
 * others do not.
 * @param {string} str
 * @returns {string}
 */
function normalizePath(str) {
	let len = str.length;
	let out = '';
	for (let i = 0; i < len; i++) {
		const char = str[i];
		if (/[A-Za-z]/.test(char)) {
			if (i==0) out+= char + ' ';
			else out+= (str[i-1]==' ' ? '' : ' ') + char + (i < len -1 ? ' ' : '');
		}
		else if (char=='-' && str[i-1]!==' ') out+= ' ' + char;
		else out += char;
	}

	return out.replace(/\s\s+/g, ' ').replace(/z/g, 'Z');
}

export function serializeHtml(node) {
	let str = '';
	let child = node.firstChild;
	while (child) {
		str += serializeDomTree(child);
		child = child.nextSibling;
	}
	return str;
}

/**
 * Serialize a DOM tree.
 * Uses deterministic sorting where necessary to ensure consistent tests.
 * @param {Element|Node} node	The root node to serialize
 * @returns {string} html
 */
function serializeDomTree(node) {
	if (node.nodeType === 3) {
		return encodeEntities(node.data);
	}
	else if (node.nodeType === 8) {
		return '';
	}
	else if (node.nodeType === 1 || node.nodeType === 9) {
		let str = '<' + node.localName;
		const attrs = [];
		for (let i=0; i<node.attributes.length; i++) {
			attrs.push(node.attributes[i].name);
		}
		attrs.sort();
		for (let i=0; i<attrs.length; i++) {
			const name = attrs[i];
			let value = node.getAttribute(name);
			if (value == null) continue;
			if (!value && name==='class') continue;
			str += ' ' + name;
			value = encodeEntities(value);

			// normalize svg <path d="value">
			if (node.localName==='path' && name==='d') {
				value = normalizePath(value);
			}
			str += '="' + value + '"';
		}
		str += '>';
		if (!VOID_ELEMENTS.test(node.localName)) {
			let child = node.firstChild;
			while (child) {
				str += serializeDomTree(child);
				child = child.nextSibling;
			}
			str += '</' + node.localName + '>';
		}
		return str;
	}
}

/**
 * Normalize event creation in browsers
 * @param {string} name
 * @returns {Event}
 */
export function createEvent(name) {
	// Modern browsers
	if (typeof Event==='function') {
		return new Event(name);
	}

	// IE 11...
	let event = document.createEvent('Event');
	event.initEvent(name, true, true);
	return event;
}

/**
 * Sort a cssText alphabetically.
 * @param {string} cssText
 */
export function sortCss(cssText) {
	return cssText.split(';')
		.filter(Boolean)
		.map(s => s.replace(/^\s+|\s+$/g, '').replace(/(\s*:\s*)/g, ': '))
		.sort((a, b) => {
			// CSS Variables are typically positioned at the start
			if (a[0]==='-') {
				// If both are a variable we just compare them
				if (b[0]==='-') return a.localeCompare(b);
				return -1;
			}
			// b is a css var
			if (b[0]==='-') return 1;

			return a.localeCompare(b);
		})
		.join('; ') + ';';
}

/**
 * Setup the test environment
 * @returns {HTMLDivElement}
 */
export function setupScratch() {
	const scratch = document.createElement('div');
	scratch.id = 'scratch';
	(document.body || document.documentElement).appendChild(scratch);
	return scratch;
}

let oldOptions = null;
export function clearOptions() {
	oldOptions = assign({}, options);
	delete options.vnode;
	delete options.diff;
	delete options.diffed;
	delete options.commit;
	delete options.unmount;
}

/**
 * Teardown test environment and reset preact's internal state
 * @param {HTMLDivElement} scratch
 */
export function teardown(scratch) {
	if (scratch) {
		scratch.parentNode.removeChild(scratch);
	}

	if (oldOptions != null) {
		assign(options, oldOptions);
		oldOptions = null;
	}

	testUtilTeardown();

	if (getLog().length > 0) {
		clearLog();
	}
}

const Foo = () => 'd';
export const getMixedArray = () => (
	// Make it a function so each test gets a new copy of the array
	[0, 'a', 'b', c, <Foo />, null, undefined, false, ['e', 'f'], 1]
);
export const mixedArrayHTML = '0abcdef1';

/**
 * Reset obj to empty to keep reference
 * @param {object} obj
 */
export function clear(obj) {
	Object.keys(obj).forEach(key => delete obj[key]);
}

/**
 * Hacky normalization of attribute order across browsers.
 * @param {string} html
 */
export function sortAttributes(html) {
	return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => {
		let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 );
		if (~after.indexOf('/')) after = '></'+pre+'>';
		return '<' + pre + list.join('') + after;
	});
}
<|endoftext|>


<commit_before>/**
 * sorTable widget
 * adds headers to table which allow sorting
 * @version 0.4.5 2012-03-22
 * @author Gregor Kofler
 *
 * @param {Object} table table or tbody (when several tbodies in one table) element
 * @param {Array} columnFormat optional array describing column format, core.js String.toDateTime()
 *
 * served events: "beginSort", "finishSort", "dragStart", "dragStop"
 *
 * @todo provide optional column to sort upon startup
 *
 * @todo manual sort interferes with addRow(), removeRow()
 */
vxJS.widget.sorTable = function(table, config) {
	if(!config) {
		config = {};
	}

	var columnFormat = config.columnFormat && config.columnFormat.length ? config.columnFormat : [];

	var	th, tb, rows = [], cols = [], w, activeColumn, origSort = [], that = {},
		draggedRow, ind = {}, mouseUpId, mouseMoveId, clickListenerId;

	var removeIndicator = function() {
		if(ind.above) {
			vxJS.dom.removeClassName(ind.above, "insertAbove");
			ind.above = null;
		}
		else if(ind.below) {
			vxJS.dom.removeClassName(ind.below, "insertBelow");
			ind.below = null;
		}
	};

	var hiliteColumn = function(col) {
		var i = rows.length;

		if (col) {
			vxJS.dom.removeClassName(col.elem, "vxJS_sorTable_header_" + (col.asc ? "desc" : "asc"));
			vxJS.dom.addClassName(col.elem, "vxJS_sorTable_header_" + (col.asc ? "asc" : "desc"));
			while(i--) {
				vxJS.dom.addClassName(rows[i].cells[col.ndx], "active");
			}
		}
	};

	var loliteColumn = function(col) {
		var i = rows.length;

		if (col) {
			vxJS.dom.removeClassName(col.elem, "vxJS_sorTable_header_" + (col.asc ? "asc" : "desc"));
			while(i--) {
				vxJS.dom.removeClassName(rows[i].cells[col.ndx], "active");
			}
		}
	};

	var mouseMoveListener = function(e) {
		var mPos = vxJS.event.getAbsMousePos(e), y = draggedRow.pos.y, elem = draggedRow.elem, sib, next;

		if(mPos.y < y && (sib = vxJS.dom.prevSameNodeNameSibling(elem)) && mPos.y < (y -= sib.offsetHeight/2)) {
			while((next = vxJS.dom.prevSameNodeNameSibling(sib))) {
				if(mPos.y > y - sib.offsetHeight/2 - next.offsetHeight/2) {
					break;
				}
				y -= sib.offsetHeight;
				sib = next;
			}
			if(ind == sib) {
				return;
			}
			removeIndicator();
			ind.above = sib;
			vxJS.dom.addClassName(sib, "insertAbove");
		}
		else if(mPos.y > (y += elem.offsetHeight) && (sib = vxJS.dom.nextSameNodeNameSibling(elem)) && mPos.y > (y += sib.offsetHeight/2)) {
			while((next = vxJS.dom.nextSameNodeNameSibling(sib))) {
				if(mPos.y < y + sib.offsetHeight/2 + next.offsetHeight/2) {
					break;
				}
				y += sib.offsetHeight;
				sib = next;
			}
			if(ind == sib) {
				return;
			}
			removeIndicator();
			ind.below = sib;
			vxJS.dom.addClassName(sib, "insertBelow");
		}
		else {
			removeIndicator();
		}
		vxJS.event.preventDefault(e);
	};

	var stopDrag = function() {
		vxJS.event.removeListener(mouseUpId);
		vxJS.event.removeListener(mouseMoveId);

		if(ind.above) {
			ind.above.parentNode.insertBefore(draggedRow.elem, ind.above);
		}
		else if(ind.below) {
			ind.below.parentNode.insertBefore(draggedRow.elem, ind.below.nextSibling);
		}

		if(ind.below || ind.above) {
			loliteColumn(activeColumn);
			activeColumn = null;
		}

		vxJS.event.serve(that, "dragStop");

		vxJS.dom.removeClassName(draggedRow.elem, "dragged");
		removeIndicator();
		draggedRow = null;
	};

	var startDrag = function(e) {
		var p = this;

		while(p) {
			if(p.parentNode.parentNode == tb) {
				break;
			}
			p = p.parentNode;
		}

		if(!p || cols[p.cellIndex].format !== "manual") {
			return;
		}

		mouseMoveId	= vxJS.event.addListener(document, "mousemove", mouseMoveListener);
		mouseUpId	= vxJS.event.addListener(document, "mouseup", stopDrag);

		draggedRow = {
			elem: p.parentNode,
			pos: vxJS.dom.getElementOffset(p.parentNode)
		};

		vxJS.dom.addClassName(draggedRow.elem, "dragged");

		vxJS.event.serve(that, "dragStart");
		vxJS.event.preventDefault(e);
	};

	var getColumnValues = function(ndx) {
		var i = rows.length, f = cols[ndx].format, vals = [], r;

		var valFuncs = {
				"float":		function(v) { return (/^[+\-]?(?:\d{1,3}(?:[ ,\x27]\d{3})+|\d+)(?:\.\d+)?$/).test(v) ? v.replace(/[^0-9.\-]/g, "") : v; },
				"float_comma":	function(v) { return (/^[+\-]?(?:\d{1,3}(?:[ .\x27]\d{3})+|\d+)(?:,\d+)?$/).test(v) ? v.replace(/[^0-9,\-]/g, "").replace(",", ".") : v; },
				"date":			function(v, f) { var d = v.toDateTime(f, true); return d ? d.format("%Y-%M-%D") : v; },
				"time":			function(v, f) { return v.toDateTime(f); }
			};

		if(f && /^(date_|time_)[a-z]+$/.test(f)) {
			f = f.split("_")[0];
		}

		if(!f || typeof f == "function") {
			while(i--) {
				r = rows[i];
				vals[i] = { element: r, value: vxJS.dom.concatText(r.cells[ndx]).toLowerCase() };
			}
		}
		else {
			while(i--) {
				r = rows[i];
				vals[i] = { element: r, value: valFuncs[f](vxJS.dom.concatText(rows[i].cells[ndx]), cols[ndx].format) };
			}
		}

		return vals;
	};

	var buildTable = function(vals) {
		var t = document.createDocumentFragment(), i = 0;

		rows = [];
		while(vals[i]) {
			rows[i] = vals[i].element;
			t.appendChild(rows[i++]);
		}
		tb.appendChild(t);
	};

	var doSort = function() {
		var cbSort = function() {
			var s = this.asc ? 1 : -1;

			if(/^(?:float|float_comma)$/.test(this.format)) {
				return function(a, b) {
					a = +a.value;
					if(isNaN(a)) { return 1; }
					b = +b.value;
					if(isNaN(b)) { return -1; }
					if(a === b) { return 0; }
					return a < b ? -s : s;
				};
			}

			return function(a, b) {
				if(a.value === b.value) { return 0; }
				return a.value < b.value ? -s : s;
			};
		};

		vxJS.event.serve(that, "beginSort");

		buildTable(
			getColumnValues(activeColumn.ndx).
				sort(typeof activeColumn.format == "function" ? activeColumn.format.bind(activeColumn) : cbSort.bind(activeColumn)()
			)
		);

		vxJS.event.serve(that, "finishSort");
	};

	var sortOnClick = function() {
		var c, n = this, ndx;

		while(!/th|td/i.test(n.nodeName) && n.parentNode) {
			n = n.parentNode;
		}

		if(typeof (ndx = n.cellIndex) == "undefined") {
			return;
		}
		c = cols[ndx];
		if(!/^no_sort|manual$/.test(c.format)) {
			loliteColumn(activeColumn);
			if(c === activeColumn) {
				c.asc = !c.asc;
			}
			else {
				activeColumn = c;
			}
			hiliteColumn(c);
			doSort();
		}
	};

	var enableSort = function() {
		if(!clickListenerId) {
			clickListenerId = vxJS.event.addListener(th, "click", sortOnClick);
			vxJS.dom.removeClassName(th, "disabled");
		}
	};

	var disableSort = function() {
		if(clickListenerId) {
			vxJS.event.removeListener(clickListenerId);
			vxJS.dom.addClassName(th, "disabled");
			clickListenerId = null;
		}
	};

	(function() {
		var i;

		if (table.nodeName.toUpperCase() === "TBODY") {
			tb = table;
			table = tb.parentNode;
		}
		else if (table.nodeName.toUpperCase() === "TABLE") {
			tb = table.tBodies[0];
		}
		else {
			throw new Error("vxJS.widget.sorTable: No valid table or tbody element.");
		}

		if(!(th = table.getElementsByTagName("thead")[0])) {
			th = "thead".create();
			tb.parentNode.insertBefore(th, tb);
			th.appendChild(tb.rows[0]);
		}

		w = th.rows[0].cells.length;

		rows = vxJS.collectionToArray(tb.rows);
		origSort = [].concat(rows);

		if(columnFormat.indexOf("manual") !== -1) {
			vxJS.event.addListener(tb, "mousedown", startDrag);
		}

		for (i = 0; i < w; ++i) {
			cols.push({ ndx: i, elem: th.rows[0].cells[i], format: columnFormat[i], asc: true });
		}

		enableSort();
	}());

	that.getActiveColumn = function() {
		return activeColumn;
	};

	that.getCurrentOrder = function() {
		var i, l = rows.length, order = [];

		for(i = 0; i < l; ++i) {
			order.push(origSort.indexOf(tb.rows[i]));
		}
		return order;
	};

	that.insertRow = function(tr, ndx) {
		if(+ndx > rows.length || typeof ndx == "undefined") {
			ndx = rows.length - 1;
		}
		origSort.splice(ndx, 0, tr);
		rows.splice(ndx, 0, tr);
		tb.insertBefore(tr, rows[ndx + 1]);
	};

	that.removeRow = function(tr) {
		origSort.splice(origSort.indexOf(tr), 1);
		rows.splice(rows.indexOf(tr), 1);
		tb.removeChild(tr);
	};

	that.sortBy = function(colNdx, dir) {
		var c = cols[colNdx];

		if(!/^no_sort|manual$/.test(c.format)) {

			c.asc = !dir || dir.toLowerCase() == "asc";

			if(c !== activeColumn) {
				loliteColumn(activeColumn);
				activeColumn = c;
			}
			hiliteColumn(c);
			doSort();
		}
	};

	that.enableSort		= enableSort;
	that.disableSort	= disableSort;
	that.reSort			= doSort;
	that.element		= tb;

	return that;
};<commit_msg>set strict mode<commit_after>/**
 * sorTable widget
 * adds headers to table which allow sorting
 * @version 0.4.5 2012-03-22
 * @author Gregor Kofler
 *
 * @param {Object} table table or tbody (when several tbodies in one table) element
 * @param {Array} columnFormat optional array describing column format, core.js String.toDateTime()
 *
 * served events: "beginSort", "finishSort", "dragStart", "dragStop"
 *
 * @todo provide optional column to sort upon startup
 *
 * @todo manual sort interferes with addRow(), removeRow()
 */
vxJS.widget.sorTable = function(table, config) {
	"use strict";

	if(!config) {
		config = {};
	}

	var columnFormat = config.columnFormat && config.columnFormat.length ? config.columnFormat : [];

	var	th, tb, rows = [], cols = [], w, activeColumn, origSort = [], that = {},
		draggedRow, ind = {}, mouseUpId, mouseMoveId, clickListenerId;

	var removeIndicator = function() {
		if(ind.above) {
			vxJS.dom.removeClassName(ind.above, "insertAbove");
			ind.above = null;
		}
		else if(ind.below) {
			vxJS.dom.removeClassName(ind.below, "insertBelow");
			ind.below = null;
		}
	};

	var hiliteColumn = function(col) {
		var i = rows.length;

		if (col) {
			vxJS.dom.removeClassName(col.elem, "vxJS_sorTable_header_" + (col.asc ? "desc" : "asc"));
			vxJS.dom.addClassName(col.elem, "vxJS_sorTable_header_" + (col.asc ? "asc" : "desc"));
			while(i--) {
				vxJS.dom.addClassName(rows[i].cells[col.ndx], "active");
			}
		}
	};

	var loliteColumn = function(col) {
		var i = rows.length;

		if (col) {
			vxJS.dom.removeClassName(col.elem, "vxJS_sorTable_header_" + (col.asc ? "asc" : "desc"));
			while(i--) {
				vxJS.dom.removeClassName(rows[i].cells[col.ndx], "active");
			}
		}
	};

	var mouseMoveListener = function(e) {
		var mPos = vxJS.event.getAbsMousePos(e), y = draggedRow.pos.y, elem = draggedRow.elem, sib, next;

		if(mPos.y < y && (sib = vxJS.dom.prevSameNodeNameSibling(elem)) && mPos.y < (y -= sib.offsetHeight/2)) {
			while((next = vxJS.dom.prevSameNodeNameSibling(sib))) {
				if(mPos.y > y - sib.offsetHeight/2 - next.offsetHeight/2) {
					break;
				}
				y -= sib.offsetHeight;
				sib = next;
			}
			if(ind == sib) {
				return;
			}
			removeIndicator();
			ind.above = sib;
			vxJS.dom.addClassName(sib, "insertAbove");
		}
		else if(mPos.y > (y += elem.offsetHeight) && (sib = vxJS.dom.nextSameNodeNameSibling(elem)) && mPos.y > (y += sib.offsetHeight/2)) {
			while((next = vxJS.dom.nextSameNodeNameSibling(sib))) {
				if(mPos.y < y + sib.offsetHeight/2 + next.offsetHeight/2) {
					break;
				}
				y += sib.offsetHeight;
				sib = next;
			}
			if(ind == sib) {
				return;
			}
			removeIndicator();
			ind.below = sib;
			vxJS.dom.addClassName(sib, "insertBelow");
		}
		else {
			removeIndicator();
		}
		vxJS.event.preventDefault(e);
	};

	var stopDrag = function() {
		vxJS.event.removeListener(mouseUpId);
		vxJS.event.removeListener(mouseMoveId);

		if(ind.above) {
			ind.above.parentNode.insertBefore(draggedRow.elem, ind.above);
		}
		else if(ind.below) {
			ind.below.parentNode.insertBefore(draggedRow.elem, ind.below.nextSibling);
		}

		if(ind.below || ind.above) {
			loliteColumn(activeColumn);
			activeColumn = null;
		}

		vxJS.event.serve(that, "dragStop");

		vxJS.dom.removeClassName(draggedRow.elem, "dragged");
		removeIndicator();
		draggedRow = null;
	};

	var startDrag = function(e) {
		var p = this;

		while(p) {
			if(p.parentNode.parentNode == tb) {
				break;
			}
			p = p.parentNode;
		}

		if(!p || cols[p.cellIndex].format !== "manual") {
			return;
		}

		mouseMoveId	= vxJS.event.addListener(document, "mousemove", mouseMoveListener);
		mouseUpId	= vxJS.event.addListener(document, "mouseup", stopDrag);

		draggedRow = {
			elem: p.parentNode,
			pos: vxJS.dom.getElementOffset(p.parentNode)
		};

		vxJS.dom.addClassName(draggedRow.elem, "dragged");

		vxJS.event.serve(that, "dragStart");
		vxJS.event.preventDefault(e);
	};

	var getColumnValues = function(ndx) {
		var i = rows.length, f = cols[ndx].format, vals = [], r;

		var valFuncs = {
				"float":		function(v) { return (/^[+\-]?(?:\d{1,3}(?:[ ,\x27]\d{3})+|\d+)(?:\.\d+)?$/).test(v) ? v.replace(/[^0-9.\-]/g, "") : v; },
				"float_comma":	function(v) { return (/^[+\-]?(?:\d{1,3}(?:[ .\x27]\d{3})+|\d+)(?:,\d+)?$/).test(v) ? v.replace(/[^0-9,\-]/g, "").replace(",", ".") : v; },
				"date":			function(v, f) { var d = v.toDateTime(f, true); return d ? d.format("%Y-%M-%D") : v; },
				"time":			function(v, f) { return v.toDateTime(f); }
			};

		if(f && /^(date_|time_)[a-z]+$/.test(f)) {
			f = f.split("_")[0];
		}

		if(!f || typeof f == "function") {
			while(i--) {
				r = rows[i];
				vals[i] = { element: r, value: vxJS.dom.concatText(r.cells[ndx]).toLowerCase() };
			}
		}
		else {
			while(i--) {
				r = rows[i];
				vals[i] = { element: r, value: valFuncs[f](vxJS.dom.concatText(rows[i].cells[ndx]), cols[ndx].format) };
			}
		}

		return vals;
	};

	var buildTable = function(vals) {
		var t = document.createDocumentFragment(), i = 0;

		rows = [];
		while(vals[i]) {
			rows[i] = vals[i].element;
			t.appendChild(rows[i++]);
		}
		tb.appendChild(t);
	};

	var doSort = function() {
		var cbSort = function() {
			var s = this.asc ? 1 : -1;

			if(/^(?:float|float_comma)$/.test(this.format)) {
				return function(a, b) {
					a = +a.value;
					if(isNaN(a)) { return 1; }
					b = +b.value;
					if(isNaN(b)) { return -1; }
					if(a === b) { return 0; }
					return a < b ? -s : s;
				};
			}

			return function(a, b) {
				if(a.value === b.value) { return 0; }
				return a.value < b.value ? -s : s;
			};
		};

		vxJS.event.serve(that, "beginSort");

		buildTable(
			getColumnValues(activeColumn.ndx).
				sort(typeof activeColumn.format == "function" ? activeColumn.format.bind(activeColumn) : cbSort.bind(activeColumn)()
			)
		);

		vxJS.event.serve(that, "finishSort");
	};

	var sortOnClick = function() {
		var c, n = this, ndx;

		while(!/th|td/i.test(n.nodeName) && n.parentNode) {
			n = n.parentNode;
		}

		if(typeof (ndx = n.cellIndex) == "undefined") {
			return;
		}
		c = cols[ndx];
		if(!/^no_sort|manual$/.test(c.format)) {
			loliteColumn(activeColumn);
			if(c === activeColumn) {
				c.asc = !c.asc;
			}
			else {
				activeColumn = c;
			}
			hiliteColumn(c);
			doSort();
		}
	};

	var enableSort = function() {
		if(!clickListenerId) {
			clickListenerId = vxJS.event.addListener(th, "click", sortOnClick);
			vxJS.dom.removeClassName(th, "disabled");
		}
	};

	var disableSort = function() {
		if(clickListenerId) {
			vxJS.event.removeListener(clickListenerId);
			vxJS.dom.addClassName(th, "disabled");
			clickListenerId = null;
		}
	};

	(function() {
		var i;

		if (table.nodeName.toUpperCase() === "TBODY") {
			tb = table;
			table = tb.parentNode;
		}
		else if (table.nodeName.toUpperCase() === "TABLE") {
			tb = table.tBodies[0];
		}
		else {
			throw new Error("vxJS.widget.sorTable: No valid table or tbody element.");
		}

		if(!(th = table.getElementsByTagName("thead")[0])) {
			th = "thead".create();
			tb.parentNode.insertBefore(th, tb);
			th.appendChild(tb.rows[0]);
		}

		w = th.rows[0].cells.length;

		rows = vxJS.collectionToArray(tb.rows);
		origSort = [].concat(rows);

		if(columnFormat.indexOf("manual") !== -1) {
			vxJS.event.addListener(tb, "mousedown", startDrag);
		}

		for (i = 0; i < w; ++i) {
			cols.push({ ndx: i, elem: th.rows[0].cells[i], format: columnFormat[i], asc: true });
		}

		enableSort();
	}());

	that.getActiveColumn = function() {
		return activeColumn;
	};

	that.getCurrentOrder = function() {
		var i, l = rows.length, order = [];

		for(i = 0; i < l; ++i) {
			order.push(origSort.indexOf(tb.rows[i]));
		}
		return order;
	};

	that.insertRow = function(tr, ndx) {
		if(+ndx > rows.length || typeof ndx == "undefined") {
			ndx = rows.length - 1;
		}
		origSort.splice(ndx, 0, tr);
		rows.splice(ndx, 0, tr);
		tb.insertBefore(tr, rows[ndx + 1]);
	};

	that.removeRow = function(tr) {
		origSort.splice(origSort.indexOf(tr), 1);
		rows.splice(rows.indexOf(tr), 1);
		tb.removeChild(tr);
	};

	that.sortBy = function(colNdx, dir) {
		var c = cols[colNdx];

		if(!/^no_sort|manual$/.test(c.format)) {

			c.asc = !dir || dir.toLowerCase() == "asc";

			if(c !== activeColumn) {
				loliteColumn(activeColumn);
				activeColumn = c;
			}
			hiliteColumn(c);
			doSort();
		}
	};

	that.enableSort		= enableSort;
	that.disableSort	= disableSort;
	that.reSort			= doSort;
	that.element		= tb;

	return that;
};<|endoftext|>


<commit_before>(function ($, window, document, undefined) {
    var pluginName = "threadListChecker",
        defaults = {
            'queryInterval': 30 * 1000,
            'pjaxSelector': ''
        };

    function Plugin(element, options) {
        this.element = $(element);
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.intervalId = null;
        this.isPaused = false;
        this.init();
    }

    Plugin.prototype = {
        init: function () {
            this.bindListeners();
        },
        bindListeners: function () {
            this.registerTimer();

            // Kartik widgets triggers `change` event on initialization.
            // The entire page initialization takes up to 500ms
            // Delay registerInputListeners by 1000 ms to be sure
            setTimeout(function () {
                this.registerInputListeners();
            }.bind(this), 1000);
        },
        registerInputListeners: function () {
            $(this.settings.pjaxSelector).find('input')
                .on('change ifChecked', function (e) {
                    this.deleteTimer();
                    return true;
                }.bind(this))
                .on('focus', function (e) {
                    this.pauseTimer();
                    return true;
                }.bind(this))
                .on('blur', function (e) {
                    this.resumeTimer();
                    return true;
                }.bind(this));
        },
        registerTimer: function () {
            if (this.intervalId !== null) {
                return;
            }

            var interval = this.settings.queryInterval;

            this.intervalId = Visibility.every(interval, 3 * interval, function () {
                if (window.Pace !== undefined) {
                    Pace.ignore(this.query.bind(this));
                } else {
                    this.query();
                }
            }.bind(this));
        },
        deleteTimer: function () {
            if (this.intervalId === null) {
                return;
            }

            Visibility.stop(this.intervalId);
        },
        pauseTimer: function () {
            if (this.isPaused === true) {
                return;
            }

            this.isPaused = true;
        },
        resumeTimer: function () {
            if (this.isPaused === false) {
                return;
            }

            this.isPaused = false;
        },
        query: function () {
            if (this.isPaused) {
                return;
            }

            $.pjax.reload(this.settings.pjaxSelector).done(this.deleteTimer);
        }
    };

    $.fn[pluginName] = function (options) {
        this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
            }
        });
        return this;
    };
})(jQuery, window, document);
<commit_msg>Fixed threadListChecker to prevent timers overlapping in result of faled query<commit_after>(function ($, window, document, undefined) {
    var pluginName = "threadListChecker",
        defaults = {
            'queryInterval': 30 * 1000,
            'pjaxSelector': ''
        };

    function Plugin(element, options) {
        this.element = $(element);
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.intervalId = null;
        this.isPaused = false;
        this.init();
    }

    Plugin.prototype = {
        init: function () {
            this.bindListeners();
        },
        bindListeners: function () {
            this.registerTimer();

            // Kartik widgets trigger `change` event on initialization.
            // The entire page initialization takes up to 500 ms
            // Delay registerInputListeners by 1000 ms to be sure
            setTimeout(function () {
                this.registerInputListeners();
            }.bind(this), 1000);
        },
        registerInputListeners: function () {
            $(this.settings.pjaxSelector).find('input')
                .on('change ifChecked', function (e) {
                    this.deleteTimer();
                    return true;
                }.bind(this))
                .on('focus', function (e) {
                    this.pauseTimer();
                    return true;
                }.bind(this))
                .on('blur', function (e) {
                    this.resumeTimer();
                    return true;
                }.bind(this));
        },
        registerTimer: function () {
            if (this.intervalId !== null) {
                return;
            }

            var interval = this.settings.queryInterval;

            this.intervalId = Visibility.every(interval, 3 * interval, function () {
                if (window.Pace !== undefined) {
                    Pace.ignore(this.query.bind(this));
                } else {
                    this.query();
                }
            }.bind(this));
        },
        deleteTimer: function () {
            if (this.intervalId === null) {
                return;
            }

            Visibility.stop(this.intervalId);
        },
        pauseTimer: function () {
            if (this.isPaused === true) {
                return;
            }

            this.isPaused = true;
        },
        resumeTimer: function () {
            if (this.isPaused === false) {
                return;
            }

            this.isPaused = false;
        },
        query: function () {
            if (this.isPaused) {
                return;
            }

            this.deleteTimer();
            $.pjax.reload(this.settings.pjaxSelector);
        }
    };

    $.fn[pluginName] = function (options) {
        this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
            }
        });
        return this;
    };
})(jQuery, window, document);
<|endoftext|>


<commit_before>/*jshint esversion:6, node:true*/
/*
 * Application Core
 * https://github.com/goliatone/application-core
 *
 * Copyright (c) 2016 goliatone
 * Licensed under the MIT license.
 */
'use strict';

const path = require('path');
const glob = require('glob');
const extend = require('gextend');
const Keypath = require('gkeypath');
const EventEmitter = require('events');
const PluginLoader = require('in');
const sanitizeName = require('./utils').sanitizeName;

const DEFAULTS = {
    logger: console,
    _logger: console,
    autorun: true,
    autoinitialize: true,
    name: 'Application',
    sanitizeName: sanitizeName,
    registerReadyEvent: 'registered',
    //TODO: monitoring should be enabled only for data processing apps.
    coremodules: ['./logger', './errors', './dispatcher', './monitoring', './repl'],
    basepath: process.cwd()
};

class Application extends EventEmitter {
    constructor(options){
        options = extend({}, DEFAULTS, options);

        super();

        if(options.autoinitialize) this.init(options);
    }

    init(config) {
        if(this.initialized) return;
        this.initialized = true;

        config = extend({}, DEFAULTS, config);

        extend(this, config);

        /*
         * We want our listeners to be the first
         * ones added
         */
        this._registerListeners();

        /*
         * This is not the most elegant solution
         * but
         */
        if(this.config.app) extend(this, this.config.app);

        this.config = Keypath.wrap(this.config, null, 'data');

        ///*
        this.loader = new PluginLoader({
            context: this,
            basepath: __dirname,
            mountHandler: function _mount(bean, context){
                context.register(bean.plugin, bean.id);
                return bean.plugin;
            },
            afterMount: function(context){
                //this actually get's called twice :)
                //TODO: Fix
                context.emit('coreplugins.ready');
                context.logger.log('Core modules loaded!');
            }
        });

        this.register(extend, 'extend');

        this.loader.mountList(this.coremodules)
            .then(()=>{
                if(!this.loaders) return;

                if(this.loaders.modules){
                    let modulepath = this.loaders.modules;
                    if(!path.isAbsolute(modulepath)) modulepath = path.resolve(modulepath);
                    this.loader.mountDirectory(modulepath);
                }

                if(this.loaders.handlers){
                    let modulepath = this.loaders.handlers;
                    if(!path.isAbsolute(modulepath)) modulepath = path.resolve(modulepath);
                    this.loadHandlers(modulepath);
                }
            }).catch(this.onErrorHandler.bind(this, true, 'Error loading plugin.'));
    }

    register(instance, name){
        name = this.sanitizeName(name);
        var out = instance;

        if(instance.init){
            var config = this.config.get(name, {});
            out = instance.init(this, config);
            if(!out) out = instance;
        }

        if(this.hasOwnProperty(name)){
            this._logger.warn('register: we are going to override plugin %s', name);
        }

        function _register(context, name, instance){
            context[name] = instance;
            context.emit(name + '.' + context.registerReadyEvent, out);
        }

        if(typeof out === Promise){
            out.then((instance)=> {
                _register(this, name, instance);
            }).catch(this.onErrorHandler.bind(this, true, 'register: Error registering module' + name));
        } else _register(this, name, out);
    }

    onceRegistered(id, handler){
        this.once(id + '.' + this.registerReadyEvent, handler.bind(this));
        return this;
    }

    onErrorHandler(message, critical, err){
        this._logger.error('%s', message);
        this._logger.error(err.message);
        this._logger.error(err.stack);

        if(critical && this.exitOnError){
            process.exit(1);
        }
    }

    //TODO: We need to make this async
    //so we can refactor it and use loader.
    loadDirectory(dir, options={}){
        if(!path.isAbsolute(dir)){
            dir = path.join(getPathToMain(), dir);
        }

        this._logger.info('load directory %s', dir);

        var files = glob.sync(dir, options);

        if(options.handler){
            return options.handler(files);
        }

        return Promise.resolve(files);
    }

    loadHandlers(dir='handlers', options={}){
        // this.loader.mountDirectory(dir, {mountHandler: function(bean, context, config={}){
        //         context.handle(bean.id, bean.plugin);
        //         return bean.plugin;
        //     }
        // });

        this.loadDirectory(dir).then((files)=>{
            files.map((file) => {
                this.command(getModuleName(file), require(file));
            });
        });
    }

    resolve(id){
        //This method could return a Promise and a proxy,
        //were we use the proxy to handle calls for instance
        //while its being loaded...
        var instance = this[id];
        return Promise.resolve(instance);
    }

    _registerListeners(){
        this.once('run.pre', ()=>{
            let listeners = this.listeners('run.pre').length - 1;
            this._logger.debug('Running "pre" stage of "run" hook: %s listeners.', listeners);
        });

        this.once('run', ()=>{
            let listeners = this.listeners('run').length - 1;
            this._logger.debug('Running "run" hook: %s listeners.', listeners);
        });

        this.once('run.post', ()=>{
            let listeners = this.listeners('run.post').length - 1;
            this._logger.debug('Running "post" stage of "run" hook: %s listeners.', listeners);
        });
    }

    run(){
        if(!this.hook) {
            this.once('coreplugins.ready', this.run);
            return;
        }

        if(this.didRun) return;
        this.didRun = true;

        this.hook('run', {name: this.name}).then(()=>{
            this._logger.debug('Hook "run" complete.');
        });
    }

    //TODO: rename as command or registerCommand
    handle(event, handler){
        this._logger.warn('core::handle will be deprecated. Register command %s', event);
        this.command(event, handler);
    }

    command(event, handler){
        this._logger.debug('- register handler for %s', event);
        handler = handler.bind(this);

        this.on(event, handler);
    }

    close(code, label){
        //here we could run a close routing. Whatever it is, it should not
        //take more than 10 seconds- Serene's default
        return Promise.resolve();
    }

    get nicename(){
        return sanitizeName(this._name);
    }

    static loadConfig(options={}, asObject=true){
        if(!options.basepath) options.basepath = getPathToMain();

        var libDefaults = require('simple-config-loader')({
            basepath: __dirname
        });

        options = extend({}, DEFAULTS, libDefaults.data, options);

        var config = require('simple-config-loader')({
            config: options,
            basepath: process.cwd()
        });

        if(asObject) return config._target;
        return config;
    }
}

function getPathToMain(){
    var sep = require('path').sep;
    var main = process.argv[1];
    main = main.split(sep);
    main.pop();
    main = main.join(sep);
    return main;
}

function getModuleName(file){
    return path.basename(file).replace(path.extname(file), '');
}

/**
 * Exports module
 */
module.exports = Application;
<commit_msg>core: adding doc strings. clean up<commit_after>/*jshint esversion:6, node:true*/
/*
 * Application Core
 * https://github.com/goliatone/application-core
 *
 * Copyright (c) 2016 goliatone
 * Licensed under the MIT license.
 */
'use strict';

const path = require('path');
const glob = require('glob');
const extend = require('gextend');
const Keypath = require('gkeypath');
const EventEmitter = require('events');
const PluginLoader = require('in');
const sanitizeName = require('./utils').sanitizeName;

const DEFAULTS = {
    logger: console,
    _logger: console,
    autorun: true,
    autoinitialize: true,
    name: 'Application',
    sanitizeName: sanitizeName,
    registerReadyEvent: 'registered',
    //TODO: monitoring should be enabled only for data processing apps.
    coremodules: ['./logger', './errors', './dispatcher', './monitoring', './repl'],
    basepath: process.cwd()
};

class Application extends EventEmitter {
    constructor(options){
        options = extend({}, DEFAULTS, options);

        super();

        if(options.autoinitialize) this.init(options);
    }

    init(config) {
        if(this.initialized) return;
        this.initialized = true;

        config = extend({}, DEFAULTS, config);
        extend(this, config);

        /*
         * We want our listeners to be the first
         * ones added
         */
        this._registerListeners();
        this._configure();
        this._mount();
    }

    _configure(){
        /*
         * This is not the most elegant solution
         * but
         */
        if(this.config.app) extend(this, this.config.app);

        this.config = Keypath.wrap(this.config, null, 'data');
    }

    _mount(){
        ///*
        this.loader = new PluginLoader({
            context: this,
            basepath: __dirname,
            mountHandler: function _mount(bean, context){
                context.register(bean.plugin, bean.id);
                return bean.plugin;
            },
            afterMount: function(context){
                //this actually get's called twice :)
                //TODO: Fix
                context.emit('coreplugins.ready');
                context.logger.log('Core modules loaded!');
            }
        });

        this.register(extend, 'extend');

        this.loader.mountList(this.coremodules)
            .then(()=>{
                if(!this.loaders) return;

                if(this.loaders.modules){
                    let modulepath = this.loaders.modules;
                    if(!path.isAbsolute(modulepath)) modulepath = path.resolve(modulepath);
                    this.loader.mountDirectory(modulepath);
                }

                if(this.loaders.handlers){
                    let modulepath = this.loaders.handlers;
                    if(!path.isAbsolute(modulepath)) modulepath = path.resolve(modulepath);
                    this.loadHandlers(modulepath);
                }
            }).catch(this.onErrorHandler.bind(this, true, 'Error loading plugin.'));
    }

    register(instance, name){
        name = this.sanitizeName(name);
        var out = instance;

        if(instance.init){
            var config = this.config.get(name, {});
            out = instance.init(this, config);
            if(!out) out = instance;
        }

        if(this.hasOwnProperty(name)){
            this._logger.warn('register: we are going to override plugin %s', name);
        }

        function _register(context, name, instance){
            context[name] = instance;
            context.emit(name + '.' + context.registerReadyEvent, out);
        }

        // Promise.resolve(out).then((module)=>{
        //     _register(this, name, module);
        // }).catch(this.onErrorHandler.bind(this, true, 'register: Error registering module' + name));

        if(typeof out === Promise){
            out.then((instance)=> {
                _register(this, name, instance);
            }).catch(this.onErrorHandler.bind(this, true, 'register: Error registering module' + name));
        } else _register(this, name, out);
    }

    onceRegistered(id, handler){
        this.once(id + '.' + this.registerReadyEvent, handler.bind(this));
        return this;
    }

    onErrorHandler(message, critical, err){
        this._logger.error('%s', message);
        this._logger.error(err.message);
        this._logger.error(err.stack);

        if(critical && this.exitOnError){
            process.exit(1);
        }
    }

    //TODO: We need to make this async
    //so we can refactor it and use loader.
    loadDirectory(dir, options={}){
        if(!path.isAbsolute(dir)){
            dir = path.join(getPathToMain(), dir);
        }

        this._logger.info('load directory %s', dir);

        var files = glob.sync(dir, options);

        if(options.handler){
            return options.handler(files);
        }

        return Promise.resolve(files);
    }

    loadHandlers(dir='handlers', options={}){
        // this.loader.mountDirectory(dir, {mountHandler: function(bean, context, config={}){
        //         context.handle(bean.id, bean.plugin);
        //         return bean.plugin;
        //     }
        // });

        this.loadDirectory(dir).then((files)=>{
            files.map((file) => {
                this.command(getModuleName(file), require(file));
            });
        });
    }

    resolve(id){
        //This method could return a Promise and a proxy,
        //were we use the proxy to handle calls for instance
        //while its being loaded...
        var instance = this[id];
        return Promise.resolve(instance);
    }

    _registerListeners(){
        this.once('run.pre', ()=>{
            let listeners = this.listeners('run.pre').length - 1;
            this._logger.debug('Running "pre" stage of "run" hook: %s listeners.', listeners);
        });

        this.once('run', ()=>{
            let listeners = this.listeners('run').length - 1;
            this._logger.debug('Running "run" hook: %s listeners.', listeners);
        });

        this.once('run.post', ()=>{
            let listeners = this.listeners('run.post').length - 1;
            this._logger.debug('Running "post" stage of "run" hook: %s listeners.', listeners);
        });
    }

    run(){
        /*
         * `hook` is a plugin, so we need to wait for
         * it to be registered before we can call it :)
         */
        if(!this.hook) {
            this.once('coreplugins' + '.' + this.registerReadyEvent, this.run);
            return;
        }

        if(this.didRun) return;
        this.didRun = true;

        this.hook('run', {name: this.name}).then(()=>{
            this._logger.debug('Hook "run" complete.');
        });
    }

    //TODO: rename as command or registerCommand
    handle(event, handler){
        this._logger.warn('core::handle will be deprecated. Register command %s', event);
        this.command(event, handler);
    }

    command(event, handler){
        this._logger.debug('- register handler for %s', event);
        handler = handler.bind(this);

        this.on(event, handler);
    }

    close(code, label){
        //here we could run a close routing. Whatever it is, it should not
        //take more than 10 seconds- Serene's default
        return Promise.resolve();
    }

    get nicename(){
        return sanitizeName(this._name);
    }

    /**
     * Convenience method to load configuration
     * objects.
     * @type {Object}
     */
    static loadConfig(options={}, asObject=true){
        if(!options.basepath) options.basepath = getPathToMain();
        if(!options.projectpath) options.projectpath = process.cwd();
        /*
         * First we can to load options we might have
         * here, at the core library.
         */
        let libDefaults = require('simple-config-loader')({
            basepath: __dirname
        });

        options = extend({}, DEFAULTS, libDefaults.data, options);

        let projectpath = options.projectpath;
        delete options.projectpath;

        /*
         * Now, load configurations found on the project's
         * directory.
         */
        var config = require('simple-config-loader')({
            config: options,
            basepath: projectpath
        });

        if(asObject) return config._target;
        return config;
    }
}

function getPathToMain(){
    var sep = require('path').sep;
    var main = process.argv[1];
    main = main.split(sep);
    main.pop();
    main = main.join(sep);
    return main;
}

function getModuleName(file){
    return path.basename(file).replace(path.extname(file), '');
}

/**
 * Exports module
 */
module.exports = Application;
<|endoftext|>


<commit_before>import axios from 'axios';
import gsap from 'gsap';

let hideContent, delay, request;
const container = document.querySelector('#js-ajax-container');
const loader = document.querySelector('#js-loader');

function handleClick (event) {
  const link = event.target.closest('.js-ajax-link');

  if (link) {
    event.preventDefault();
    loadAjaxContent(link.href, false);
  }
}

function loadAjaxContent (url, isPopState) {
  const currentContent = document.querySelector('#js-ajax-content');
  // Animate the loader
  hideContent = gsap.timeline()
    .set(loader, { display: 'flex' })
    .to(currentContent, {
      opacity: 0,
      y: 30,
      duration: 0.3
    })
    .to(loader, {
      opacity: 1,
      duration: 0.3
      // ease: "power2.out"
    });

  // Run the ajax request
  request = axios.get(url)
    .then(response => {
      return processAjax(response, url, isPopState);
    })
    .catch(error => console.log(error));

  // Set a minimum delay so animations don't run too quick
  delay = new Promise(resolve => setTimeout(resolve, 900));
}

function processAjax (response, url, isPopState) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(response.data, 'text/html');
  const content = doc.querySelector('#js-ajax-content');
  const bodyClass = doc.querySelector('body').classList;

  // Make sure ajax request, hide content and delay have all resolved
  Promise.allSettled([hideContent, request, delay]).then(() => {
    // Update title
    document.title = doc.querySelector('title').innerHTML;

    // Clear container and add new content
    container.innerHTML = '';
    container.appendChild(content);

    // Make sure page is scrolled to the top
    document.documentElement.scrollTop = 0;

    // Hide the loader
    gsap.timeline()
      .add('fade')
      .fromTo(content, {
        opacity: 0,
        y: 20
      }, {
        opacity: 1,
        y: 0,
        duration: 0.3
      }, 'fade')
      .to(loader, {
        opacity: 0,
        duration: 0.3
      }, 'fade')
      .set(loader, { display: 'none' });

    // If the new page has a class on the body element
    // Make sure it gets set here.
    if (bodyClass !== '') {
      document.body.classList = '';
      document.body.classList.add(bodyClass);
    } else {
      document.body.classList = '';
    }

    // Update browser history
    if (isPopState === false) {
      history.pushState({ url }, null, url);
    }
  });
}

function handlePop (event) {
  loadAjaxContent(event.state.url, true);
}

export default function () {
  document.addEventListener('click', handleClick);
  window.addEventListener('popstate', handlePop);

  // Initialise history state with current URL
  const url = location.href;
  history.replaceState({ url }, null, url);
}
<commit_msg>Initialise animations after ajax load<commit_after>import axios from 'axios';
import gsap from 'gsap';
import animations from './animations';

let hideContent, delay, request;
const container = document.querySelector('#js-ajax-container');
const loader = document.querySelector('#js-loader');

function handleClick (event) {
  const link = event.target.closest('.js-ajax-link');

  if (link) {
    event.preventDefault();
    loadAjaxContent(link.href, false);
  }
}

function loadAjaxContent (url, isPopState) {
  const currentContent = document.querySelector('#js-ajax-content');
  // Animate the loader
  hideContent = gsap.timeline()
    .set(loader, { display: 'flex' })
    .to(currentContent, {
      opacity: 0,
      y: 30,
      duration: 0.3
    })
    .to(loader, {
      opacity: 1,
      duration: 0.3
      // ease: "power2.out"
    });

  // Run the ajax request
  request = axios.get(url)
    .then(response => {
      return processAjax(response, url, isPopState);
    })
    .catch(error => console.log(error));

  // Set a minimum delay so animations don't run too quick
  delay = new Promise(resolve => setTimeout(resolve, 900));
}

function processAjax (response, url, isPopState) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(response.data, 'text/html');
  const content = doc.querySelector('#js-ajax-content');
  const bodyClass = doc.querySelector('body').classList;

  // Make sure ajax request, hide content and delay have all resolved
  Promise.allSettled([hideContent, request, delay]).then(() => {
    // Update title
    document.title = doc.querySelector('title').innerHTML;

    // Clear container and add new content
    container.innerHTML = '';
    container.appendChild(content);

    // Make sure page is scrolled to the top
    document.documentElement.scrollTop = 0;

    // Hide the loader
    gsap.timeline()
      .add('fade')
      .fromTo(content, {
        opacity: 0,
        y: 20
      }, {
        opacity: 1,
        y: 0,
        duration: 0.3
      }, 'fade')
      .to(loader, {
        opacity: 0,
        duration: 0.3
      }, 'fade')
      .set(loader, { display: 'none' });

    // If the new page has a class on the body element
    // Make sure it gets set here.
    if (bodyClass !== '') {
      document.body.classList = '';
      document.body.classList.add(bodyClass);
    } else {
      document.body.classList = '';
    }

    // Initialise interactive elements
    animations();

    // Update browser history
    if (isPopState === false) {
      history.pushState({ url }, null, url);
    }
  });
}

function handlePop (event) {
  loadAjaxContent(event.state.url, true);
}

export default function () {
  document.addEventListener('click', handleClick);
  window.addEventListener('popstate', handlePop);

  // Initialise history state with current URL
  const url = location.href;
  history.replaceState({ url }, null, url);
}
<|endoftext|>


<commit_before>'use strict'

/**
 * データストアとの中間層として機能するデータ管理クラスです。
 * SakuyaBotが保持するユーザデータの管理を行い、バックエンドでデータストアとの同期を実行します。
 */
const DataManager = class {
  /**
   * 生成時、バックエンドのデータストアからデータを取得します。
   */
  constructor (db) {
    this.db = db
  }

  /**
   * クエリを発行し、結果をPromiseで返却します。
   * @param query クエリ
   * @param value プレースホルダで置き換えるパラメータ
   * @return 実行結果を返却するPromise
   */
  _query (query, value) {
    return new Promise ((resolve, reject) => {
      this.db.query( query, value, (err, rows) => {
        if (err) reject(err)
        else resolve(rows)
      })
    })
  }

  /**
   * トランザクションを生成します。
   * @param transaction トランザクション内で実行する処理(Promise)
   */
  _transaction (transaction) {
    return new Promise ( (resolve, reject) => {
      // トランザクション開始
      this.db.beginTransaction( (err) => {
        if (err) throw err
        else {
          // トランザクション処理
          transaction()
            .then ( (data) => this.db.commit( (err) => err ? reject(err) : resolve(data) ))
            .catch( (err) => this.db.rollback( () => { throw err }) )
        }
      })
    })
  }

  /**
   * ユーザのドキュメントを作成します。
   * @param {UserObject} user TwitterのUser Object
   * @return 作成されたユーザドキュメント
   */
  createUser (user) {
    return this._transaction( () => {
      return this._query(
        'INSERT into users (id, nickname, lovelity) value (?, ?, ?) ;',
        [user.id_str, ':NAME:さん', 5]
      ).then( () => this.getUser(user))
    })
  }

  /**
   * ユーザデータを取得します。
   * @param {UserObject} user TwitterのUser Object
   * @return ユーザデータ
   */
  getUser (user) {
    return this._query(
      'SELECT * from users WHERE id = ?;',
      [user.id_str]
    ).then( (data) => { return data[0] })
  }

  /**
   * ユーザのドキュメントを更新します。
   * @param {UserObject} user TwitterのUser Object
   * @param {object} data 更新するデータのクエリ
   * @return updateの結果
   */
  _updateUser (user, data) {
    return this._query(
      'UPDATE users set ? WHERE id = ?;',
      [data, user.id_str]
    )
  }

  /**
   * 指定したIDのユーザにニックネームを設定します。
   * @param {UserObject} user TwitterのUser Object
   * @param {string} nickname ニックネーム
   * @return updateの結果
   */
  setNickname (user, nickname) {
    return this._updateUser(user, {nickname: nickname})
  }

  /**
   * 指定したIDのユーザの誕生日を設定します。
   * @param {UserObject} user TwitterのUser Object
   * @param {number} month 誕生月
   * @param {number} day 誕生日
   * @return updateの結果
   * [hint]月と日のみで管理する。Date.yearは2000でMASKする。
   */
  setBirthday (user, month, day) {
    return this._updateUser(user, {birth_m: month, birth_d: day})
  }

  /**
   * 指定したIDのユーザの好感度を設定します。。
   * @param {UserObject} user TwitterのUser Object
   * @param {number} lovelity 好感度値
   * @return updateの結果
   */
  setLovelity (user, lovelity) {
    return this._updateUser(user, {lovelity: lovelity})
  }

  /**
   * 指定したIDのユーザの献血履歴を残します。
   * @param {UserObject} user
   * @param {number} 献血量
   * @return updateの結果
   */
  addDonateLog(user, amount) {
    return this._query(
      'INSERT into donates (userId, donatedAt, amount) value (?, NOW(), ?);',
      [user.id_str, amount]
    )
  }

  /**
   * 指定したユーザが献血していたかどうかを調べます。
   * @param {UserObject} user
   * @return 献血している場合はtrue, それ以外はfalse
   */
  userIsDonated(user) {
    return this._query(
      'SELECT count(*) as DC from donates WHERE userId = ? and donatedAt BETWEEN concat(CURDATE(), " 00:00:00") and concat(CURDATE(), " 23:59:59");',
      [user.id_str]
    ).then( (data) => data[0].DC > 0 )
  }

  /**
   * 献血履歴テーブルから合計量を集計します。
   * @return {number} 献血量
   */
  sumDonation () {
    return this._query(
      'SELECT sum(amount) as Total from donates;'
    ).then( (data) => data[0] )
  }

  /**
   * 合計献血量を日別献血履歴に退避し、
   * 献血履歴テーブルをクリアします。
   * @return delete結果
   */
  stashDonatedLog () {
    return this._transaction( () => {
      return this.sumDonation()
        .then( (sum) => {
          // 合計量を昨日の分として donatesByDate に挿入
          let yesterday = new Date(Date.now() - 86400 * 1000).toISOString().slice(0,10)
          return this._query( 'INSERT into donatesByDate (donatedAtDate, amount) value (?, ?)', [yesterday, sum.Total] )
        }).then( () =>
          this._query( 'DELETE from donates;')
        )
    })
  }
}

module.exports = DataManager

// let dm = new DataManager(this.db)
// dm.stashDonatedLog()
//   .then((rows) => { console.log(rows) })
//   .catch((err) => { throw err })

// 献血テーブルに履歴を大量投入する
// let loop = function (i) {
//   return dm._query(
//     'INSERT into donates (userId, donatedAt, amount) value (?, from_unixtime(?), ?);',
//     [
//       "00000000".concat().slice(-8),
//       (new Date(Date.now()).getTime()) / 1000 - Math.floor(Math.random() * 40000),
//       Math.floor(Math.random() * 200)
//     ]
//   ).then( () => { i%1000 || console.log(i); i >= 100 || loop(i+1)} )
// }
// loop(0)

// createUser
// dm.userIsDonated(
//   {id_str: "00000000"}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { throw err })

// createUser
// dm.createUser(
//   {id_str: "00000000"}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { throw err })

// getUser
// dm.getUser(
//   {id_str: "00000000"}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { console.error(err) })

// updateUser
// dm._updateUser(
//   {id_str: "00000000"},
//   {lovelity: 10}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { console.error(err) })

/**
 * @todo 以下のメモ書きは後ほど実装に利用する。
 *
 * table の create
 * create table users ( id nvarchar(64) primary key, nickname nvarchar(32) default ':NAME:さん', birth_m int, birth_d int, lovelity int default 5 );
 * create table donates (id int primary key auto_increment, donatedAt datetime not null, userId nvarchar(64) not null, amount int not null);
 * create table donatesByDate ( donatedAtDate date primary key, amount int not null );
 *
 * 今日の献血量合計を取得
 * select sum(amount), date(donatedAt) as donatedAtDate from donates where donatedAt >= CURDATE() group by donatedAtDate;
 *
 * 昨日の献血量合計を取得
 * select sum(amount), date(donatedAt) as donatedAtDate from donates where donatedAt < CURDATE() and donatedAt >= DATE_SUB( CURDATE(), INTERVAL 1 day ) groupby donatedAtDate;
 *
 * 直近5日の合計を取得
 * select sum(amount), date(donatedAt) as donatedAtDate from donates where donatedAt >= DATE_SUB( CURDATE(), INTERVAL 4 day ) group by donatedAtDate;
 *
 */

<commit_msg>sumDonationがObjectを返す不具合の対応<commit_after>'use strict'

/**
 * データストアとの中間層として機能するデータ管理クラスです。
 * SakuyaBotが保持するユーザデータの管理を行い、バックエンドでデータストアとの同期を実行します。
 */
const DataManager = class {
  /**
   * 生成時、バックエンドのデータストアからデータを取得します。
   */
  constructor (db) {
    this.db = db
  }

  /**
   * クエリを発行し、結果をPromiseで返却します。
   * @param query クエリ
   * @param value プレースホルダで置き換えるパラメータ
   * @return 実行結果を返却するPromise
   */
  _query (query, value) {
    return new Promise ((resolve, reject) => {
      this.db.query( query, value, (err, rows) => {
        if (err) reject(err)
        else resolve(rows)
      })
    })
  }

  /**
   * トランザクションを生成します。
   * @param transaction トランザクション内で実行する処理(Promise)
   */
  _transaction (transaction) {
    return new Promise ( (resolve, reject) => {
      // トランザクション開始
      this.db.beginTransaction( (err) => {
        if (err) throw err
        else {
          // トランザクション処理
          transaction()
            .then ( (data) => this.db.commit( (err) => err ? reject(err) : resolve(data) ))
            .catch( (err) => this.db.rollback( () => { throw err }) )
        }
      })
    })
  }

  /**
   * ユーザのドキュメントを作成します。
   * @param {UserObject} user TwitterのUser Object
   * @return 作成されたユーザドキュメント
   */
  createUser (user) {
    return this._transaction( () => {
      return this._query(
        'INSERT into users (id, nickname, lovelity) value (?, ?, ?) ;',
        [user.id_str, ':NAME:さん', 5]
      ).then( () => this.getUser(user))
    })
  }

  /**
   * ユーザデータを取得します。
   * @param {UserObject} user TwitterのUser Object
   * @return ユーザデータ
   */
  getUser (user) {
    return this._query(
      'SELECT * from users WHERE id = ?;',
      [user.id_str]
    ).then( (data) => { return data[0] })
  }

  /**
   * ユーザのドキュメントを更新します。
   * @param {UserObject} user TwitterのUser Object
   * @param {object} data 更新するデータのクエリ
   * @return updateの結果
   */
  _updateUser (user, data) {
    return this._query(
      'UPDATE users set ? WHERE id = ?;',
      [data, user.id_str]
    )
  }

  /**
   * 指定したIDのユーザにニックネームを設定します。
   * @param {UserObject} user TwitterのUser Object
   * @param {string} nickname ニックネーム
   * @return updateの結果
   */
  setNickname (user, nickname) {
    return this._updateUser(user, {nickname: nickname})
  }

  /**
   * 指定したIDのユーザの誕生日を設定します。
   * @param {UserObject} user TwitterのUser Object
   * @param {number} month 誕生月
   * @param {number} day 誕生日
   * @return updateの結果
   * [hint]月と日のみで管理する。Date.yearは2000でMASKする。
   */
  setBirthday (user, month, day) {
    return this._updateUser(user, {birth_m: month, birth_d: day})
  }

  /**
   * 指定したIDのユーザの好感度を設定します。。
   * @param {UserObject} user TwitterのUser Object
   * @param {number} lovelity 好感度値
   * @return updateの結果
   */
  setLovelity (user, lovelity) {
    return this._updateUser(user, {lovelity: lovelity})
  }

  /**
   * 指定したIDのユーザの献血履歴を残します。
   * @param {UserObject} user
   * @param {number} 献血量
   * @return updateの結果
   */
  addDonateLog(user, amount) {
    return this._query(
      'INSERT into donates (userId, donatedAt, amount) value (?, NOW(), ?);',
      [user.id_str, amount]
    )
  }

  /**
   * 指定したユーザが献血していたかどうかを調べます。
   * @param {UserObject} user
   * @return 献血している場合はtrue, それ以外はfalse
   */
  userIsDonated(user) {
    return this._query(
      'SELECT count(*) as DC from donates WHERE userId = ? and donatedAt BETWEEN concat(CURDATE(), " 00:00:00") and concat(CURDATE(), " 23:59:59");',
      [user.id_str]
    ).then( (data) => data[0].DC > 0 )
  }

  /**
   * 献血履歴テーブルから合計量を集計します。
   * @return {number} 献血量
   */
  sumDonation () {
    return this._query(
      'SELECT sum(amount) as Total from donates;'
    ).then( (data) => data[0].Total )
  }

  /**
   * 合計献血量を日別献血履歴に退避し、
   * 献血履歴テーブルをクリアします。
   * @return delete結果
   */
  stashDonatedLog () {
    return this._transaction( () => {
      return this.sumDonation()
        .then( (sum) => {
          // 合計量を昨日の分として donatesByDate に挿入
          let yesterday = new Date(Date.now() - 86400 * 1000).toISOString().slice(0,10)
          return this._query( 'INSERT into donatesByDate (donatedAtDate, amount) value (?, ?)', [yesterday, sum.Total] )
        }).then( () =>
          this._query( 'DELETE from donates;')
        )
    })
  }
}

module.exports = DataManager

// let dm = new DataManager(this.db)
// dm.stashDonatedLog()
//   .then((rows) => { console.log(rows) })
//   .catch((err) => { throw err })

// 献血テーブルに履歴を大量投入する
// let loop = function (i) {
//   return dm._query(
//     'INSERT into donates (userId, donatedAt, amount) value (?, from_unixtime(?), ?);',
//     [
//       "00000000".concat().slice(-8),
//       (new Date(Date.now()).getTime()) / 1000 - Math.floor(Math.random() * 40000),
//       Math.floor(Math.random() * 200)
//     ]
//   ).then( () => { i%1000 || console.log(i); i >= 100 || loop(i+1)} )
// }
// loop(0)

// createUser
// dm.userIsDonated(
//   {id_str: "00000000"}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { throw err })

// createUser
// dm.createUser(
//   {id_str: "00000000"}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { throw err })

// getUser
// dm.getUser(
//   {id_str: "00000000"}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { console.error(err) })

// updateUser
// dm._updateUser(
//   {id_str: "00000000"},
//   {lovelity: 10}
// ).then((rows) => { console.log(rows) }
// ).catch((err) => { console.error(err) })

/**
 * @todo 以下のメモ書きは後ほど実装に利用する。
 *
 * table の create
 * create table users ( id nvarchar(64) primary key, nickname nvarchar(32) default ':NAME:さん', birth_m int, birth_d int, lovelity int default 5 );
 * create table donates (id int primary key auto_increment, donatedAt datetime not null, userId nvarchar(64) not null, amount int not null);
 * create table donatesByDate ( donatedAtDate date primary key, amount int not null );
 *
 * 今日の献血量合計を取得
 * select sum(amount), date(donatedAt) as donatedAtDate from donates where donatedAt >= CURDATE() group by donatedAtDate;
 *
 * 昨日の献血量合計を取得
 * select sum(amount), date(donatedAt) as donatedAtDate from donates where donatedAt < CURDATE() and donatedAt >= DATE_SUB( CURDATE(), INTERVAL 1 day ) groupby donatedAtDate;
 *
 * 直近5日の合計を取得
 * select sum(amount), date(donatedAt) as donatedAtDate from donates where donatedAt >= DATE_SUB( CURDATE(), INTERVAL 4 day ) group by donatedAtDate;
 *
 */

<|endoftext|>