<commit_before>var keystone = require('../'),
	crypto = require('crypto'),
	scmp = require('scmp');

/**
 * Creates a hash of str with Keystone's cookie secret.
 * Only hashes the first half of the string.
 */
function hash(str) {
	// force type
	str = '' + str;
	// get the first half
	str = str.substr(0, Math.round(str.length / 2));
	// hash using sha256
	return crypto
		.createHmac('sha256', keystone.get('cookie secret'))
		.update(str)
		.digest('base64')
		.replace(/\=+$/, '');
}

/**
 * Signs in a user using user obejct
 *
 * @param {Object} user - user object
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} onSuccess callback, is passed the User instance
 */

function signinWithUser(user, req, res, onSuccess) {
	if (arguments.length < 4) {
		throw new Error('keystone.session.signinWithUser requires user, req and res objects, and an onSuccess callback.');
	}
	if ('object' !== typeof user) {
		throw new Error('keystone.session.signinWithUser requires user to be an object.');
	}
	if ('object' !== typeof req) {
		throw new Error('keystone.session.signinWithUser requires req to be an object.');
	}
	if ('object' !== typeof res) {
		throw new Error('keystone.session.signinWithUser requires res to be an object.');
	}
	if ('function' !== typeof onSuccess) {
		throw new Error('keystone.session.signinWithUser requires onSuccess to be a function.');
	}

	req.session.regenerate(function() {
		req.user = user;
		req.session.userId = user.id;
		// if the user has a password set, store a persistence cookie to resume sessions
		if (keystone.get('cookie signin') && user.password) {
			var userToken = user.id + ':' + hash(user.password);
			res.cookie('keystone.uid', userToken, { signed: true, httpOnly: true });
		}
		onSuccess(user);
	});

}

exports.signinWithUser = signinWithUser;

var postHookedSigninWithUser = function(user, req, res, onSuccess, onFail) {
	keystone.callHook(user, 'post:signin', function(err){
		if(err){
			return onFail(err);
		}
		exports.signinWithUser(user, req, res, onSuccess, onFail);
	});
};

/**
 * Signs in a user user matching the lookup filters
 *
 * @param {Object} lookup - must contain email and password
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} onSuccess callback, is passed the User instance
 * @param {function()} onFail callback
 */

var doSignin = function(lookup, req, res, onSuccess, onFail) {
	if (!lookup) {
		return onFail(new Error('session.signin requires a User ID or Object as the first argument'));
	}
	var User = keystone.list(keystone.get('user model'));
	if ('string' === typeof lookup.email && 'string' === typeof lookup.password) {
		// match email address and password
		User.model.findOne({ email: new RegExp(lookup.email, 'i') }).exec(function(err, user) {
			if (user) {
				user._.password.compare(lookup.password, function(err, isMatch) {
					if (!err && isMatch) {
						postHookedSigninWithUser(user, req, res, onSuccess, onFail);
					} else {
						err = new Error('The email and password you entered do not match.');
						onFail(err);
					}
				});
			} else {
				onFail(err);
			}
		});
	} else {
		lookup = '' + lookup;
		// match the userId, with optional password check
		var userId = (lookup.indexOf(':') > 0) ? lookup.substr(0, lookup.indexOf(':')) : lookup,
			passwordCheck = (lookup.indexOf(':') > 0) ? lookup.substr(lookup.indexOf(':') + 1) : false;
		User.model.findById(userId).exec(function(err, user) {
			if (user && (!passwordCheck || scmp(passwordCheck, hash(user.password)))) {
				postHookedSigninWithUser(user, req, res, onSuccess, onFail);
			} else {
				if (!err) {
					err = new Error('The email and password you entered do not match.');
				}
				onFail(err);
			}
		});
	}
};

exports.signin = function(lookup, req, res, onSuccess, onFail) {
	keystone.callHook({}, 'pre:signin', function(err){
		if(err){
			return onFail(err);
		}
		doSignin(lookup, req, res, onSuccess, onFail);
	});
};

/**
 * Signs the current user out and resets the session
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.signout = function(req, res, next) {

	keystone.callHook(req.user, 'pre:signout', function(err) {
		if (err) {
			console.log("An error occurred in signout 'pre' middleware", err);
		}
		res.clearCookie('keystone.uid');
		req.user = null;

		req.session.regenerate(function(err) {
			if (err) {
				return next(err);
			}
			keystone.callHook({}, 'post:signout', function(err) {
				if (err) {
					console.log("An error occurred in signout 'post' middleware", err);
				}
				next();
			});
		});
	});

};

/**
 * Middleware to ensure session persistence across server restarts
 *
 * Looks for a userId cookie, and if present, and there is no user signed in,
 * automatically signs the user in.
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.persist = function(req, res, next) {

	var User = keystone.list(keystone.get('user model'));
	if (!req.session) {
		console.error('\nKeystoneJS Runtime Error:\n\napp must have session middleware installed. Try adding "express-session" to your express instance.\n');
		process.exit(1);
	}

	if (keystone.get('cookie signin') && !req.session.userId && req.signedCookies['keystone.uid'] && req.signedCookies['keystone.uid'].indexOf(':') > 0) {
		exports.signin(req.signedCookies['keystone.uid'], req, res, function() {
			next();
		}, function(err) {
			next(err);
		});

	} else if (req.session.userId) {

		User.model.findById(req.session.userId).exec(function(err, user) {

			if (err) {
				return next(err);
			}

			req.user = user;
			next();

		});

	}
	else {
		next();
	}

};

/**
 * Middleware to enable access to Keystone
 *
 * Bounces the user to the signin screen if they are not signed in or do not have permission.
 *
 * req.user is the user returned by the database. It's type is Keystone.List.
 *
 * req.user.canAccessKeystone denotes whether the user has access to the admin panel.
 * If you're having issues double check your user model. Setting `canAccessKeystone` to true in
 * the database will not be reflected here if it is virtual.
 * See http://mongoosejs.com/docs/guide.html#virtuals
 *
 * @param {Object} req - express request object
 * @param req.user - The user object Keystone.List
 * @param req.user.canAccessKeystone {Boolean|Function}
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.keystoneAuth = function(req, res, next) {

	if (!req.user || !req.user.canAccessKeystone) {
		var from = new RegExp('^\/keystone\/?$', 'i').test(req.url) ? '' : '?from=' + req.url;
		return res.redirect(keystone.get('signin url') + from);
	}

	next();

};
<commit_msg>Update session.js<commit_after>var keystone = require('../'),
	crypto = require('crypto'),
	scmp = require('scmp');

/**
 * Creates a hash of str with Keystone's cookie secret.
 * Only hashes the first half of the string.
 */
function hash(str) {
	// force type
	str = '' + str;
	// get the first half
	str = str.substr(0, Math.round(str.length / 2));
	// hash using sha256
	return crypto
		.createHmac('sha256', keystone.get('cookie secret'))
		.update(str)
		.digest('base64')
		.replace(/\=+$/, '');
}

/**
 * Signs in a user using user obejct
 *
 * @param {Object} user - user object
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} onSuccess callback, is passed the User instance
 */

function signinWithUser(user, req, res, onSuccess) {
	if (arguments.length < 4) {
		throw new Error('keystone.session.signinWithUser requires user, req and res objects, and an onSuccess callback.');
	}
	if ('object' !== typeof user) {
		throw new Error('keystone.session.signinWithUser requires user to be an object.');
	}
	if ('object' !== typeof req) {
		throw new Error('keystone.session.signinWithUser requires req to be an object.');
	}
	if ('object' !== typeof res) {
		throw new Error('keystone.session.signinWithUser requires res to be an object.');
	}
	if ('function' !== typeof onSuccess) {
		throw new Error('keystone.session.signinWithUser requires onSuccess to be a function.');
	}

	req.session.regenerate(function() {
		req.user = user;
		req.session.userId = user.id;
		// if the user has a password set, store a persistence cookie to resume sessions
		if (keystone.get('cookie signin') && user.password) {
			var userToken = user.id + ':' + hash(user.password);
			res.cookie('keystone.uid', userToken, { signed: true, httpOnly: true });
		}
		onSuccess(user);
	});

}

exports.signinWithUser = signinWithUser;

var postHookedSigninWithUser = function(user, req, res, onSuccess, onFail) {
	keystone.callHook(user, 'post:signin', function(err){
		if(err){
			return onFail(err);
		}
		exports.signinWithUser(user, req, res, onSuccess, onFail);
	});
};

/**
 * Signs in a user user matching the lookup filters
 *
 * @param {Object} lookup - must contain email and password
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} onSuccess callback, is passed the User instance
 * @param {function()} onFail callback
 */

var doSignin = function(lookup, req, res, onSuccess, onFail) {
	if (!lookup) {
		return onFail(new Error('session.signin requires a User ID or Object as the first argument'));
	}
	var User = keystone.list(keystone.get('user model'));
	if ('string' === typeof lookup.email && 'string' === typeof lookup.password) {
		// match email address and password
		User.model.findOne({ email: new RegExp(lookup.email, 'i') }).exec(function(err, user) {
			if (user) {
				user._.password.compare(lookup.password, function(err, isMatch) {
					if (!err && isMatch) {
						postHookedSigninWithUser(user, req, res, onSuccess, onFail);
					} else {
						err = new Error('The email and password you entered do not match.');
						onFail(err);
					}
				});
			} else {
				onFail(err);
			}
		});
	} else {
		lookup = '' + lookup;
		// match the userId, with optional password check
		var userId = (lookup.indexOf(':') > 0) ? lookup.substr(0, lookup.indexOf(':')) : lookup,
			passwordCheck = (lookup.indexOf(':') > 0) ? lookup.substr(lookup.indexOf(':') + 1) : false;
		User.model.findById(userId).exec(function(err, user) {
			if (user && (!passwordCheck || scmp(passwordCheck, hash(user.password)))) {
				postHookedSigninWithUser(user, req, res, onSuccess, onFail);
			} else {
				if (!err) {
					err = new Error('The password you entered do not match the account.');
				}
				onFail(err);
			}
		});
	}
};

exports.signin = function(lookup, req, res, onSuccess, onFail) {
	keystone.callHook({}, 'pre:signin', function(err){
		if(err){
			return onFail(err);
		}
		doSignin(lookup, req, res, onSuccess, onFail);
	});
};

/**
 * Signs the current user out and resets the session
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.signout = function(req, res, next) {

	keystone.callHook(req.user, 'pre:signout', function(err) {
		if (err) {
			console.log("An error occurred in signout 'pre' middleware", err);
		}
		res.clearCookie('keystone.uid');
		req.user = null;

		req.session.regenerate(function(err) {
			if (err) {
				return next(err);
			}
			keystone.callHook({}, 'post:signout', function(err) {
				if (err) {
					console.log("An error occurred in signout 'post' middleware", err);
				}
				next();
			});
		});
	});

};

/**
 * Middleware to ensure session persistence across server restarts
 *
 * Looks for a userId cookie, and if present, and there is no user signed in,
 * automatically signs the user in.
 *
 * @param {Object} req - express request object
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.persist = function(req, res, next) {

	var User = keystone.list(keystone.get('user model'));
	if (!req.session) {
		console.error('\nKeystoneJS Runtime Error:\n\napp must have session middleware installed. Try adding "express-session" to your express instance.\n');
		process.exit(1);
	}

	if (keystone.get('cookie signin') && !req.session.userId && req.signedCookies['keystone.uid'] && req.signedCookies['keystone.uid'].indexOf(':') > 0) {
		exports.signin(req.signedCookies['keystone.uid'], req, res, function() {
			next();
		}, function(err) {
			next(err);
		});

	} else if (req.session.userId) {

		User.model.findById(req.session.userId).exec(function(err, user) {

			if (err) {
				return next(err);
			}

			req.user = user;
			next();

		});

	}
	else {
		next();
	}

};

/**
 * Middleware to enable access to Keystone
 *
 * Bounces the user to the signin screen if they are not signed in or do not have permission.
 *
 * req.user is the user returned by the database. It's type is Keystone.List.
 *
 * req.user.canAccessKeystone denotes whether the user has access to the admin panel.
 * If you're having issues double check your user model. Setting `canAccessKeystone` to true in
 * the database will not be reflected here if it is virtual.
 * See http://mongoosejs.com/docs/guide.html#virtuals
 *
 * @param {Object} req - express request object
 * @param req.user - The user object Keystone.List
 * @param req.user.canAccessKeystone {Boolean|Function}
 * @param {Object} res - express response object
 * @param {function()} next callback
 */

exports.keystoneAuth = function(req, res, next) {

	if (!req.user || !req.user.canAccessKeystone) {
		var from = new RegExp('^\/keystone\/?$', 'i').test(req.url) ? '' : '?from=' + req.url;
		return res.redirect(keystone.get('signin url') + from);
	}

	next();

};
<|endoftext|>


<commit_before>var debug = require('debug')('telegraf:session-redis')
var redis = require('redis')
var thunkify = require('thunkify')

module.exports = function (opts) {
  opts = Object.assign({
    getSessionKey: (event) => {
      var chatId = 'global'
      if (event.chat) {
        chatId = event.chat.id
      }
      if (event.message && event.message.chat) {
        chatId = event.message.chat.id
      }
      return `${event.from.id}:v${chatId}`
    },
    store: {}
  }, opts)

  var client = redis.createClient(
    opts.store.port,
    opts.store.host,
    opts.store.options
  )
  client.get = thunkify(client.get)
  client.set = thunkify(client.set)
  client.del = thunkify(client.del)
  client.ttl = opts.ttl ? (key) => client.expire(key, opts.ttl) : () => {
  }

  return function * (next) {
    var key = opts.getSessionKey(this.message || this.callbackQuery || this.inlineQuery || this.chosenInlineResult)
    if (!key) {
      return yield next
    }
    var session = {}
    this.__defineGetter__('session', function () {
      return session
    })
    this.__defineSetter__('session', function (val) {
      session = Object.assign({}, val)
    })
    debug('session key %s', key)
    try {
      var json = yield client.get(key)
      if (json) {
        session = JSON.parse(json)
      }
      yield next
    } catch (err) {
      throw err
    } finally {
      debug('save session', session)
      if (Object.keys(session).length === 0) {
        yield client.del(key)
      } else {
        yield client.set(key, JSON.stringify(session))
      }
      client.ttl(key)
    }
  }
}
<commit_msg>make store options conform node_redis and enabling url option therefore<commit_after>var debug = require('debug')('telegraf:session-redis')
var redis = require('redis')
var thunkify = require('thunkify')

module.exports = function (opts) {
  opts = Object.assign({
    getSessionKey: (event) => {
      var chatId = 'global'
      if (event.chat) {
        chatId = event.chat.id
      }
      if (event.message && event.message.chat) {
        chatId = event.message.chat.id
      }
      return `${event.from.id}:v${chatId}`
    },
    store: {}
  }, opts)

  var client = redis.createClient(opts.store)
  client.get = thunkify(client.get)
  client.set = thunkify(client.set)
  client.del = thunkify(client.del)
  client.ttl = opts.ttl ? (key) => client.expire(key, opts.ttl) : () => {
  }

  return function * (next) {
    var key = opts.getSessionKey(this.message || this.callbackQuery || this.inlineQuery || this.chosenInlineResult)
    if (!key) {
      return yield next
    }
    var session = {}
    this.__defineGetter__('session', function () {
      return session
    })
    this.__defineSetter__('session', function (val) {
      session = Object.assign({}, val)
    })
    debug('session key %s', key)
    try {
      var json = yield client.get(key)
      if (json) {
        session = JSON.parse(json)
      }
      yield next
    } catch (err) {
      throw err
    } finally {
      debug('save session', session)
      if (Object.keys(session).length === 0) {
        yield client.del(key)
      } else {
        yield client.set(key, JSON.stringify(session))
      }
      client.ttl(key)
    }
  }
}
<|endoftext|>


<commit_before>var uid = require('uid-safe');
var url = require('url');
var crc = require('buffer-crc32');

var hash = function (session) {
  return crc.signed(JSON.stringify(session));
};

var merge = function (from, to) {
  for (var k in from)
    if (from.hasOwnProperty(k))
      to[k] = from[k];
  return to;
};

var Session = function (req, res, session, _opts) {
  Object.defineProperty(this, 'req', { value: req });
  Object.defineProperty(this, 'res', { value: res });

  this.id = session.id || Session.cfg.genuid(32);

  if (typeof session === 'object')
    merge(session, this);

  Object.defineProperty(this, 'hash', { value: hash(this) });

  var end = res.end;
  res.end = function () {
    var args = arguments;

    if (!req.session || req.session.id !== this.id)
      return this.destroy(function () { end.apply(res, args); });

    if (Session.cfg.rolling)
      this.touch();

    if (!(_opts && _opts.load) || this.hash !== hash(this))
      return this.save(function () { end.apply(res, args); });

    end.apply(res, args);
  }.bind(this);

  if (!(_opts && _opts.load))
    this.touch();

  return this;
};

Session.prototype.touch = function () {
  this.res.cookies.set(Session.cfg.name, this.id, Session.cfg.cookie);
  return this;
};
Session.prototype.save = function (callback) {
  Session.cfg.store.set(this, callback || function () {});
  return this;
};
Session.prototype.destroy = function (callback) {
  if (!this.req.session || this.req.session.id === this.id)
    this.res.cookies.set(Session.cfg.name, '', Session.cfg.cookie);
  Session.cfg.store.del(this, callback || function () {});
};

Session.cfg = {
  name   : 'session',
  rolling: false,
  cookie : {},

  genuid: function () { return uid.sync(32); },

  store: {
    _mem_: {},

    get: function (sid, callback) {
      callback(Session.cfg.store._mem_[sid]);
    },

    set: function (session, callback) {
      Session.cfg.store._mem_[session.id] = merge(session, {});
      callback();
    },

    del: function (session, callback) {
      delete Session.cfg.store._mem_[session.id];
      callback();
    }
  }
};

Session.middleware = function (cfg) {

  if (cfg)
    Session.cfg = merge(cfg, Session.cfg);

  return function (req, res, callback) {

    if (url.parse(req.url).pathname.indexOf(Session.cfg.cookie.path || '/') !== 0)
      return callback();

    var sid = req.cookies.get(Session.cfg.name, {signed: Session.cfg.cookie.signed});

    if (!sid)
      return callback();

    Session.cfg.store.get(sid, function (session) {
      if (!session)
        return callback();

      req.session = new Session(req, res, session, { load: true });

      if (Session.cfg.rolling)
        res.cookies.set(Session.cfg.name, session.sid, Session.cfg.cookie);

      return callback();
    });
  };
};

module.exports = Session;
<commit_msg>fixed bug where sessions called destroy multiple times<commit_after>var uid = require('uid-safe');
var url = require('url');
var crc = require('buffer-crc32');

var hash = function (session) {
  return crc.signed(JSON.stringify(session));
};

var merge = function (from, to) {
  for (var k in from)
    if (from.hasOwnProperty(k))
      to[k] = from[k];
  return to;
};

var Session = function (req, res, session, _opts) {
  Object.defineProperty(this, 'req', { value: req });
  Object.defineProperty(this, 'res', { value: res });

  this.id = session.id || Session.cfg.genuid(32);

  if (typeof session === 'object')
    merge(session, this);

  Object.defineProperty(this, 'hash', { value: hash(this) });

  var end = res.end;
  res.end = function () {
    var args = arguments;

    if (this._is_destroyed_)
      return end.apply(res, args);

    if (!req.session || req.session.id !== this.id)
      return this.destroy(function () { end.apply(res, args); });

    if (Session.cfg.rolling)
      this.touch();

    if (!(_opts && _opts.load) || this.hash !== hash(this))
      return this.save(function () { end.apply(res, args); });

    end.apply(res, args);
  }.bind(this);

  if (!(_opts && _opts.load))
    this.touch();

  return this;
};

Session.prototype.touch = function () {
  this.res.cookies.set(Session.cfg.name, this.id, Session.cfg.cookie);
  return this;
};
Session.prototype.save = function (callback) {
  Session.cfg.store.set(this, callback || function () {});
  return this;
};
Session.prototype.destroy = function (callback) {
  if (!this.req.session || this.req.session.id === this.id)
    this.res.cookies.set(Session.cfg.name, '', Session.cfg.cookie);

  if (this.req.session === this)
    this.req.session = null;

  this._is_destroyed_ = true;

  Session.cfg.store.del(this, callback || function () {});
};

Session.cfg = {
  name   : 'session',
  rolling: false,
  cookie : {},

  genuid: function () { return uid.sync(32); },

  store: {
    _mem_: {},

    get: function (sid, callback) {
      callback(Session.cfg.store._mem_[sid]);
    },

    set: function (session, callback) {
      Session.cfg.store._mem_[session.id] = merge(session, {});
      callback();
    },

    del: function (session, callback) {
      delete Session.cfg.store._mem_[session.id];
      callback();
    }
  }
};

Session.middleware = function (cfg) {

  if (cfg)
    Session.cfg = merge(cfg, Session.cfg);

  return function (req, res, callback) {

    if (url.parse(req.url).pathname.indexOf(Session.cfg.cookie.path || '/') !== 0)
      return callback();

    var sid = req.cookies.get(Session.cfg.name, {signed: Session.cfg.cookie.signed});

    if (!sid)
      return callback();

    Session.cfg.store.get(sid, function (session) {
      if (!session)
        return callback();

      req.session = new Session(req, res, session, { load: true });

      if (Session.cfg.rolling)
        res.cookies.set(Session.cfg.name, session.sid, Session.cfg.cookie);

      return callback();
    });
  };
};

module.exports = Session;
<|endoftext|>


<commit_before>// Requires
var path = require('path');
var jsdom = require('jsdom');
var fs = require('fs');
var less = require('less');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;

var Squishy = function(index_path) {
  this.dirname = path.dirname(index_path);
  this.index = fs.readFileSync(index_path);
  this.jquery = fs.readFileSync(__dirname + '/jquery.js');
};

Squishy.prototype.squish = function(callback) {
  var that = this;

  var optimize = function(err, styles, scripts, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;

    // Remove Comments
    $('*').contents().each(function() {
      if (this.nodeType == 8) {
        $(this).remove();
      }
    });

    // Append inline styles to the head
    var style = window.document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = styles;
    window.document.head.appendChild(style);

    // Uglify the javascript
    var ast = jsp.parse(scripts);
    ast = pro.ast_mangle(ast);
    ast = pro.ast_squeeze(ast);

    // Append inline JS to the body
    var script = window.document.createElement('script');
    script.type = 'text/javascript';
    script.text = pro.gen_code(ast);
    window.document.body.appendChild(script);

    callback(null, '<!DOCTYPE html>' + $('html')[0].outerHTML);
  };

  var parse = function(err, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;
    var styles = '';
    var lesscss = '';
    var scripts = '';

    // Collect Styles
    $('link').each(function() {
      var $this = $(this);
      var resource = path.join(that.dirname, $this.attr('href')).toString();

      if ($this.data().optimize === 'css') {
        styles += fs.readFileSync(resource);
        $this.remove();
      }

      if($this.data().optimize === 'less') {
        lesscss += fs.readFileSync(resource);
        $this.remove();
      }
    });

    // Collect Scripts
    $('script').each(function() {
      var $this = $(this);

      if ($this.data().optimize === 'js') {
        var resource = path.join(that.dirname, $this.attr('src')).toString();
        scripts += fs.readFileSync(resource);
        $this.remove();
      }

      // Remove dev only scripts (i.e. client-side less)
      if ($this.data().optimize === 'dev') {
        $this.remove();
      }
    });

    less.render(lesscss, function(err, css) {
      optimize(err, styles + css, scripts, window);
    });
  };

  jsdom.env({
    html: this.index,
    src: [this.jquery],
    done: parse
  });
};

module.exports = Squishy;
<commit_msg>Squish images into base64<commit_after>// Requires
var path = require('path');
var jsdom = require('jsdom');
var fs = require('fs');
var less = require('less');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;

var Squishy = function(index_path) {
  this.dirname = path.dirname(index_path);
  this.index = fs.readFileSync(index_path);
  this.jquery = fs.readFileSync(__dirname + '/jquery.js');
};

Squishy.prototype.squish = function(callback) {
  var that = this;

  var optimize = function(err, styles, scripts, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;

    // Remove Comments
    $('*').contents().each(function() {
      if (this.nodeType == 8) {
        $(this).remove();
      }
    });

    // Append inline styles to the head
    var style = window.document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = styles;
    window.document.head.appendChild(style);

    // Uglify the javascript
    var ast = jsp.parse(scripts);
    ast = pro.ast_mangle(ast);
    ast = pro.ast_squeeze(ast);

    // Append inline JS to the body
    var script = window.document.createElement('script');
    script.type = 'text/javascript';
    script.text = pro.gen_code(ast);
    window.document.body.appendChild(script);

    callback(null, '<!DOCTYPE html>' + $('html')[0].outerHTML);
  };

  var parse = function(err, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;
    var styles = '';
    var lesscss = '';
    var scripts = '';

    // Collect Styles
    $('link').each(function() {
      var $this = $(this);
      var resource = path.join(that.dirname, $this.attr('href')).toString();

      if ($this.data().optimize === 'css') {
        styles += fs.readFileSync(resource);
        $this.remove();
      }

      if($this.data().optimize === 'less') {
        lesscss += fs.readFileSync(resource);
        $this.remove();
      }
    });

    // Collect Scripts
    $('script').each(function() {
      var $this = $(this);

      if ($this.data().optimize === 'js') {
        var resource = path.join(that.dirname, $this.attr('src')).toString();
        scripts += fs.readFileSync(resource);
        $this.remove();
      }

      // Remove dev only scripts (i.e. client-side less)
      if ($this.data().optimize === 'dev') {
        $this.remove();
      }
    });

    // Squish Images
    $('img').each(function() {
      var $this = $(this);

      if($this.data().optimize === 'image') {
        var resource = path.join(that.dirname, $this.attr('src')).toString();
        var encoded = fs.readFileSync(resource).toString('base64');
        $this.attr('src', 'data:image/png;base64,' + encoded);
      }
    });

    less.render(lesscss, function(err, css) {
      optimize(err, styles + css, scripts, window);
    });
  };

  jsdom.env({
    html: this.index,
    src: [this.jquery],
    done: parse
  });
};

module.exports = Squishy;
<|endoftext|>


<commit_before>// Requires
var path = require('path');
var jsdom = require('jsdom');
var fs = require('fs');
var less = require('less');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;

var Squishy = function(index_path) {
  this.dirname = path.dirname(index_path);
  this.index = fs.readFileSync(index_path);
  this.jquery = fs.readFileSync(__dirname + '/jquery.js');
};

Squishy.prototype.squish = function(callback) {
  var that = this;

  var optimize = function(err, styles, scripts, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;

    // Remove Comments
    $('*').contents().each(function() {
      if (this.nodeType == 8) {
        $(this).remove();
      }
    });

    // Append inline styles to the head
    var style = window.document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = styles;
    window.document.head.appendChild(style);

    // Uglify the javascript
    var ast = jsp.parse(scripts);
    ast = pro.ast_mangle(ast);
    ast = pro.ast_squeeze(ast);

    // Append inline JS to the body
    var script = window.document.createElement('script');
    script.type = 'text/javascript';
    script.text = pro.gen_code(ast);
    window.document.body.appendChild(script);

    callback(null, $('html')[0].outerHTML);
  };

  var parse = function(err, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;
    var styles = '';
    var lesscss = '';
    var scripts = '';

    // Collect Styles
    $('link').each(function() {
      var $this = $(this);
      var resource = path.join(that.dirname, $this.attr('href')).toString();

      if ($this.data().optimize === 'css') {
        styles += fs.readFileSync(resource);
        $this.remove();
      }

      if($this.data().optimize === 'less') {
        lesscss += fs.readFileSync(resource);
        $this.remove();
      }
    });

    // Collect Scripts
    $('script').each(function() {
      var $this = $(this);

      if ($this.data().optimize === 'js') {
        var resource = path.join(that.dirname, $this.attr('src')).toString();
        scripts += fs.readFileSync(resource);
        $this.remove();
      }

      // Remove dev only scripts (i.e. client-side less)
      if ($this.data().optimize === 'dev') {
        $this.remove();
      }
    });

    less.render(lesscss, function(err, css) {
      optimize(err, styles + css, scripts, window);
    });
  };

  jsdom.env({
    html: this.index,
    src: [this.jquery],
    done: parse
  });
};

module.exports = Squishy;
<commit_msg>Proper HTML5 doctype<commit_after>// Requires
var path = require('path');
var jsdom = require('jsdom');
var fs = require('fs');
var less = require('less');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;

var Squishy = function(index_path) {
  this.dirname = path.dirname(index_path);
  this.index = fs.readFileSync(index_path);
  this.jquery = fs.readFileSync(__dirname + '/jquery.js');
};

Squishy.prototype.squish = function(callback) {
  var that = this;

  var optimize = function(err, styles, scripts, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;

    // Remove Comments
    $('*').contents().each(function() {
      if (this.nodeType == 8) {
        $(this).remove();
      }
    });

    // Append inline styles to the head
    var style = window.document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = styles;
    window.document.head.appendChild(style);

    // Uglify the javascript
    var ast = jsp.parse(scripts);
    ast = pro.ast_mangle(ast);
    ast = pro.ast_squeeze(ast);

    // Append inline JS to the body
    var script = window.document.createElement('script');
    script.type = 'text/javascript';
    script.text = pro.gen_code(ast);
    window.document.body.appendChild(script);

    callback(null, '<!DOCTYPE html>' + $('html')[0].outerHTML);
  };

  var parse = function(err, window) {
    if(err) {
      return callback(err);
    }

    var $ = window.$;
    var styles = '';
    var lesscss = '';
    var scripts = '';

    // Collect Styles
    $('link').each(function() {
      var $this = $(this);
      var resource = path.join(that.dirname, $this.attr('href')).toString();

      if ($this.data().optimize === 'css') {
        styles += fs.readFileSync(resource);
        $this.remove();
      }

      if($this.data().optimize === 'less') {
        lesscss += fs.readFileSync(resource);
        $this.remove();
      }
    });

    // Collect Scripts
    $('script').each(function() {
      var $this = $(this);

      if ($this.data().optimize === 'js') {
        var resource = path.join(that.dirname, $this.attr('src')).toString();
        scripts += fs.readFileSync(resource);
        $this.remove();
      }

      // Remove dev only scripts (i.e. client-side less)
      if ($this.data().optimize === 'dev') {
        $this.remove();
      }
    });

    less.render(lesscss, function(err, css) {
      optimize(err, styles + css, scripts, window);
    });
  };

  jsdom.env({
    html: this.index,
    src: [this.jquery],
    done: parse
  });
};

module.exports = Squishy;
<|endoftext|>


<commit_before>
"use strict";

var ds      = require('./datastore.js');
var request = require('request');
var utils  = require('./utils.js');
var clc     = require('cli-color');
var _results = {
  endpoints:{},
  endpointscount:0,
  calls:0,
  asserts:0,
  success: true
};
var suite_file = process.argv[2];

exports.run = runTestPlan;
exports.that = exports.if = require('./assertions.js');
exports.getDate = ds.getData;
exports.setDate = ds.setData;

exports.outputJSON = require('./output').json;
exports.outputCli = require('./output').cli;

function assertions(test, response, body ){

  test.pass = false;

  if ( test.asserts ){
    for(var i=0; i<test.asserts.length; i++){

      test.asserts[i].result = test.asserts[i].test.call(test.asserts[i], response, body);

      if ( test.asserts[i].result === false ){
        return test;
      }
    }
  }

  test.pass = true;

  return test;
}

function save_results( test ){
  if ( !_results.endpoints[test.uri] ){
    _results.endpoints[test.uri] = true;
    _results.endpointscount++;
  }

  _results.calls++;

  var i=0;

  while ( i<test.asserts.length && test.asserts[i].result ){
    _results.asserts++;
    i++;
  }

  if ( i<test.asserts.length && !test.asserts[i].result ){
    _results.success = false;
  }

}

function runTestPlan(plan, output, next){
  var j=0;

  if( plan.prepare ){
    plan.prepare.call(null, plan);
  }

  output.begin( plan );

  runTest(plan, j, output, function(){

    output.end( plan );
    output.stats( _results );

    if( plan.clean ){
      plan.clean.call(null, plan);
    }

    if( next ){
      next();
    }
  });
}

function runTest(plan, pointer, output, finalCallback){

  var test = plan.tests[pointer];

  test.body = utils.getValue(test.body);
  test.uri = utils.getValue(test.uri);
  test.multipart = utils.getValue(test.multipart);
  test.headers = utils.getValue(test.headers);

  test.waitBefore = test.waitBefore || 0;
  test.waitAfter = test.waitAfter ||  0;

  var delayAfter = function(plan, pointer, output, callback, finalCallback){
    if ( test.waitAfter ){
      output.write('Waiting '+ test.waitAfter +'s');
    }
    setTimeout(function(){
      runTest(plan, pointer, output, finalCallback);
    }, test.waitAfter*1000);
  };

  var delayBefore = function(plan, pointer, output, callback, finalCallback){
    if ( test.waitBefore ){
      output.write('Waiting '+ test.waitBefore +'s');
    }
    executeHTTPTest(plan, test, pointer, output, callback, finalCallback);
  };

  setTimeout( function(){
    delayBefore(plan, pointer, output, delayAfter, finalCallback);
  }, test.waitBefore*1000);

}

function store(test, body){

  var data = JSON.parse(body);

  for(var i=0; i<test.store.length; i++){
    ds.setData( test.store[i].dest, utils.getPathValue(data, test.store[i].src) );
  }
}

function executeHTTPTest( plan, test, pointer, output, callback, finalCallback ){

  var params = {
    url: plan.base_uri + test.uri,
    method: 'GET',
    jar: true,
    rejectUnauthorized: false
  };

  if ( test.method ){
    params.method = test.method;
  }

  if( test.body ){
    if (typeof(test.body) == "object" ) {
      params.form = test.body;
    } else if (typeof(test.body) === "string"){
      params.body = test.body;
    }
  }

  if ( test.headers ){

    if ( typeof(test.headers) === 'function'){
      params.headers = test.headers();
    } else {
      params.headers = test.headers;
    }
  }

  if( test.multipart ){
    params.multipart = test.multipart;
  }

  test.pass = true;

  var req = request(params, function(err, response, body){

    if ( err ){
      console.log( clc.red.bold('ERROR') );
      console.log( "Could not complete HTTP request:");
      console.log( clc.bold("Params:"));
      console.log( params );
      console.log( clc.bold("Error:"));
      console.log( err );
      return;
    }

    if ( test.asserts ){
      test = assertions(test, response, body);
    }

    output.show( test );

    if( ! test.pass ){
      output.end( plan );
      if (plan.clean){
        plan.clean.call(null, plan);
      }
      return false;
    }

    if ( test.store ){
      store(test, body);
    }

    save_results(test);

    pointer++;

    output.inter( plan, pointer );

    if( pointer < plan.tests.length ){
      callback(plan, pointer, output, callback, finalCallback);
    } else {
      finalCallback();
    }
  });
}
<commit_msg>Fix data method export<commit_after>
"use strict";

var ds      = require('./datastore.js');
var request = require('request');
var utils  = require('./utils.js');
var clc     = require('cli-color');
var _results = {
  endpoints:{},
  endpointscount:0,
  calls:0,
  asserts:0,
  success: true
};
var suite_file = process.argv[2];

exports.run = runTestPlan;
exports.that = exports.if = require('./assertions.js');
exports.getData = ds.getData;
exports.setData = ds.setData;

exports.outputJSON = require('./output').json;
exports.outputCli = require('./output').cli;

function assertions(test, response, body ){

  test.pass = false;

  if ( test.asserts ){
    for(var i=0; i<test.asserts.length; i++){

      test.asserts[i].result = test.asserts[i].test.call(test.asserts[i], response, body);

      if ( test.asserts[i].result === false ){
        return test;
      }
    }
  }

  test.pass = true;

  return test;
}

function save_results( test ){
  if ( !_results.endpoints[test.uri] ){
    _results.endpoints[test.uri] = true;
    _results.endpointscount++;
  }

  _results.calls++;

  var i=0;

  while ( i<test.asserts.length && test.asserts[i].result ){
    _results.asserts++;
    i++;
  }

  if ( i<test.asserts.length && !test.asserts[i].result ){
    _results.success = false;
  }

}

function runTestPlan(plan, output, next){
  var j=0;

  if( plan.prepare ){
    plan.prepare.call(null, plan);
  }

  output.begin( plan );

  runTest(plan, j, output, function(){

    output.end( plan );
    output.stats( _results );

    if( plan.clean ){
      plan.clean.call(null, plan);
    }

    if( next ){
      next();
    }
  });
}

function runTest(plan, pointer, output, finalCallback){

  var test = plan.tests[pointer];

  test.body = utils.getValue(test.body);
  test.uri = utils.getValue(test.uri);
  test.multipart = utils.getValue(test.multipart);
  test.headers = utils.getValue(test.headers);

  test.waitBefore = test.waitBefore || 0;
  test.waitAfter = test.waitAfter ||  0;

  var delayAfter = function(plan, pointer, output, callback, finalCallback){
    if ( test.waitAfter ){
      output.write('Waiting '+ test.waitAfter +'s');
    }
    setTimeout(function(){
      runTest(plan, pointer, output, finalCallback);
    }, test.waitAfter*1000);
  };

  var delayBefore = function(plan, pointer, output, callback, finalCallback){
    if ( test.waitBefore ){
      output.write('Waiting '+ test.waitBefore +'s');
    }
    executeHTTPTest(plan, test, pointer, output, callback, finalCallback);
  };

  setTimeout( function(){
    delayBefore(plan, pointer, output, delayAfter, finalCallback);
  }, test.waitBefore*1000);

}

function store(test, body){

  var data = JSON.parse(body);

  for(var i=0; i<test.store.length; i++){
    ds.setData( test.store[i].dest, utils.getPathValue(data, test.store[i].src) );
  }
}

function executeHTTPTest( plan, test, pointer, output, callback, finalCallback ){

  var params = {
    url: plan.base_uri + test.uri,
    method: 'GET',
    jar: true,
    rejectUnauthorized: false
  };

  if ( test.method ){
    params.method = test.method;
  }

  if( test.body ){
    if (typeof(test.body) == "object" ) {
      params.form = test.body;
    } else if (typeof(test.body) === "string"){
      params.body = test.body;
    }
  }

  if ( test.headers ){

    if ( typeof(test.headers) === 'function'){
      params.headers = test.headers();
    } else {
      params.headers = test.headers;
    }
  }

  if( test.multipart ){
    params.multipart = test.multipart;
  }

  test.pass = true;

  var req = request(params, function(err, response, body){

    if ( err ){
      console.log( clc.red.bold('ERROR') );
      console.log( "Could not complete HTTP request:");
      console.log( clc.bold("Params:"));
      console.log( params );
      console.log( clc.bold("Error:"));
      console.log( err );
      return;
    }

    if ( test.asserts ){
      test = assertions(test, response, body);
    }

    output.show( test );

    if( ! test.pass ){
      output.end( plan );
      if (plan.clean){
        plan.clean.call(null, plan);
      }
      return false;
    }

    if ( test.store ){
      store(test, body);
    }

    save_results(test);

    pointer++;

    output.inter( plan, pointer );

    if( pointer < plan.tests.length ){
      callback(plan, pointer, output, callback, finalCallback);
    } else {
      finalCallback();
    }
  });
}
<|endoftext|>


<commit_before>var parseClient = function(prop){
	'use strict';
	var safari = typeof(prop.doNotTrack) ? prop.doNotTrack : false;
	var msie = typeof(prop.navigator) !== 'undefined' &&
		typeof(prop.navigator.msDoNotTrack) !== 'undefined' ?
			prop.navigator.msDoNotTrack : false;
	var other = typeof(prop.navigator) !== 'undefined' &&
		typeof(prop.navigator.doNotTrack) !== 'undefined' ?
			prop.navigator.doNotTrack : false;

	return safari || msie || other || '';
};

module.exports = function(hatOnCb, hatOffCb){
	'use strict';

	return function(target){
		var reqNoTrack = typeof(target.headers) !== 'undefined' &&
			target.headers.DNT === 1 || false;
		var navNoTrack = !!parseClient(target).match(/1|yes/) || false;
		return reqNoTrack || navNoTrack ?
			hatOnCb(target) : hatOffCb(target) || false;
	};
};
<commit_msg>refactored tinfoil.js by abstracting some checks<commit_after>var findNavigator = function (win) {
	'use strict';

	var hasNavigator = typeof(win.navigator) !== 'undefined';
	return hasNavigator ? win.navigator : false;
};

var checkProperty = function (target, property) {
	'use strict';

	if (target) {
		return target[property];
	}
	return false;
};

var parseClient = function (prop) {
	'use strict';

	var safari = typeof(prop.doNotTrack) ? prop.doNotTrack : false;
	var msie = checkProperty(findNavigator(prop), 'msDoNotTrack');
	var other = checkProperty(findNavigator(prop), 'doNotTrack');

	return safari || msie || other || '';
};

module.exports = function (hatOnCb, hatOffCb) {
	'use strict';

	return function (target) {
		var reqNoTrack = typeof(target.headers) !== 'undefined' &&
			target.headers.DNT === 1 || false;
		var navNoTrack = !!parseClient(target).match(/1|yes/) || false;
		return reqNoTrack || navNoTrack ?
			hatOnCb(target) : hatOffCb(target) || false;
	};
};
<|endoftext|>


<commit_before>module.exports = function (settings) {

	class Track {
		native = {};
		context;
		constructor(context) {
			this.context = context;
		}
	}

	class AsyncTracker {
		#tracks = [];
		#sources = new Set();
		#count = 0;
		#resolve;

		constructor(contexts = [window]) {
			this.promise = new Promise(ok => {
				this.#resolve = ok;
			});

			for (const context of contexts) {
				const track = new Track(context);
				this.#tracks.push(track);

				this.#handleMicrotask(track);
				this.#handleRequestAnimationFrame(track);
				this.#handleTimeout(track);
				this.#handlePromise(track);
				this.#handleReady(track);
				this.#handleFetch(track);
				this.#handleXHR(track);
				this.#handleNodes(track);
			}
		}

		#handleNode(node) {
			const id = this.waits(node.nodeName + ' ' + (node.src ?? node.href));
			const itc = () => {
				node.removeEventListener('load', itc);
				node.removeEventListener('error', itc);
				this.completes(id);
			};
			node.addEventListener('load', itc);
			node.addEventListener('error', itc);
		}

		#unhandleNode(node) {
			this.completes(node.nodeName + ' ' + (node.src ?? node.href));
		}

		#handleNodes({ context }) {
			function check(node) {
				if (node.nodeType != Node.ELEMENT_NODE) return;
				const tag = node.nodeName;
				if (tag == "SCRIPT") {
					return node.src && (!node.type || node.type == "text/javascript" || node.type == "module");
				} else if (tag == "LINK") {
					return node.href && node.rel == "stylesheet";
				}
			}
			const observer = new MutationObserver(mutations => {
				for (const { addedNodes, removedNodes } of mutations) {
					if (addedNodes) for (const node of addedNodes) {
						if (check(node)) this.#handleNode(node);
					}
					if (removedNodes) for (const node of removedNodes) {
						if (check(node)) this.#unhandleNode(node);
					}
				}
			});
			observer.observe(context.document, {
				childList: true,
				subtree: true
			});
		}

		#handleXHR({ native, context }) {
			const it = this;
			native.XMLHttpRequest = context.XMLHttpRequest;
			context.XMLHttpRequest = class XMLHttpRequest extends native.XMLHttpRequest {
				#id;
				#to;
				#done;
				constructor() {
					super();
					this.#done = () => {
						if (this.#to != null) {
							native.clearTimeout.call(context, this.#to);
							this.#to = null;
						}
						this.removeEventListener('abort', this.#done);
						this.removeEventListener('error', this.#done);
						this.removeEventListener('load', this.#done);
						it.completes(this.#id);
					};
				}
				static get [Symbol.species]() {
					return XMLHttpRequest;
				}
				get [Symbol.toStringTag]() {
					return 'XMLHttpRequest';
				}
				send(...args) {
					const ret = super.send(...args);
					this.addEventListener('abort', this.#done);
					this.addEventListener('error', this.#done);
					this.addEventListener('load', this.#done);
					this.#to = native.setTimeout.call(context, this.#done, settings.stall);
					this.#id = it.creates('xhr');
					return ret;
				}
			};
		}

		#handleFetch({ native, context }) {
			const it = this;
			native.fetch = context.fetch;
			context.fetch = async function fetch(...args) {
				const id = it.creates('fetch');
				try {
					const res = await native.fetch.apply(context, args);
					return res;
				} finally {
					it.completes(id);
				}
			};
		}

		#handleReady({ native, context }) {
			const it = this;
			const { document: doc } = context;
			native.removeEventListener = doc.removeEventListener;
			native.addEventListener = doc.addEventListener;
			native.bubbles = new Map();
			native.captures = new Map();
			class EventWatch {
				constructor(name, fn) {
					this.id = it.creates(doc.readyState == "loading" ? name : null);
					this.fn = fn;
				}
				async handleEvent(e) {
					try {
						if (this.fn.handleEvent) await this.fn.handleEvent(e);
						else await this.fn(e);
					} finally {
						it.completes(this.id);
						this.id = null;
					}
				}
			}
			doc.addEventListener = function (name, fn, cap) {
				if (name != "DOMContentLoaded") {
					return native.addEventListener.call(doc, name, fn, cap);
				}
				const eMap = cap ? native.captures : native.bubbles;
				if (eMap.has(fn)) return;
				const watch = new EventWatch(name, fn);
				eMap.set(fn, watch);
				return native.addEventListener.call(doc, name, watch, cap);
			};
			doc.removeEventListener = function (name, fn, cap) {
				if (name != "DOMContentLoaded") {
					return native.removeEventListener.call(doc, name, fn, cap);
				}
				const eMap = cap ? native.captures : native.bubbles;
				const watch = eMap.get(fn);
				if (!watch) return;
				it.completes(watch.id);
				return native.removeEventListener.call(doc, name, watch, cap);
			};
		}

		#handleMicrotask({ native, context }) {
			const it = this;
			native.queueMicrotask = context.queueMicrotask;
			context.queueMicrotask = function (fn) {
				const id = it.creates('task');
				native.queueMicrotask.call(context, () => {
					try {
						fn.call(this);
					} finally {
						it.completes(id);
					}
				});
			};
		}

		#handleTimeout({ native, context }) {
			const it = this;
			native.setTimeout = context.setTimeout;
			context.setTimeout = function (fn, to) {
				const id = 'to' + native.setTimeout.call(context, () => {
					try {
						fn.call(this);
					} finally {
						it.completes(id);
					}
				}, to);
				it.waits(id);
			};

			native.clearTimeout = context.clearTimeout;
			context.clearTimeout = function (id) {
				const rid = native.clearTimeout.call(context, id);
				it.completes('to' + id);
				return rid;
			};
		}

		#handleRequestAnimationFrame({ native, context }) {
			const it = this;
			native.requestAnimationFrame = context.requestAnimationFrame;
			context.requestAnimationFrame = function (fn) {
				const id = 'raf' + native.requestAnimationFrame.call(context, () => {
					try {
						fn.call(this);
					} finally {
						it.completes(id);
					}
				});
				it.waits(id);
				return id;
			};

			native.cancelAnimationFrame = context.cancelAnimationFrame;
			context.cancelAnimationFrame = function (id) {
				const rid = native.cancelAnimationFrame.call(context, id);
				it.completes('raf' + id);
				return rid;
			};
		}
		countme = 0;
		#handlePromise({ native, context }) {
			const it = this;
			native.Promise = context.Promise;
			context.Promise = class Promise extends native.Promise {
				#id;
				static get [Symbol.species]() {
					return Promise;
				}
				get [Symbol.toStringTag]() {
					return 'Promise';
				}
				constructor(fn) {
					let id;
					if (!fn) super();
					else super((ok, fail) => {
						fn(v => {
							ok(v);
							it.completes(this.#id);
						}, e => {
							fail(e);
							it.completes(this.#id);
						});
						id = it.creates('promise');
					});
					this.#id = id;
				}
			};
		}

		waits(id) {
			if (id == null) return;
			this.#sources.add(id);
			return id;
		}
		creates(prefix) {
			if (prefix == null) return;
			const id = `${prefix}${this.#count++}`;
			this.#sources.add(id);
			return id;
		}

		completes(id) {
			if (id == null) return;
			const { context, native } = this.#tracks[0];
			native.queueMicrotask.call(context, () => {
				if (this.#sources.delete(id)) {
					if (this.#sources.size === 0) {
						this.#resolve("idle");
					}
				}
			});
		}

		destroy() {
			for (const { native, context } of this.#tracks) {
				for (const [name, prim] of Object.entries(native)) {
					context[name] = prim;
				}
			}
			this.#tracks = [];
			this.#sources = [];
		}
	}

	// https://playwright.dev/docs/api/class-page#page-route
	delete window.navigator.serviceWorker;

	const tracker = new AsyncTracker();

	// tracker must at least wait for this
	document.addEventListener('DOMContentLoaded', () => { });

	Object.defineProperty(window, settings.id, {
		enumerable: false,
		configurable: false,
		writable: false,
		value: tracker.promise
	});
};
<commit_msg>fetch: completes after returning<commit_after>module.exports = function (settings) {

	class Track {
		native = {};
		context;
		constructor(context) {
			this.context = context;
		}
	}

	class AsyncTracker {
		#tracks = [];
		#sources = new Set();
		#count = 0;
		#resolve;

		constructor(contexts = [window]) {
			this.promise = new Promise(ok => {
				this.#resolve = ok;
			});

			for (const context of contexts) {
				const track = new Track(context);
				this.#tracks.push(track);

				this.#handleMicrotask(track);
				this.#handleRequestAnimationFrame(track);
				this.#handleTimeout(track);
				this.#handlePromise(track);
				this.#handleReady(track);
				this.#handleFetch(track);
				this.#handleXHR(track);
				this.#handleNodes(track);
			}
		}

		#handleNode(node) {
			const id = this.waits(node.nodeName + ' ' + (node.src ?? node.href));
			const itc = () => {
				node.removeEventListener('load', itc);
				node.removeEventListener('error', itc);
				this.completes(id);
			};
			node.addEventListener('load', itc);
			node.addEventListener('error', itc);
		}

		#unhandleNode(node) {
			this.completes(node.nodeName + ' ' + (node.src ?? node.href));
		}

		#handleNodes({ context }) {
			function check(node) {
				if (node.nodeType != Node.ELEMENT_NODE) return;
				const tag = node.nodeName;
				if (tag == "SCRIPT") {
					return node.src && (!node.type || node.type == "text/javascript" || node.type == "module");
				} else if (tag == "LINK") {
					return node.href && node.rel == "stylesheet";
				}
			}
			const observer = new MutationObserver(mutations => {
				for (const { addedNodes, removedNodes } of mutations) {
					if (addedNodes) for (const node of addedNodes) {
						if (check(node)) this.#handleNode(node);
					}
					if (removedNodes) for (const node of removedNodes) {
						if (check(node)) this.#unhandleNode(node);
					}
				}
			});
			observer.observe(context.document, {
				childList: true,
				subtree: true
			});
		}

		#handleXHR({ native, context }) {
			const it = this;
			native.XMLHttpRequest = context.XMLHttpRequest;
			context.XMLHttpRequest = class XMLHttpRequest extends native.XMLHttpRequest {
				#id;
				#to;
				#done;
				constructor() {
					super();
					this.#done = () => {
						if (this.#to != null) {
							native.clearTimeout.call(context, this.#to);
							this.#to = null;
						}
						this.removeEventListener('abort', this.#done);
						this.removeEventListener('error', this.#done);
						this.removeEventListener('load', this.#done);
						it.completes(this.#id);
					};
				}
				static get [Symbol.species]() {
					return XMLHttpRequest;
				}
				get [Symbol.toStringTag]() {
					return 'XMLHttpRequest';
				}
				send(...args) {
					const ret = super.send(...args);
					this.addEventListener('abort', this.#done);
					this.addEventListener('error', this.#done);
					this.addEventListener('load', this.#done);
					this.#to = native.setTimeout.call(context, this.#done, settings.stall);
					this.#id = it.creates('xhr');
					return ret;
				}
			};
		}

		#handleFetch({ native, context }) {
			const it = this;
			native.fetch = context.fetch;
			context.fetch = function fetch(...args) {
				const id = it.creates('fetch');
				try {
					const r = native.fetch.apply(context, args);
					r.finally(() => it.completes(id));
					return r;
				} catch (err) {
					it.completes(id);
				}
			};
		}

		#handleReady({ native, context }) {
			const it = this;
			const { document: doc } = context;
			native.removeEventListener = doc.removeEventListener;
			native.addEventListener = doc.addEventListener;
			native.bubbles = new Map();
			native.captures = new Map();
			class EventWatch {
				constructor(name, fn) {
					this.id = it.creates(doc.readyState == "loading" ? name : null);
					this.fn = fn;
				}
				async handleEvent(e) {
					try {
						if (this.fn.handleEvent) await this.fn.handleEvent(e);
						else await this.fn(e);
					} finally {
						it.completes(this.id);
						this.id = null;
					}
				}
			}
			doc.addEventListener = function (name, fn, cap) {
				if (name != "DOMContentLoaded") {
					return native.addEventListener.call(doc, name, fn, cap);
				}
				const eMap = cap ? native.captures : native.bubbles;
				if (eMap.has(fn)) return;
				const watch = new EventWatch(name, fn);
				eMap.set(fn, watch);
				return native.addEventListener.call(doc, name, watch, cap);
			};
			doc.removeEventListener = function (name, fn, cap) {
				if (name != "DOMContentLoaded") {
					return native.removeEventListener.call(doc, name, fn, cap);
				}
				const eMap = cap ? native.captures : native.bubbles;
				const watch = eMap.get(fn);
				if (!watch) return;
				it.completes(watch.id);
				return native.removeEventListener.call(doc, name, watch, cap);
			};
		}

		#handleMicrotask({ native, context }) {
			const it = this;
			native.queueMicrotask = context.queueMicrotask;
			context.queueMicrotask = function (fn) {
				const id = it.creates('task');
				native.queueMicrotask.call(context, () => {
					try {
						fn.call(this);
					} finally {
						it.completes(id);
					}
				});
			};
		}

		#handleTimeout({ native, context }) {
			const it = this;
			native.setTimeout = context.setTimeout;
			context.setTimeout = function (fn, to) {
				const id = 'to' + native.setTimeout.call(context, () => {
					try {
						fn.call(this);
					} finally {
						it.completes(id);
					}
				}, to);
				it.waits(id);
			};

			native.clearTimeout = context.clearTimeout;
			context.clearTimeout = function (id) {
				const rid = native.clearTimeout.call(context, id);
				it.completes('to' + id);
				return rid;
			};
		}

		#handleRequestAnimationFrame({ native, context }) {
			const it = this;
			native.requestAnimationFrame = context.requestAnimationFrame;
			context.requestAnimationFrame = function (fn) {
				const id = 'raf' + native.requestAnimationFrame.call(context, () => {
					try {
						fn.call(this);
					} finally {
						it.completes(id);
					}
				});
				it.waits(id);
				return id;
			};

			native.cancelAnimationFrame = context.cancelAnimationFrame;
			context.cancelAnimationFrame = function (id) {
				const rid = native.cancelAnimationFrame.call(context, id);
				it.completes('raf' + id);
				return rid;
			};
		}
		countme = 0;
		#handlePromise({ native, context }) {
			const it = this;
			native.Promise = context.Promise;
			context.Promise = class Promise extends native.Promise {
				#id;
				static get [Symbol.species]() {
					return Promise;
				}
				get [Symbol.toStringTag]() {
					return 'Promise';
				}
				constructor(fn) {
					let id;
					if (!fn) super();
					else super((ok, fail) => {
						fn(v => {
							ok(v);
							it.completes(this.#id);
						}, e => {
							fail(e);
							it.completes(this.#id);
						});
						id = it.creates('promise');
					});
					this.#id = id;
				}
			};
		}

		waits(id) {
			if (id == null) return;
			this.#sources.add(id);
			return id;
		}
		creates(prefix) {
			if (prefix == null) return;
			const id = `${prefix}${this.#count++}`;
			this.#sources.add(id);
			return id;
		}

		completes(id) {
			if (id == null) return;
			const { context, native } = this.#tracks[0];
			native.queueMicrotask.call(context, () => {
				if (this.#sources.delete(id)) {
					if (this.#sources.size === 0) {
						this.#resolve("idle");
					}
				}
			});
		}

		destroy() {
			for (const { native, context } of this.#tracks) {
				for (const [name, prim] of Object.entries(native)) {
					context[name] = prim;
				}
			}
			this.#tracks = [];
			this.#sources = [];
		}
	}

	// https://playwright.dev/docs/api/class-page#page-route
	delete window.navigator.serviceWorker;

	const tracker = new AsyncTracker();

	// tracker must at least wait for this
	document.addEventListener('DOMContentLoaded', () => { });

	Object.defineProperty(window, settings.id, {
		enumerable: false,
		configurable: false,
		writable: false,
		value: tracker.promise
	});
};
<|endoftext|>


<commit_before>var util        = require('util');
var _           = require('lodash');
var inflection  = require('inflection');
var merge       = require('object-merge');

function Treeize(options) {
  this.baseOptions = {
    input: {
      delimiter:          ':',
      detectCollections:  true,
      uniformRows:        false,
    },
    output: {
      prune:              true,
      objectOverwrite:    true,
      resultsAsObject:    false,
    },
    log:                  false,
  };

  this.data = {
    signature: {
      nodes: [],
      type: null,
    },
    seed: [],
    tree: [],
  };

  this.stats = {
    time:     {
      total:      0,
      signatures: 0,
    },
    rows:         0,
    sources:      0,
  };

  // set default options (below)
  this.resetOptions();

  if (options) {
    this.options(options);
  }

  return this;
}

Treeize.prototype.log = function() {
  if (this._options.log) {
    console.log.apply(this, arguments);
  }

  return this;
};

Treeize.prototype.getData = function() {
  return this.data.tree;
};

Treeize.prototype.getSeedData = function() {
  return this.data.seed;
};

Treeize.prototype.getStats = function() {
  return this.stats;
};

/*
  Reads the signature from a given row to determine path mapping.  If passed without params, assumes
  a forced reading which will last
 */
Treeize.prototype.signature = function(row, options, auto) {
  if (!row) {
    return this.data.signature;
  }

  // start timer
  var t1 = (new Date()).getTime();

  // sets the signature as fixed (or not) when manually set
  this.data.signature.isFixed = auto !== true;

  var nodes         = this.data.signature.nodes = [];
  var isArray       = _.isArray(row);
  var opt           = merge(this._options, options || {});

  this.data.signature.type = isArray ? 'array' : 'object';

  _.each(row, function(value, key) {
    // set up attribute
    var attr        = {}

    attr.key        = typeof key === 'number' ? key : key;//.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
    attr.fullPath   = isArray ? value : key;
    attr.split      = attr.fullPath.split(opt.input.delimiter);
    attr.path       = attr.split.slice(0,attr.split.length-1).join(opt.input.delimiter);
    attr.parent     = attr.split.slice(0,attr.split.length-2).join(opt.input.delimiter);//.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
    attr.node       = attr.split[attr.split.length - 2];
    attr.attr       = attr.split[attr.split.length - 1];

    if (attr.attr.match(/\*/gi)) {
      attr.attr = attr.attr.replace(/[\*]/gi,'');
      attr.pk = true;
    }

    if (attr.pk) {
      this.log('primary key detected in node "' + attr.attr + '"');
    }

    // set up node reference
    var node = _.findWhere(nodes, { path: attr.path });
    if (!node) {
      node = { path: attr.path, attributes: [], blueprint: [] };
      nodes.push(node);
    }

    node.isCollection = !attr.node || (opt.input.detectCollections && inflection.pluralize(attr.node) === attr.node);

    var collectionFlag = attr.node && attr.node.match(/^[\-\+]|[\-\+]$/g);
    if (collectionFlag) {
      this.log('collection flag "' + collectionFlag + '" detected in node "' + attr.node + '"');
      node.flags = true;
      node.isCollection = attr.node.match(/^\+|\+$/g);
      attr.node = attr.node.replace(/^[\*\-\+]|[\*\-\+]$/g,''); // clean node
    }

    node.name         = attr.node;
    node.depth        = attr.split.length - 1;
    node.parent       = attr.split.slice(0, attr.split.length - 2).join(opt.input.delimiter);
    node.attributes.push({ name: attr.attr, key: attr.key });
    if (attr.pk) {
      this.log('adding node to blueprint');
      node.flags = true;
      node.blueprint.push({ name: attr.attr, key: attr.key });
    }
  }, this);

  // backfill blueprint when not specifically defined
  nodes.forEach(function(node) {
    if (!node.blueprint.length) {
      node.blueprint = node.attributes;
    }
  });

  nodes.sort(function(a, b) { return a.depth < b.depth ? -1 : 1; });

  // end timer and add time
  var t2 = ((new Date()).getTime() - t1);
  this.stats.time.signatures += t2;
  this.stats.time.total += t2;

  return this;
};

Treeize.prototype.getSignature = function() {
  return this.signature();
};

Treeize.prototype.setSignature = function(row, options) {
  return this.signature(row, options);
};

Treeize.prototype.setSignatureAuto = function(row, options) {
  return this.signature(row, options, true);
};

Treeize.prototype.clearSignature = function() {
  this.data.signature = { nodes: [], type: null };
  this.data.signature.isFixed = false;

  return this;
};

Treeize.prototype.grow = function(data, options) {
  var opt = merge(this._options, options || {});

  // chain past if no data to grow
  if (!data || !_.isArray(data) || !data.length) {
    return this;
  }

  this.log('OPTIONS>', opt);

  // locate existing signature (when sharing signatures between data sources)
  var signature = this.getSignature();

  // set data uniformity (locally) to true to avoid signature fetching on data rows
  if (_.isArray(data[0])) {
    opt.input.uniformRows = true;
  }

  if (!signature.nodes.length) {
    this.log('setting signature from first row of data (auto)');
    // set signature from first row
    signature = this.setSignatureAuto(data[0], options).getSignature();

    // remove header row in flat array data (avoids processing headers as actual values)
    if (_.isArray(data[0])) {
      var originalData = data;
      data = [];

      // copy data without original signature row before processing
      originalData.forEach(function(row, index) {
        if (index > 0) {
          data.push(row);
        }
      });
    }
  }

  if (opt.output.resultsAsObject && _.isArray(this.data.tree)) {
    this.data.tree = {};
  }

  this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null));

  this.stats.sources++;
  var t1 = (new Date()).getTime();

  data.forEach(function(row) {
    this.data.seed.push(row);
    var trails = {}; // LUT for trails (find parent of new node in trails path)
    var trail = root = this.data.tree; // OPTIMIZATION: do we need to reset this trail for each row?
    this.log('CURRENT TRAIL STATUS>', trail);
    var t = null;

    // set initial root object path for non-array datasets
    if (opt.output.resultsAsObject) {
      trails[''] = trail;
    }

    if (!this.data.signature.isFixed && !opt.input.uniformRows) {
      this.log('setting signature from new row of data (auto)');
      // get signature from each row
      this.setSignatureAuto(row, opt);
      this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null));
    }

    this.stats.rows++;

    if (_.where(this.signature().nodes, { flags: true }).length) {
      // flags detected within signature, clean attributes of row
      _.each(row, function(value, key) {
        if (typeof key === 'string') {
          var clean = key.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
          if (clean !== key) {
            this.log('cleaning key "' + key + '" and embedding as "' + clean + '"');
            row[key.replace(/^[\*\-\+]|[\*\-\+]$/g,'')] = value; // simply embed value at clean path (if not already)
          }
        }
      }, this);
    }

    this.signature().nodes.forEach(function(node) {
      this.log('PROCESSING NODE>', node);
      var blueprint = {};
      var blueprintExtended = {};

      // create blueprint for locating existing nodes
      node.blueprint.forEach(function(attribute) {
        var key = (node.path ? (node.path + ':') : '') + attribute.name;
        blueprint[attribute.name] = row[attribute.key];
        this.log('creating attribute "' + attribute.name + '" within blueprint', row[attribute.key]);
      }, this);

      // create full node signature for insertion/updating
      node.attributes.forEach(function(attribute) {
        var key = (node.path ? (node.path + ':') : '') + attribute.name;
        var value = row[attribute.key];

        // insert extended blueprint attributes when not empty (or not pruning)
        if (!opt.output.prune || (value !== null && value !== undefined)) {
          this.log('creating attribute "' + attribute.name + '" within extended blueprint', row[attribute.key]);
          blueprintExtended[attribute.name] = row[attribute.key];
        }
      }, this);

      this.log('EXTENDED BLUEPRINT>', blueprintExtended);
      this.log('BLUEPRINT>', blueprint);

      // ONLY INSERT IF NOT PRUNED
      if (!opt.output.prune || !_.isEmpty(blueprintExtended)) {
        // IF 0 DEPTH AND RESULTSASOBJECT, EXTEND ROOT
        if (opt.output.resultsAsObject && node.depth === 0) {
          _.extend(trails[node.path] = trail = root, blueprintExtended);
          this.log('extending blueprint onto root>', trail);

        // IF ROOT TRAIL IS NOT YET MAPPED
        } else if (node.isCollection && !(trail = trails[node.parent])) {
          this.log('PARENT TRAIL NOT FOUND (ROOT?)');
          // set up target node if doesn't exist
          if (!(trail = _.findWhere(root, blueprint))) {
            root.push(trail = blueprintExtended);
          } else {
            _.extend(trail, blueprintExtended);
          }
          trails[node.path] = trail;

        // NORMAL NODE TRAVERSAL
        } else {
          // NOT ROOT CASE
          if (node.isCollection) {
            // handle collection nodes
            this.log('inserting into collection node', trail);
            if (!trail[node.name]) {
              // node attribute doesnt exist, create array with fresh blueprint
              trail[node.name] = [blueprintExtended];
              trails[node.path] = blueprintExtended;
            } else {
              // node attribute exists, find or inject blueprint
              var t;
              if (!(t = _.findWhere(trail[node.name], blueprint))) {
                trail[node.name].push(trail = blueprintExtended);
              } else {
                _.extend(t, blueprintExtended);
              }
              trails[node.path] = t || trail;
            }
          } else {
            // handle non-collection nodes
            if (trail == root && node.parent === '') {
              root.push(trails[node.parent] = trail = {});
              this.log('root insertion');
            }
            trail = trails[node.parent];

            // ON DEEP NODES, THE PARENT WILL BE TOO LONG AND FAIL ON THE NEXT IF STATEMENT BELOW
            // ASSUMPTION: in deep nodes, no signatures will be present, so entries will simply be pushed onto collections defined within

            if (!trail) { // do something to fix a broken trail (usually from too deep?)
              // backtrack from parent trail segments until trail found, then create creadcrumbs
              var breadcrumbs = [];
              var segments = node.parent.split(':');
              var pathAttempt = node.parent;
              var segmentsStripped = 0;

              this.log('path MISSING for location "' + pathAttempt + '"');
              while (!(trail = trails[pathAttempt])) {
                segmentsStripped++;
                pathAttempt = _.initial(segments, segmentsStripped).join(':');
                this.log('..attempting path location for "' + pathAttempt + '"');

                //infinite loop kickout
                if (segmentsStripped > 15) break;
              }
              this.log('path FOUND for location for "' + pathAttempt + '" after removing ' + segmentsStripped + ' segments');

              // create stored nodes if they don't exist.
              _.each(_.rest(segments, segments.length - segmentsStripped), function(segment) {
                var isCollection = ((inflection.pluralize(segment) === segment) || segment.match(/^\+|\+$/)) && (!segment.match(/^\-|\-$/));
                // TODO: add modifier detection
                this.log('creating or trailing path segment ' + (isCollection ? '[collection]' : '{object}') + ' "' + segment + '"');

                segment = segment.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
                if (isCollection) {
                  // retrieve or set collection segment and push new trail onto it
                  (trail[segment] = trail[segment] || []).push(trail = {});
                } else {
                  trail = trail[segment] = trail[segment] || {};
                }
              }, this);
            }

            this.log('inserting into non-collection node');
            //if (!trail[node.name]) { // TODO: CONSIDER: add typeof check to this for possible overwriting
            if (!trail[node.name] || (opt.output.objectOverwrite && (typeof trail[node.name] !== typeof blueprintExtended))) {
              // node attribute doesnt exist, create object
              this.log('create object');
              trail[node.name] = blueprintExtended;
              trails[node.path] = blueprintExtended;
            } else {
              // node attribute exists, set path for next pass
              // TODO: extend trail??
              this.log('object at node "' + node.name + '" exists as "' + trail[node.name] + '", skipping insertion and adding trail');
              if (typeof trail[node.name] === 'object') {
                trail[node.name] = merge(trail[node.name], blueprintExtended);
              }
              this.log('trail[node.name] updated to "' + trail[node.name]);
              trails[node.path] = trail[node.path];
            }
          }
        }
        // END PRUNE PASS
      }
    }, this);
  }, this);

  var t2 = ((new Date()).getTime() - t1);
  this.stats.time.total += t2;

  // clear signature between growth sets - TODO: consider leaving this wipe pass off if processing multiple identical sources (add)
  if (!signature.isFixed) {
    this.signature([]);
  }

  return this;
};

/*
  .[get|set]options (options)

  Get and sets global options.
 */

Treeize.prototype.options = function(options) {
  if (!options) {
    return merge({}, this._options);
  }

  this._options = merge(this._options, options);

  return this;
};

Treeize.prototype.getOptions = function() {
  return this._options;
};

Treeize.prototype.setOptions = function(options) {
  return this.options(options);
};

Treeize.prototype.resetOptions = function() {
  this._options = merge({}, this.baseOptions);

  return this;
};

Treeize.prototype.toString = function treeToString() {
  return util.inspect(this.data.tree, false, null);
};

module.exports = Treeize;
<commit_msg>renamed "root" variable to "base" to avoid deprecation error<commit_after>var util        = require('util');
var _           = require('lodash');
var inflection  = require('inflection');
var merge       = require('object-merge');

function Treeize(options) {
  this.baseOptions = {
    input: {
      delimiter:          ':',
      detectCollections:  true,
      uniformRows:        false,
    },
    output: {
      prune:              true,
      objectOverwrite:    true,
      resultsAsObject:    false,
    },
    log:                  false,
  };

  this.data = {
    signature: {
      nodes: [],
      type: null,
    },
    seed: [],
    tree: [],
  };

  this.stats = {
    time:     {
      total:      0,
      signatures: 0,
    },
    rows:         0,
    sources:      0,
  };

  // set default options (below)
  this.resetOptions();

  if (options) {
    this.options(options);
  }

  return this;
}

Treeize.prototype.log = function() {
  if (this._options.log) {
    console.log.apply(this, arguments);
  }

  return this;
};

Treeize.prototype.getData = function() {
  return this.data.tree;
};

Treeize.prototype.getSeedData = function() {
  return this.data.seed;
};

Treeize.prototype.getStats = function() {
  return this.stats;
};

/*
  Reads the signature from a given row to determine path mapping.  If passed without params, assumes
  a forced reading which will last
 */
Treeize.prototype.signature = function(row, options, auto) {
  if (!row) {
    return this.data.signature;
  }

  // start timer
  var t1 = (new Date()).getTime();

  // sets the signature as fixed (or not) when manually set
  this.data.signature.isFixed = auto !== true;

  var nodes         = this.data.signature.nodes = [];
  var isArray       = _.isArray(row);
  var opt           = merge(this._options, options || {});

  this.data.signature.type = isArray ? 'array' : 'object';

  _.each(row, function(value, key) {
    // set up attribute
    var attr        = {}

    attr.key        = typeof key === 'number' ? key : key;//.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
    attr.fullPath   = isArray ? value : key;
    attr.split      = attr.fullPath.split(opt.input.delimiter);
    attr.path       = attr.split.slice(0,attr.split.length-1).join(opt.input.delimiter);
    attr.parent     = attr.split.slice(0,attr.split.length-2).join(opt.input.delimiter);//.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
    attr.node       = attr.split[attr.split.length - 2];
    attr.attr       = attr.split[attr.split.length - 1];

    if (attr.attr.match(/\*/gi)) {
      attr.attr = attr.attr.replace(/[\*]/gi,'');
      attr.pk = true;
    }

    if (attr.pk) {
      this.log('primary key detected in node "' + attr.attr + '"');
    }

    // set up node reference
    var node = _.findWhere(nodes, { path: attr.path });
    if (!node) {
      node = { path: attr.path, attributes: [], blueprint: [] };
      nodes.push(node);
    }

    node.isCollection = !attr.node || (opt.input.detectCollections && inflection.pluralize(attr.node) === attr.node);

    var collectionFlag = attr.node && attr.node.match(/^[\-\+]|[\-\+]$/g);
    if (collectionFlag) {
      this.log('collection flag "' + collectionFlag + '" detected in node "' + attr.node + '"');
      node.flags = true;
      node.isCollection = attr.node.match(/^\+|\+$/g);
      attr.node = attr.node.replace(/^[\*\-\+]|[\*\-\+]$/g,''); // clean node
    }

    node.name         = attr.node;
    node.depth        = attr.split.length - 1;
    node.parent       = attr.split.slice(0, attr.split.length - 2).join(opt.input.delimiter);
    node.attributes.push({ name: attr.attr, key: attr.key });
    if (attr.pk) {
      this.log('adding node to blueprint');
      node.flags = true;
      node.blueprint.push({ name: attr.attr, key: attr.key });
    }
  }, this);

  // backfill blueprint when not specifically defined
  nodes.forEach(function(node) {
    if (!node.blueprint.length) {
      node.blueprint = node.attributes;
    }
  });

  nodes.sort(function(a, b) { return a.depth < b.depth ? -1 : 1; });

  // end timer and add time
  var t2 = ((new Date()).getTime() - t1);
  this.stats.time.signatures += t2;
  this.stats.time.total += t2;

  return this;
};

Treeize.prototype.getSignature = function() {
  return this.signature();
};

Treeize.prototype.setSignature = function(row, options) {
  return this.signature(row, options);
};

Treeize.prototype.setSignatureAuto = function(row, options) {
  return this.signature(row, options, true);
};

Treeize.prototype.clearSignature = function() {
  this.data.signature = { nodes: [], type: null };
  this.data.signature.isFixed = false;

  return this;
};

Treeize.prototype.grow = function(data, options) {
  var opt = merge(this._options, options || {});

  // chain past if no data to grow
  if (!data || !_.isArray(data) || !data.length) {
    return this;
  }

  this.log('OPTIONS>', opt);

  // locate existing signature (when sharing signatures between data sources)
  var signature = this.getSignature();

  // set data uniformity (locally) to true to avoid signature fetching on data rows
  if (_.isArray(data[0])) {
    opt.input.uniformRows = true;
  }

  if (!signature.nodes.length) {
    this.log('setting signature from first row of data (auto)');
    // set signature from first row
    signature = this.setSignatureAuto(data[0], options).getSignature();

    // remove header row in flat array data (avoids processing headers as actual values)
    if (_.isArray(data[0])) {
      var originalData = data;
      data = [];

      // copy data without original signature row before processing
      originalData.forEach(function(row, index) {
        if (index > 0) {
          data.push(row);
        }
      });
    }
  }

  if (opt.output.resultsAsObject && _.isArray(this.data.tree)) {
    this.data.tree = {};
  }

  this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null));

  this.stats.sources++;
  var t1 = (new Date()).getTime();

  data.forEach(function(row) {
    this.data.seed.push(row);
    var trails = {}; // LUT for trails (find parent of new node in trails path)
    var trail = base = this.data.tree; // OPTIMIZATION: do we need to reset this trail for each row?
    this.log('CURRENT TRAIL STATUS>', trail);
    var t = null;

    // set initial base object path for non-array datasets
    if (opt.output.resultsAsObject) {
      trails[''] = trail;
    }

    if (!this.data.signature.isFixed && !opt.input.uniformRows) {
      this.log('setting signature from new row of data (auto)');
      // get signature from each row
      this.setSignatureAuto(row, opt);
      this.log('SIGNATURE>', util.inspect(this.getSignature(), false, null));
    }

    this.stats.rows++;

    if (_.where(this.signature().nodes, { flags: true }).length) {
      // flags detected within signature, clean attributes of row
      _.each(row, function(value, key) {
        if (typeof key === 'string') {
          var clean = key.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
          if (clean !== key) {
            this.log('cleaning key "' + key + '" and embedding as "' + clean + '"');
            row[key.replace(/^[\*\-\+]|[\*\-\+]$/g,'')] = value; // simply embed value at clean path (if not already)
          }
        }
      }, this);
    }

    this.signature().nodes.forEach(function(node) {
      this.log('PROCESSING NODE>', node);
      var blueprint = {};
      var blueprintExtended = {};

      // create blueprint for locating existing nodes
      node.blueprint.forEach(function(attribute) {
        var key = (node.path ? (node.path + ':') : '') + attribute.name;
        blueprint[attribute.name] = row[attribute.key];
        this.log('creating attribute "' + attribute.name + '" within blueprint', row[attribute.key]);
      }, this);

      // create full node signature for insertion/updating
      node.attributes.forEach(function(attribute) {
        var key = (node.path ? (node.path + ':') : '') + attribute.name;
        var value = row[attribute.key];

        // insert extended blueprint attributes when not empty (or not pruning)
        if (!opt.output.prune || (value !== null && value !== undefined)) {
          this.log('creating attribute "' + attribute.name + '" within extended blueprint', row[attribute.key]);
          blueprintExtended[attribute.name] = row[attribute.key];
        }
      }, this);

      this.log('EXTENDED BLUEPRINT>', blueprintExtended);
      this.log('BLUEPRINT>', blueprint);

      // ONLY INSERT IF NOT PRUNED
      if (!opt.output.prune || !_.isEmpty(blueprintExtended)) {
        // IF 0 DEPTH AND RESULTSASOBJECT, EXTEND base
        if (opt.output.resultsAsObject && node.depth === 0) {
          _.extend(trails[node.path] = trail = base, blueprintExtended);
          this.log('extending blueprint onto base>', trail);

        // IF base TRAIL IS NOT YET MAPPED
        } else if (node.isCollection && !(trail = trails[node.parent])) {
          this.log('PARENT TRAIL NOT FOUND (base?)');
          // set up target node if doesn't exist
          if (!(trail = _.findWhere(base, blueprint))) {
            base.push(trail = blueprintExtended);
          } else {
            _.extend(trail, blueprintExtended);
          }
          trails[node.path] = trail;

        // NORMAL NODE TRAVERSAL
        } else {
          // NOT base CASE
          if (node.isCollection) {
            // handle collection nodes
            this.log('inserting into collection node', trail);
            if (!trail[node.name]) {
              // node attribute doesnt exist, create array with fresh blueprint
              trail[node.name] = [blueprintExtended];
              trails[node.path] = blueprintExtended;
            } else {
              // node attribute exists, find or inject blueprint
              var t;
              if (!(t = _.findWhere(trail[node.name], blueprint))) {
                trail[node.name].push(trail = blueprintExtended);
              } else {
                _.extend(t, blueprintExtended);
              }
              trails[node.path] = t || trail;
            }
          } else {
            // handle non-collection nodes
            if (trail == base && node.parent === '') {
              base.push(trails[node.parent] = trail = {});
              this.log('base insertion');
            }
            trail = trails[node.parent];

            // ON DEEP NODES, THE PARENT WILL BE TOO LONG AND FAIL ON THE NEXT IF STATEMENT BELOW
            // ASSUMPTION: in deep nodes, no signatures will be present, so entries will simply be pushed onto collections defined within

            if (!trail) { // do something to fix a broken trail (usually from too deep?)
              // backtrack from parent trail segments until trail found, then create creadcrumbs
              var breadcrumbs = [];
              var segments = node.parent.split(':');
              var pathAttempt = node.parent;
              var segmentsStripped = 0;

              this.log('path MISSING for location "' + pathAttempt + '"');
              while (!(trail = trails[pathAttempt])) {
                segmentsStripped++;
                pathAttempt = _.initial(segments, segmentsStripped).join(':');
                this.log('..attempting path location for "' + pathAttempt + '"');

                //infinite loop kickout
                if (segmentsStripped > 15) break;
              }
              this.log('path FOUND for location for "' + pathAttempt + '" after removing ' + segmentsStripped + ' segments');

              // create stored nodes if they don't exist.
              _.each(_.rest(segments, segments.length - segmentsStripped), function(segment) {
                var isCollection = ((inflection.pluralize(segment) === segment) || segment.match(/^\+|\+$/)) && (!segment.match(/^\-|\-$/));
                // TODO: add modifier detection
                this.log('creating or trailing path segment ' + (isCollection ? '[collection]' : '{object}') + ' "' + segment + '"');

                segment = segment.replace(/^[\*\-\+]|[\*\-\+]$/g,'');
                if (isCollection) {
                  // retrieve or set collection segment and push new trail onto it
                  (trail[segment] = trail[segment] || []).push(trail = {});
                } else {
                  trail = trail[segment] = trail[segment] || {};
                }
              }, this);
            }

            this.log('inserting into non-collection node');
            //if (!trail[node.name]) { // TODO: CONSIDER: add typeof check to this for possible overwriting
            if (!trail[node.name] || (opt.output.objectOverwrite && (typeof trail[node.name] !== typeof blueprintExtended))) {
              // node attribute doesnt exist, create object
              this.log('create object');
              trail[node.name] = blueprintExtended;
              trails[node.path] = blueprintExtended;
            } else {
              // node attribute exists, set path for next pass
              // TODO: extend trail??
              this.log('object at node "' + node.name + '" exists as "' + trail[node.name] + '", skipping insertion and adding trail');
              if (typeof trail[node.name] === 'object') {
                trail[node.name] = merge(trail[node.name], blueprintExtended);
              }
              this.log('trail[node.name] updated to "' + trail[node.name]);
              trails[node.path] = trail[node.path];
            }
          }
        }
        // END PRUNE PASS
      }
    }, this);
  }, this);

  var t2 = ((new Date()).getTime() - t1);
  this.stats.time.total += t2;

  // clear signature between growth sets - TODO: consider leaving this wipe pass off if processing multiple identical sources (add)
  if (!signature.isFixed) {
    this.signature([]);
  }

  return this;
};

/*
  .[get|set]options (options)

  Get and sets global options.
 */

Treeize.prototype.options = function(options) {
  if (!options) {
    return merge({}, this._options);
  }

  this._options = merge(this._options, options);

  return this;
};

Treeize.prototype.getOptions = function() {
  return this._options;
};

Treeize.prototype.setOptions = function(options) {
  return this.options(options);
};

Treeize.prototype.resetOptions = function() {
  this._options = merge({}, this.baseOptions);

  return this;
};

Treeize.prototype.toString = function treeToString() {
  return util.inspect(this.data.tree, false, null);
};

module.exports = Treeize;
<|endoftext|>


<commit_before>// Ce-lo dice game
// for the streets.
// the rules:
// there are three dice
// each player rolls the dice once.
// they must roll until a recongized pattern is found
// whichever player gets the bets pair wins.

// Patterns
// 4-5-6- highest
// Trips- triples of dice. higher triples wins
// Point- doubles with number. Higher single wins
// 1-2-3 - loses automatically

//prompts:
// comp or number of people playing?

var intro = function (){
    console.log ("Welcome to Celo Dice!\n");
};

var players = function (){
    //play against computer?
    console.log("\n Do you want to play against a computer or against a friend. Press 1 for computer, 2 for friend.");
    var opponent = prompt("Do you want to play against a computer or against a friend. Press 1 for computer, 2 for friend.");
    opponent =  Number(opponent);
    if (opponent === 1){
        console.log("You are playing against the computer: Hal 9000");
    } else if (opponent === 2){
        console.log("You are playing against a human, player 2");
    } else {
        console.log("enter a either 1 for the computer or 2 for another human");
    }
};

var game_mech = function (){

};

var rules_Explain = function (){

    console.log("See rules? Yes or No");
    var see_Rules = prompt ("See rules? Yes or No");
    see_Rules =  see_Rules.toUpperCase();

    //rules
    if (see_Rules === "YES" || see_Rules === "Y"){
        console.log ("\n The Rules:\n There are three dice.");
        console.log (" Each player rolls once.");
        console.log (" Each player must roll until a recongized pattern is  found.\n");
        console.log ("The Patterns: \n 4-5-6-: highest pattern");
    console.log (" Trips: triples of dice. higher triples wins");
    console.log (" Point: doubles with number. Higher single wins");
    console.log (" 1-2-3: loses automatically");

    } else if (see_Rules === "NO" || see_Rules === "N") {

        // enter game
        console.log("\nOk, let's play!");

    } else {
        console.log("Yo, pick a valid entry");
        rules_Explain();
    }
};

var game = function()  {
    intro();
    rules_Explain();
    players();
};

// start game.
game();

<commit_msg>added dice_roll function inside game_mech function in digital celo<commit_after>// Ce-lo dice game
// for the streets.
// the rules:
// there are three dice
// each player rolls the dice once.
// they must roll until a recongized pattern is found
// whichever player gets the bets pair wins.

// Patterns
// 4-5-6- highest
// Trips- triples of dice. higher triples wins
// Point- doubles with number. Higher single wins
// 1-2-3 - loses automatically

//prompts:
// comp or number of people playing?

var intro = function (){
    console.log ("Welcome to Celo Dice!\n");
};

var players = function (){
    //play against computer?
    console.log("\n Do you want to play against a computer or against a friend. Press 1 for computer, 2 for friend.");
    var opponent = prompt("Do you want to play against a computer or against a friend. Press 1 for computer, 2 for friend.");
    opponent =  Number(opponent);
    if (opponent === 1){
        console.log("You are playing against the computer: Hal 9000");
    } else if (opponent === 2){
        console.log("You are playing against a human, player 2");
    } else {
        console.log("enter a either 1 for the computer or 2 for another human");
    }
};

var game_mech = function () {
        dicePattern = [];
        for (var x = 0; x < 3; x += 1){
            var dice = Math.floor((Math.random() * 6 ) + 1);
            dicePattern.push(dice);
        }
        dicePattern = dicePattern.join("");
        // test to output
        console.log("game mech dicePattern " + dicePattern);

        // return dice pattern to pattern function.
        return pattern_ID(dicePattern);
    };

var pattern_ID = function (dicePattern){
    // test to output
    console.log("patter_ID dicePattern " + dicePattern);

    //what is best way to hardcode pattern?

};

var rules_Explain = function (){

    console.log("See rules? Yes or No");
    var see_Rules = prompt ("See rules? Yes or No");
    see_Rules =  see_Rules.toUpperCase();

    //rules
    if (see_Rules === "YES" || see_Rules === "Y"){
        console.log ("\n The Rules:\n There are three dice.");
        console.log (" Each player rolls once.");
        console.log (" Each player must roll until a recongized pattern is  found.\n");
        console.log ("The Patterns: \n 4-5-6-: highest pattern.");
    console.log (" Trips: Triples of dice. The higher triples wins.");
    console.log (" Point: Doubles with a single number. The higher single wins.");
    console.log (" 1-2-3: loses automatically.");

    } else if (see_Rules === "NO" || see_Rules === "N") {

        // enter game
        console.log("\nOk, let's play!");

    } else {
        console.log("Yo, pick a valid entry");
        rules_Explain();
    }
};

var game = function()  {
    intro();
    rules_Explain();
    players();
    game_mech();
};

// start game.
game();

<|endoftext|>


<commit_before>'use strict';

let events = require('events');
let _ = require('lodash');
let slugify = require('./slugify');
let Promise = require('bluebird');

class DigsEmitter extends events.EventEmitter {

  constructor() {
    super();

    createId(this);
    createValidatorProxies(this);
  }

  toString() {
    return `<${this.constructor.name}#${this.id}>`;
  }

  when(fulfilledEvent, rejectedEvent) {
    rejectedEvent = rejectedEvent || 'error';
    debug(`${this}: Waiting for event "${fulfilledEvent}"...`);
    return new Promise(function(resolve, reject) {
      this.once(fulfilledEvent, function(data) {
        this.removeListener(rejectedEvent, reject);
        resolve(data);
      })
        .once(rejectedEvent, reject);
    }.bind(this));
  }

  /**
   * Create a DigsEmitter instance from raw object
   * @param {Object} prototype Some object
   * @returns {DigsEmitter} DigsEmitter instance
   */
  static create(prototype) {
    let obj = _.create(DigsEmitter.prototype, prototype);
    createId(obj);
    createValidatorProxies(obj);
    return obj;
  }
}

function createId(ctx) {
  let name = slugify(ctx.constructor.name);
  ctx.id = ctx.id || _.uniqueId(`${name}-`);
  return ctx;
}

function createValidatorProxies(ctx) {
  let assertParams = require('./digs-util').assertParams;

  _.each(_.functions(ctx), function(funcName) {
    let schemata;
    let func = this[funcName];
    if (_.has(this, funcName) && _.isArray((schemata = func.schemata))) {
      this[funcName] = function validatorProxy() {
        assertParams(arguments, schemata);
        return func.apply(this, arguments);
      };
    }
  }, ctx);

  return ctx;
}

module.exports = DigsEmitter;
<commit_msg>fix debug<commit_after>'use strict';

let events = require('events');
let _ = require('lodash');
let slugify = require('./slugify');
let Promise = require('bluebird');
let debug = require('debug')('digs:digs-common:digs-emitter');

class DigsEmitter extends events.EventEmitter {

  constructor() {
    super();

    createId(this);
    createValidatorProxies(this);
  }

  toString() {
    return `<${this.constructor.name}#${this.id}>`;
  }

  when(fulfilledEvent, rejectedEvent) {
    rejectedEvent = rejectedEvent || 'error';
    debug(`${this}: Waiting for event "${fulfilledEvent}"...`);
    return new Promise(function(resolve, reject) {
      this.once(fulfilledEvent, function(data) {
        this.removeListener(rejectedEvent, reject);
        resolve(data);
      })
        .once(rejectedEvent, reject);
    }.bind(this));
  }

  /**
   * Create a DigsEmitter instance from raw object
   * @param {Object} prototype Some object
   * @returns {DigsEmitter} DigsEmitter instance
   */
  static create(prototype) {
    let obj = _.create(DigsEmitter.prototype, prototype);
    createId(obj);
    createValidatorProxies(obj);
    return obj;
  }
}

function createId(ctx) {
  let name = slugify(ctx.constructor.name);
  ctx.id = ctx.id || _.uniqueId(`${name}-`);
  return ctx;
}

function createValidatorProxies(ctx) {
  let assertParams = require('./digs-util').assertParams;

  _.each(_.functions(ctx), function(funcName) {
    let schemata;
    let func = this[funcName];
    if (_.has(this, funcName) && _.isArray((schemata = func.schemata))) {
      this[funcName] = function validatorProxy() {
        assertParams(arguments, schemata);
        return func.apply(this, arguments);
      };
    }
  }, ctx);

  return ctx;
}

module.exports = DigsEmitter;
<|endoftext|>