<commit_before>/*jshint node:true*/
"use strict";

var queryHelpers = require("./query"),

	mongoose = require("mongoose"),
	CastError = mongoose.SchemaType.CastError,
	DocumentArray = mongoose.Types.DocumentArray;

/*!
 * Misc helpers
 */

function getObject(req, item) {
	if (typeof item.toObject === "function") {
		return item.toObject(req.options.toObject);
	} else {
		return item;
	}
}

/*!
 * Document resource handlers
 */

function mongooseCollCount(req, cb) {
	var query = req.options.query();

	if (req.param("query")) {
		// Cache query operator
		if (!req._queryOperator) {
			req._queryOperator = queryHelpers.create(req.param("query"));
		}

		query = query.find(req._queryOperator);
	}

	query.count(cb);
}

function mongooseCollList(req, offset, limit, cb) {
	var options = req.options;
	var query = options.query();

	if (req.param("query")) {
		// Cache query operator
		if (!req._queryOperator) {
			req._queryOperator = queryHelpers.create(req.param("query"));
		}

		query = query.find(req._queryOperator);
	}

	query = query.skip(offset).limit(limit);

	if (options.sort) {
		query = query.sort(options.sort);
	}

	return query.exec(function(err, items) {
		if (err) {
			cb(err);
		} else {
			cb(null, items.map(function(item) {
				var obj = getObject(req, item);
				return obj;
			}));
		}
	});
}

function mongooseCollPost(req, cb) {
	var model = req.mongoose.model;

	model.create(req.body, function(err) {
		if (err) {
			cb(err);
		} else {
			cb.created();
		}
	});
}

/*!
 * Document resource handlers
 */

function mongooseDocHook(req, next) {
	var options = req.options;

	var crit = {};
	crit[options.key] = req.params.id;
	req.mongoose.path += "/" + req.params.id;

	options.query().find(crit).findOne(function(err, item) {
		if (err instanceof CastError || !item) {
			return next.notFound();
		}

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

		req.mongoose.doc = item;
		next();
	});
}

function mongooseDocGet(req, cb) {
	cb(null, getObject(req, req.mongoose.doc));
}

function mongooseDocPut(req, isPatch, cb) {
	var doc = req.mongoose.doc;

	doc.set(req.body);
	doc.save(function(err) {
		cb(err);
	});
}

function mongooseDocDel(req, cb) {
	req.mongoose.doc.remove(function(err) {
		cb(err);
	});
}

/*!
 * Document path resource handlers
 */

function mongoosePathHook(req, next) {
	var doc = req.mongoose.doc;
	var docpath = req.mongoose.path;
	var subkeys = req.options.subkeys;

	var path = req.params["*"];
	var parts = path.split("/");

	var fullpath = docpath;
	var current = doc;
	var parent = doc;
	var link = {};

	while(parts.length > 0) {
		var part = parts.shift();
		fullpath += "/" + part;

		if (current instanceof DocumentArray) {
			parent = current;

			var key = "_id";
			if (subkeys) {
				if (typeof subkeys === "string") {
					key = subkeys;
				} else {
					Object.keys(subkeys).forEach(function(pattern) {
						if (req.match(pattern, fullpath)) {
							key = subkeys[pattern];
						}
					});
				}
			}

			if (key !== "_id") {
				current = current.filter(function(item) {
					return item[key] === part;
				})[0];
				link = { id: current._id };
			} else {
				current = current.id(part);
				link = { id: part };
			}
		} else {
			if ("field" in link) {
				link.field += "." + part;
			} else {
				parent = current;
				link = { field: part };
			}

			current = parent.get(link.field);
		}

		if (!current) {
			return next.notFound();
		}
	}

	req.mongoose.parent = parent;
	req.mongoose.item = current;
	req.mongoose.link = link;

	next();
}

function mongoosePathGet(req, cb) {
	var item = req.mongoose.item;

	if (item instanceof DocumentArray) {
		cb.list(mongooseDocArrayCount, mongooseDocArrayList);
	} else {
		cb(null, getObject(req, item));
	}
}

function mongoosePathPut(req, isPatch, cb) {
	var parent = req.mongoose.parent;
	var link = req.mongoose.link;
	var doc = req.mongoose.doc;

	if ("id" in link) {
		parent.id(link.id).set(req.body);
	} else if ("field" in link) {
		parent.set(link.field, req.body._value);
	} else {
		return cb(new Error("Unknown link type"));
	}

	doc.save(function(err) {
		cb(err);
	});
}

function mongoosePathDel(req, cb) {
	var parent = req.mongoose.parent;
	var link = req.mongoose.link;
	var doc = req.mongoose.doc;

	if ("id" in link) {
		parent.splice(parent.indexOf(parent.id(link.id)), 1);
	} else if ("field" in link) {
		parent.set(link.field, undefined);
	} else {
		return cb(new Error("Unknown link type"));
	}

	doc.save(function(err) {
		cb(err);
	});
}

function mongoosePathPost(req, cb) {
	var item = req.mongoose.item;

	if (!(item instanceof DocumentArray)) {
		return cb.methodNotAllowed();
	}

	mongooseDocArrayPost(req, cb);
}

/*!
 * Mongoose DocumentArray helpers
 */

function queryDocArray(req) {
	var docArray = req.mongoose.item;

	if (req.param("query")) {
		// Cache query result
		if (!req.mongoose._queryResult) {
			req.mongoose._queryResult = docArray.filter(
				queryHelpers.match.bind(
					null,
					queryHelpers.create(req.param("query"))
				)
			);
		}

		return req.mongoose._queryResult;
	} else {
		return docArray;
	}
}

function mongooseDocArrayCount(req, cb) {
	var len = queryDocArray(req).length;
	cb(null, len);
}

function mongooseDocArrayList(req, offset, limit, cb) {
	var items = queryDocArray(req);

	if (limit > 0) {
		items = items.slice(offset, offset + limit);
	} else {
		items = items.slice(offset);
	}

	cb(null, items);
}

function mongooseDocArrayPost(req, cb) {
	var docArray = req.mongoose.item;
	var doc = req.mongoose.doc;
	var index = NaN;

	if (req.param("index")) {
		index = Number(req.param("index"));
	}

	if (isNaN(index)) {
		index = docArray.length;
	}

	docArray.splice(Math.max(0, Math.min(docArray.length, index)), 0, req.body);

	doc.save(function(err) {
		if (err) {
			cb(err);
		} else {
			cb.created();
		}
	});
}

/*!
 * Mongoose resource definition helper
 */

function mongooseResource(name, Model) {
	var collResource = this.sub(name)
		.hook(function modelHook(req, next) {
			req.mongoose = { model: Model, path: name };
			next();
		})
		.count(mongooseCollCount)
		.list(mongooseCollList)
		.post(mongooseCollPost)
		.set("query", function mongooseDefaultQuery() { return Model.find(); })
		.set("key", "_id");

	var docResource = collResource.sub(":id")
		.hook(mongooseDocHook)
		.get(mongooseDocGet)
		.put(mongooseDocPut)
		.del(mongooseDocDel);

	docResource.sub("*", mongoosePathHook)
		.get(mongoosePathGet)
		.put(mongoosePathPut)
		.del(mongoosePathDel)
		.post(mongoosePathPost);

	return collResource;
}

module.exports = mongooseResource;
<commit_msg>Enable _value in mongoose PUT<commit_after>/*jshint node:true*/
"use strict";

var queryHelpers = require("./query"),

	mongoose = require("mongoose"),
	CastError = mongoose.SchemaType.CastError,
	DocumentArray = mongoose.Types.DocumentArray;

/*!
 * Misc helpers
 */

function getObject(req, item) {
	if (typeof item.toObject === "function") {
		return item.toObject(req.options.toObject);
	} else {
		return item;
	}
}

/*!
 * Document resource handlers
 */

function mongooseCollCount(req, cb) {
	var query = req.options.query();

	if (req.param("query")) {
		// Cache query operator
		if (!req._queryOperator) {
			req._queryOperator = queryHelpers.create(req.param("query"));
		}

		query = query.find(req._queryOperator);
	}

	query.count(cb);
}

function mongooseCollList(req, offset, limit, cb) {
	var options = req.options;
	var query = options.query();

	if (req.param("query")) {
		// Cache query operator
		if (!req._queryOperator) {
			req._queryOperator = queryHelpers.create(req.param("query"));
		}

		query = query.find(req._queryOperator);
	}

	query = query.skip(offset).limit(limit);

	if (options.sort) {
		query = query.sort(options.sort);
	}

	return query.exec(function(err, items) {
		if (err) {
			cb(err);
		} else {
			cb(null, items.map(function(item) {
				var obj = getObject(req, item);
				return obj;
			}));
		}
	});
}

function mongooseCollPost(req, cb) {
	var model = req.mongoose.model;

	model.create(req.body, function(err) {
		if (err) {
			cb(err);
		} else {
			cb.created();
		}
	});
}

/*!
 * Document resource handlers
 */

function mongooseDocHook(req, next) {
	var options = req.options;

	var crit = {};
	crit[options.key] = req.params.id;
	req.mongoose.path += "/" + req.params.id;

	options.query().find(crit).findOne(function(err, item) {
		if (err instanceof CastError || !item) {
			return next.notFound();
		}

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

		req.mongoose.doc = item;
		next();
	});
}

function mongooseDocGet(req, cb) {
	cb(null, getObject(req, req.mongoose.doc));
}

function mongooseDocPut(req, isPatch, cb) {
	var doc = req.mongoose.doc;

	doc.set(req.body);
	doc.save(function(err) {
		cb(err);
	});
}

function mongooseDocDel(req, cb) {
	req.mongoose.doc.remove(function(err) {
		cb(err);
	});
}

/*!
 * Document path resource handlers
 */

function mongoosePathHook(req, next) {
	var doc = req.mongoose.doc;
	var docpath = req.mongoose.path;
	var subkeys = req.options.subkeys;

	var path = req.params["*"];
	var parts = path.split("/");

	var fullpath = docpath;
	var current = doc;
	var parent = doc;
	var link = {};

	while(parts.length > 0) {
		var part = parts.shift();
		fullpath += "/" + part;

		if (current instanceof DocumentArray) {
			parent = current;

			var key = "_id";
			if (subkeys) {
				if (typeof subkeys === "string") {
					key = subkeys;
				} else {
					Object.keys(subkeys).forEach(function(pattern) {
						if (req.match(pattern, fullpath)) {
							key = subkeys[pattern];
						}
					});
				}
			}

			if (key !== "_id") {
				current = current.filter(function(item) {
					return item[key] === part;
				})[0];
				link = { id: current._id };
			} else {
				current = current.id(part);
				link = { id: part };
			}
		} else {
			if ("field" in link) {
				link.field += "." + part;
			} else {
				parent = current;
				link = { field: part };
			}

			current = parent.get(link.field);
		}

		if (!current) {
			return next.notFound();
		}
	}

	req.mongoose.parent = parent;
	req.mongoose.item = current;
	req.mongoose.link = link;

	next();
}

function mongoosePathGet(req, cb) {
	var item = req.mongoose.item;

	if (item instanceof DocumentArray) {
		cb.list(mongooseDocArrayCount, mongooseDocArrayList);
	} else {
		cb(null, getObject(req, item));
	}
}

function mongoosePathPut(req, isPatch, cb) {
	var parent = req.mongoose.parent;
	var link = req.mongoose.link;
	var doc = req.mongoose.doc;
	var value = req.body;

	if ("_value" in value) {
		value = value._value;
	}

	if ("id" in link) {
		parent.id(link.id).set(value);
	} else if ("field" in link) {
		parent.set(link.field, value);
	} else {
		return cb(new Error("Unknown link type"));
	}

	doc.save(function(err) {
		cb(err);
	});
}

function mongoosePathDel(req, cb) {
	var parent = req.mongoose.parent;
	var link = req.mongoose.link;
	var doc = req.mongoose.doc;

	if ("id" in link) {
		parent.splice(parent.indexOf(parent.id(link.id)), 1);
	} else if ("field" in link) {
		parent.set(link.field, undefined);
	} else {
		return cb(new Error("Unknown link type"));
	}

	doc.save(function(err) {
		cb(err);
	});
}

function mongoosePathPost(req, cb) {
	var item = req.mongoose.item;

	if (!(item instanceof DocumentArray)) {
		return cb.methodNotAllowed();
	}

	mongooseDocArrayPost(req, cb);
}

/*!
 * Mongoose DocumentArray helpers
 */

function queryDocArray(req) {
	var docArray = req.mongoose.item;

	if (req.param("query")) {
		// Cache query result
		if (!req.mongoose._queryResult) {
			req.mongoose._queryResult = docArray.filter(
				queryHelpers.match.bind(
					null,
					queryHelpers.create(req.param("query"))
				)
			);
		}

		return req.mongoose._queryResult;
	} else {
		return docArray;
	}
}

function mongooseDocArrayCount(req, cb) {
	var len = queryDocArray(req).length;
	cb(null, len);
}

function mongooseDocArrayList(req, offset, limit, cb) {
	var items = queryDocArray(req);

	if (limit > 0) {
		items = items.slice(offset, offset + limit);
	} else {
		items = items.slice(offset);
	}

	cb(null, items);
}

function mongooseDocArrayPost(req, cb) {
	var docArray = req.mongoose.item;
	var doc = req.mongoose.doc;
	var index = NaN;

	if (req.param("index")) {
		index = Number(req.param("index"));
	}

	if (isNaN(index)) {
		index = docArray.length;
	}

	docArray.splice(Math.max(0, Math.min(docArray.length, index)), 0, req.body);

	doc.save(function(err) {
		if (err) {
			cb(err);
		} else {
			cb.created();
		}
	});
}

/*!
 * Mongoose resource definition helper
 */

function mongooseResource(name, Model) {
	/*jshint validthis:true*/
	var collResource = this.sub(name)
		.hook(function modelHook(req, next) {
			req.mongoose = { model: Model, path: name };
			next();
		})
		.count(mongooseCollCount)
		.list(mongooseCollList)
		.post(mongooseCollPost)
		.set("query", function mongooseDefaultQuery() { return Model.find(); })
		.set("key", "_id");

	var docResource = collResource.sub(":id")
		.hook(mongooseDocHook)
		.get(mongooseDocGet)
		.put(mongooseDocPut)
		.del(mongooseDocDel);

	docResource.sub("*", mongoosePathHook)
		.get(mongoosePathGet)
		.put(mongoosePathPut)
		.del(mongoosePathDel)
		.post(mongoosePathPost);

	return collResource;
}

module.exports = mongooseResource;
<|endoftext|>


<commit_before>const pairs = require('lodash.pairs');
const sortBy = require('lodash.sortby');

// Only consider users who have a message count of greater than this threshold.
// `pisg` default is the square-root of the message count of the top user, so
// we emulate that here with Caturday's message count of 13920.
const threshold = Math.sqrt(13920);

function mostQuestions(users) {
  const qs = sortBy(pairs(users).map(u => {
    return [
      u[0],
      u[1].lines > threshold
        ? u[1].questions / u[1].lines
        : 0
    ];
  }), u => {
    // Negate for descending order
    return -u[1];
  });

  return [qs[0], qs[1]];
}

module.exports = mostQuestions;
<commit_msg>most-questions: wrap params<commit_after>const pairs = require('lodash.pairs');
const sortBy = require('lodash.sortby');

// Only consider users who have a message count of greater than this threshold.
// `pisg` default is the square-root of the message count of the top user, so
// we emulate that here with Caturday's message count of 13920.
const threshold = Math.sqrt(13920);

function mostQuestions(users) {
  const qs = sortBy(pairs(users).map((u) => {
    return [
      u[0],
      u[1].lines > threshold
        ? u[1].questions / u[1].lines
        : 0
    ];
  }), (u) => {
    // Negate for descending order
    return -u[1];
  });

  return [qs[0], qs[1]];
}

module.exports = mostQuestions;
<|endoftext|>


<commit_before>/**
 * The DomNavigator is responsible for navigation between components, both via
 * a top-level DOM `click` event handler and on-request.
 */
var when = require('when');
var Context = require('../context');
var DomRenderer = require('../renderers/dom');

/**
 * Creates a new instance of DomNavigator with the provided `options`.
 *
 *  - `renderer`: A valid Renderer instance. Defaults to DomRenderer.
 *  - `context`: A valid Context instance. If provided, this Context will be
 *      rehydrated with any state provided from upstream Renderers provided a
 *      similar Context. Recommended.
 */
function DomNavigator(options) {
  if (!(this instanceof DomNavigator)) {
    return new DomNavigator(options);
  }

  options = options || {};

  this.renderer = this.renderer || options.renderer || new DomRenderer(options);
  this.context = this.context || options.context || new Context(options);

  if (!this.renderer) {
    throw new Error('Missing a valid renderer.');
  }
}
DomNavigator.createNavigator = DomNavigator;

/**
 * Navigates to the given `location`, represented as either a String href or
 * Location object.
 *
 * Available options:
 * - `performAction` - If true, perform the Action configured for this route.
 * - `pushState` - If true, push the new head state onto the History API.
 */
DomNavigator.prototype.navigate = function navigate(location, options) {
  var self = this;

  options = options || {};

  // TODO(schoon) - Does `isSameDomain` only matter for DomNavigator? At the
  // least, it's a Navigator thing, so it can be pulled from Router to here.
  if (!self.context.router.isSameDomain(location, global.location)) {
    return;
  }

  // TODO(schoon) - Explore moving some of this logic to Context to avoid
  // reaching into it for routing information.
  var route = self.context.router.getRoute(location);
  var promise;

  if (!route) {
    route = self.context.router.getErrorRoute(404);
  }

  if (!route) {
    throw new Error('No route found, and no 404 page provided.');
  }

  if (!route.body) {
    throw new Error('No body component found.');
  }

  if (options.performAction && route.action && self.context) {
    promise = self.context.performAction(route.action, route.params);
  }

  return when(promise)
    .then(function () {
      var props = {
        route: route,
        context: self.context
      };

      self.renderer.render(route, self.context);

      global.document.title = self.getTitle(props) || global.document.title;

      if (options.pushState) {
        global.history.pushState(null, '', location);
      }
    });
};

/**
 * Handles anchor navigation as a result of `event` ClickEvent by triggering
 * a re-render. All other `click` events pass through, unharmed.
 */
DomNavigator.prototype.onClick = function onClick(event) {
  var self = this;

  if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
    return;
  }

  function search(element) {
    if (!element) {
      return;
    }

    if (element.href) {
      return navigate(element.href);
    }

    search(element.parentElement);
  }

  function navigate(location) {
    event.preventDefault();

    self.navigate(location, {
      performAction: true,
      pushState: true
    });
  }

  search(event.target);
};

/**
 * Calculates the expected title based on the `props`. The included route's
 * `title` property, either a String or a Function, is used for the calculation.
 * If a Function, the `props` themselves are passed in.
 */
DomNavigator.prototype.getTitle = function getTitle(props) {
  var title = props.route.title;

  if (typeof title === 'function') {
    title = title(props);
  }

  return String(title);
};

/**
 * Installs the necessary event handlers in the browser.
 */
DomNavigator.prototype.start = function start() {
  var self = this;

  // Rehydrate the Context first and once.
  if (self.context) {
    self.context.fromObject(global.LITTLEST_ISOMORPH_CONTEXT);
  }

  // When the Back button is pressed, we need to rehydrate based on state
  // stored by the browser-stored.
  global.onpopstate = function () {
    self.navigate(global.location, {
      performAction: true
    });
  };

  // When the DOM initially loads, we need to rehydrate based on state provided
  // by the server.
  global.document.addEventListener('DOMContentLoaded', function () {
    self.navigate(global.location, {
      performAction: !global.LITTLEST_ISOMORPH_CONTEXT
    });
  });

  // Navigation is done through a top-level click handler, so attach that.
  global.document.addEventListener('click', function onClick(event) {
    self.onClick(event);
  });

  self.context.on('navigate', function (params) {
    self.navigate(params.location, {
      performAction: true,
      pushState: true
    });
  });
};

/*!
 * Export `DomNavigator`.
 */
module.exports = DomNavigator;
<commit_msg>DomNavigator: Allow forcing the initial Action.<commit_after>/**
 * The DomNavigator is responsible for navigation between components, both via
 * a top-level DOM `click` event handler and on-request.
 */
var when = require('when');
var Context = require('../context');
var DomRenderer = require('../renderers/dom');

/**
 * Creates a new instance of DomNavigator with the provided `options`.
 *
 *  - `renderer`: A valid Renderer instance. Defaults to DomRenderer.
 *  - `context`: A valid Context instance. If provided, this Context will be
 *      rehydrated with any state provided from upstream Renderers provided a
 *      similar Context. Recommended.
 */
function DomNavigator(options) {
  if (!(this instanceof DomNavigator)) {
    return new DomNavigator(options);
  }

  options = options || {};

  this.renderer = this.renderer || options.renderer || new DomRenderer(options);
  this.context = this.context || options.context || new Context(options);

  if (!this.renderer) {
    throw new Error('Missing a valid renderer.');
  }
}
DomNavigator.createNavigator = DomNavigator;

/**
 * Navigates to the given `location`, represented as either a String href or
 * Location object.
 *
 * Available options:
 * - `performAction` - If true, perform the Action configured for this route.
 * - `pushState` - If true, push the new head state onto the History API.
 */
DomNavigator.prototype.navigate = function navigate(location, options) {
  var self = this;

  options = options || {};

  // TODO(schoon) - Does `isSameDomain` only matter for DomNavigator? At the
  // least, it's a Navigator thing, so it can be pulled from Router to here.
  if (!self.context.router.isSameDomain(location, global.location)) {
    return;
  }

  // TODO(schoon) - Explore moving some of this logic to Context to avoid
  // reaching into it for routing information.
  var route = self.context.router.getRoute(location);
  var promise;

  if (!route) {
    route = self.context.router.getErrorRoute(404);
  }

  if (!route) {
    throw new Error('No route found, and no 404 page provided.');
  }

  if (!route.body) {
    throw new Error('No body component found.');
  }

  if (options.performAction && route.action && self.context) {
    promise = self.context.performAction(route.action, route.params);
  }

  return when(promise)
    .then(function () {
      var props = {
        route: route,
        context: self.context
      };

      self.renderer.render(route, self.context);

      global.document.title = self.getTitle(props) || global.document.title;

      if (options.pushState) {
        global.history.pushState(null, '', location);
      }
    });
};

/**
 * Handles anchor navigation as a result of `event` ClickEvent by triggering
 * a re-render. All other `click` events pass through, unharmed.
 */
DomNavigator.prototype.onClick = function onClick(event) {
  var self = this;

  if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
    return;
  }

  function search(element) {
    if (!element) {
      return;
    }

    if (element.href) {
      return navigate(element.href);
    }

    search(element.parentElement);
  }

  function navigate(location) {
    event.preventDefault();

    self.navigate(location, {
      performAction: true,
      pushState: true
    });
  }

  search(event.target);
};

/**
 * Calculates the expected title based on the `props`. The included route's
 * `title` property, either a String or a Function, is used for the calculation.
 * If a Function, the `props` themselves are passed in.
 */
DomNavigator.prototype.getTitle = function getTitle(props) {
  var title = props.route.title;

  if (typeof title === 'function') {
    title = title(props);
  }

  return String(title);
};

/**
 * Installs the necessary event handlers in the browser.
 *
 * Available options:
 * - `performAction` - Force the navigator to perform the initial Action.
 */
DomNavigator.prototype.start = function start(options) {
  var self = this;

  options = options || {};

  // Rehydrate the Context first and once.
  if (self.context) {
    self.context.fromObject(global.LITTLEST_ISOMORPH_CONTEXT);
  }

  // When the Back button is pressed, we need to rehydrate based on state
  // stored by the browser-stored.
  global.onpopstate = function () {
    self.navigate(global.location, {
      performAction: true
    });
  };

  // When the DOM initially loads, we need to rehydrate based on state provided
  // by the server.
  global.document.addEventListener('DOMContentLoaded', function () {
    self.navigate(global.location, {
      performAction: !global.LITTLEST_ISOMORPH_CONTEXT || options.performAction
    });
  });

  // Navigation is done through a top-level click handler, so attach that.
  global.document.addEventListener('click', function onClick(event) {
    self.onClick(event);
  });

  self.context.on('navigate', function (params) {
    self.navigate(params.location, {
      performAction: true,
      pushState: true
    });
  });
};

/*!
 * Export `DomNavigator`.
 */
module.exports = DomNavigator;
<|endoftext|>


<commit_before>
/*!
 * CSS - Selector
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Block = require('./block')
  , Node = require('./node');

/**
 * Initialize a new `Selector` with the given `val` and `block`
 * which defaults to an empty block.
 *
 * @param {String} val
 * @param {Block} block
 * @api public
 */

var Selector = module.exports = function Selector(val, block){
  Node.call(this);
  this.val = val;
  this.block = block;
};

/**
 * Inherit from `Node.prototype`.
 */

Selector.prototype.__proto__ = Node.prototype;

/**
 * Return the selector string.
 *
 * @return {String}
 * @api public
 */

Selector.prototype.toString = function(){
  return this.val;
};
<commit_msg>Added Selector#clone()<commit_after>
/*!
 * CSS - Selector
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Block = require('./block')
  , Node = require('./node');

/**
 * Initialize a new `Selector` with the given `val` and `block`
 * which defaults to an empty block.
 *
 * @param {String} val
 * @param {Block} block
 * @api public
 */

var Selector = module.exports = function Selector(val, block){
  Node.call(this);
  this.val = val;
  this.block = block;
};

/**
 * Inherit from `Node.prototype`.
 */

Selector.prototype.__proto__ = Node.prototype;

/**
 * Return the selector string.
 *
 * @return {String}
 * @api public
 */

Selector.prototype.toString = function(){
  return this.val;
};

/**
 * Return a clone of this node.
 *
 * @return {Node}
 * @api public
 */

Selector.prototype.clone = function(){
  return new Selector(this.val, this.block.clone());
};
<|endoftext|>


<commit_before>exports.readOptions = readOptions;

var _ = require("underscore");

var standardOptions = exports._standardOptions = {
    styleMap: [
        "p.Heading1 => h1:fresh",
        "p.Heading2 => h2:fresh",
        "p.Heading3 => h3:fresh",
        "p.Heading4 => h4:fresh",
        "p[style-name='Heading 1'] => h1:fresh",
        "p[style-name='Heading 2'] => h2:fresh",
        "p[style-name='Heading 3'] => h3:fresh",
        "p[style-name='Heading 4'] => h4:fresh",
        "p[style-name='heading 1'] => h1:fresh",
        "p[style-name='heading 2'] => h2:fresh",
        "p[style-name='heading 3'] => h3:fresh",
        "p[style-name='heading 4'] => h4:fresh",
        "p[style-name='heading 4'] => h4:fresh",

        "p[style-name='footnote text'] => p",

        "p:unordered-list(1) => ul > li:fresh",
        "p:unordered-list(2) => ul|ol > li > ul > li:fresh",
        "p:unordered-list(3) => ul|ol > li > ul|ol > li > ul > li:fresh",
        "p:unordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh",
        "p:unordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh",
        "p:ordered-list(1) => ol > li:fresh",
        "p:ordered-list(2) => ul|ol > li > ol > li:fresh",
        "p:ordered-list(3) => ul|ol > li > ul|ol > li > ol > li:fresh",
        "p:ordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh",
        "p:ordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh"
    ],
    transformDocument: identity,
    includeDefaultStyleMap: true
};

function readOptions(options) {
    options = options || {};
    var fullOptions = {};
    _.extend(fullOptions, standardOptions, options);

    fullOptions.styleMap = readStyleMap(options.styleMap, fullOptions);

    return fullOptions;
}

function readStyleMap(styleMap, options) {
    var customStyleMap = readCustomStyleMap(styleMap);
    return customStyleMap.concat(options.includeDefaultStyleMap ? standardOptions.styleMap : []);
}

function readCustomStyleMap(styleMap) {
    if (!styleMap) {
        return [];
    } else if (_.isString(styleMap)) {
        return styleMap.split("\n").map(function(line) {
            return line.trim();
        });
    } else {
        return styleMap;
    }
}

function identity(value) {
    return value;
}
<commit_msg>Add default mapping for p[style-name='Normal']<commit_after>exports.readOptions = readOptions;

var _ = require("underscore");

var standardOptions = exports._standardOptions = {
    styleMap: [
        "p[style-name='Normal'] => p:fresh",

        "p.Heading1 => h1:fresh",
        "p.Heading2 => h2:fresh",
        "p.Heading3 => h3:fresh",
        "p.Heading4 => h4:fresh",
        "p[style-name='Heading 1'] => h1:fresh",
        "p[style-name='Heading 2'] => h2:fresh",
        "p[style-name='Heading 3'] => h3:fresh",
        "p[style-name='Heading 4'] => h4:fresh",
        "p[style-name='heading 1'] => h1:fresh",
        "p[style-name='heading 2'] => h2:fresh",
        "p[style-name='heading 3'] => h3:fresh",
        "p[style-name='heading 4'] => h4:fresh",
        "p[style-name='heading 4'] => h4:fresh",

        "p[style-name='footnote text'] => p",

        "p:unordered-list(1) => ul > li:fresh",
        "p:unordered-list(2) => ul|ol > li > ul > li:fresh",
        "p:unordered-list(3) => ul|ol > li > ul|ol > li > ul > li:fresh",
        "p:unordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh",
        "p:unordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh",
        "p:ordered-list(1) => ol > li:fresh",
        "p:ordered-list(2) => ul|ol > li > ol > li:fresh",
        "p:ordered-list(3) => ul|ol > li > ul|ol > li > ol > li:fresh",
        "p:ordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh",
        "p:ordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh"
    ],
    transformDocument: identity,
    includeDefaultStyleMap: true
};

function readOptions(options) {
    options = options || {};
    var fullOptions = {};
    _.extend(fullOptions, standardOptions, options);

    fullOptions.styleMap = readStyleMap(options.styleMap, fullOptions);

    return fullOptions;
}

function readStyleMap(styleMap, options) {
    var customStyleMap = readCustomStyleMap(styleMap);
    return customStyleMap.concat(options.includeDefaultStyleMap ? standardOptions.styleMap : []);
}

function readCustomStyleMap(styleMap) {
    if (!styleMap) {
        return [];
    } else if (_.isString(styleMap)) {
        return styleMap.split("\n").map(function(line) {
            return line.trim();
        });
    } else {
        return styleMap;
    }
}

function identity(value) {
    return value;
}
<|endoftext|>


<commit_before>/*jshint node:true*/
'use strict';

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

/*
 *! Input-related methods
 */

module.exports = function(proto) {
  /**
   * Add an input to command
   *
   * Also switches "current input", that is the input that will be affected
   * by subsequent input-related methods.
   *
   * Note: only one stream input is supported for now.
   *
   * @method FfmpegCommand#input
   * @category Input
   * @aliases mergeAdd,addInput
   *
   * @param {String|Readable} source input file path or readable stream
   * @return FfmpegCommand
   */
  proto.mergeAdd =
  proto.addInput =
  proto.input = function(source) {
    if (typeof source !== 'string') {
      if (!('readable' in source)) {
        throw new Error('Invalid input');
      }

      var hasInputStream = this._inputs.some(function(input) {
        return typeof input.source !== 'string';
      });

      if (hasInputStream) {
        throw new Error('Only one input stream is supported');
      }

      source.pause();
    }

    this._inputs.push(this._currentInput = {
      source: source,
      before: utils.args(),
      after: utils.args(),
    });

    return this;
  };

  /**
   * Specify input format for the last specified input
   *
   * @method FfmpegCommand#inputFormat
   * @category Input
   * @aliases withInputFormat,fromFormat
   *
   * @param {String} format input format
   * @return FfmpegCommand
   */
  proto.withInputFormat =
  proto.inputFormat =
  proto.fromFormat = function(format) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    this._currentInput.before('-f', format);
    return this;
  };

  /**
   * Specify input FPS for the last specified input
   * (only valid for raw video formats)
   *
   * @method FfmpegCommand#inputFps
   * @category Input
   * @aliases withInputFps,withInputFPS,withFpsInput,withFPSInput,inputFPS,inputFps,fpsInput
   *
   * @param {Number} fps input FPS
   * @return FfmpegCommand
   */
  proto.withInputFps =
  proto.withInputFPS =
  proto.withFpsInput =
  proto.withFPSInput =
  proto.inputFPS =
  proto.inputFps =
  proto.fpsInput =
  proto.FPSInput = function(fps) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    this._currentInput.before('-r', fps);
    return this;
  };

  /**
   * Specify input seek time for the last specified input
   *
   * @method FfmpegCommand#seek
   * @category Input
   * @aliases setStartTime,seekTo
   *
   * @param {String|Number} seek seek time in seconds or as a '[hh:[mm:]]ss[.xxx]' string
   * @param {Boolean} [fast=false] use fast (but inexact) seek
   * @return FfmpegCommand
   */
  proto.setStartTime =
  proto.seekTo =
  proto.seek = function(seek, fast) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    if (fast) {
      this._currentInput.before('-ss', seek);
    } else {
      this._currentInput.after('-ss', seek);
    }

    return this;
  };

  /**
   * Specify input fast-seek time for the last specified input
   *
   * @method FfmpegCommand#fastSeek
   * @category Input
   * @aliases fastSeekTo
   *
   * @param {String|Number} seek fast-seek time in seconds or as a '[[hh:]mm:]ss[.xxx]' string
   * @return FfmpegCommand
   */
  proto.fastSeek =
  proto.fastSeekTo = function(seek) {
    return this.seek(seek, true);
  };

  /**
   * Loop over the last specified input
   *
   * @method FfmpegCommand#loop
   * @category Input
   *
   * @param {String|Number} [duration] loop duration in seconds or as a '[[hh:]mm:]ss[.xxx]' string
   * @return FfmpegCommand
   */
  proto.loop = function(duration) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    this._currentInput.before('-loop', '1');

    if (typeof duration !== 'undefined') {
      this.duration(duration);
    }

    return this;
  };
};
<commit_msg>Fix input stream readable check<commit_after>/*jshint node:true*/
'use strict';

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

/*
 *! Input-related methods
 */

module.exports = function(proto) {
  /**
   * Add an input to command
   *
   * Also switches "current input", that is the input that will be affected
   * by subsequent input-related methods.
   *
   * Note: only one stream input is supported for now.
   *
   * @method FfmpegCommand#input
   * @category Input
   * @aliases mergeAdd,addInput
   *
   * @param {String|Readable} source input file path or readable stream
   * @return FfmpegCommand
   */
  proto.mergeAdd =
  proto.addInput =
  proto.input = function(source) {
    if (typeof source !== 'string') {
      if (!('readable' in source) || !(source.readable)) {
        throw new Error('Invalid input');
      }

      var hasInputStream = this._inputs.some(function(input) {
        return typeof input.source !== 'string';
      });

      if (hasInputStream) {
        throw new Error('Only one input stream is supported');
      }

      source.pause();
    }

    this._inputs.push(this._currentInput = {
      source: source,
      before: utils.args(),
      after: utils.args(),
    });

    return this;
  };

  /**
   * Specify input format for the last specified input
   *
   * @method FfmpegCommand#inputFormat
   * @category Input
   * @aliases withInputFormat,fromFormat
   *
   * @param {String} format input format
   * @return FfmpegCommand
   */
  proto.withInputFormat =
  proto.inputFormat =
  proto.fromFormat = function(format) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    this._currentInput.before('-f', format);
    return this;
  };

  /**
   * Specify input FPS for the last specified input
   * (only valid for raw video formats)
   *
   * @method FfmpegCommand#inputFps
   * @category Input
   * @aliases withInputFps,withInputFPS,withFpsInput,withFPSInput,inputFPS,inputFps,fpsInput
   *
   * @param {Number} fps input FPS
   * @return FfmpegCommand
   */
  proto.withInputFps =
  proto.withInputFPS =
  proto.withFpsInput =
  proto.withFPSInput =
  proto.inputFPS =
  proto.inputFps =
  proto.fpsInput =
  proto.FPSInput = function(fps) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    this._currentInput.before('-r', fps);
    return this;
  };

  /**
   * Specify input seek time for the last specified input
   *
   * @method FfmpegCommand#seek
   * @category Input
   * @aliases setStartTime,seekTo
   *
   * @param {String|Number} seek seek time in seconds or as a '[hh:[mm:]]ss[.xxx]' string
   * @param {Boolean} [fast=false] use fast (but inexact) seek
   * @return FfmpegCommand
   */
  proto.setStartTime =
  proto.seekTo =
  proto.seek = function(seek, fast) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    if (fast) {
      this._currentInput.before('-ss', seek);
    } else {
      this._currentInput.after('-ss', seek);
    }

    return this;
  };

  /**
   * Specify input fast-seek time for the last specified input
   *
   * @method FfmpegCommand#fastSeek
   * @category Input
   * @aliases fastSeekTo
   *
   * @param {String|Number} seek fast-seek time in seconds or as a '[[hh:]mm:]ss[.xxx]' string
   * @return FfmpegCommand
   */
  proto.fastSeek =
  proto.fastSeekTo = function(seek) {
    return this.seek(seek, true);
  };

  /**
   * Loop over the last specified input
   *
   * @method FfmpegCommand#loop
   * @category Input
   *
   * @param {String|Number} [duration] loop duration in seconds or as a '[[hh:]mm:]ss[.xxx]' string
   * @return FfmpegCommand
   */
  proto.loop = function(duration) {
    if (!this._currentInput) {
      throw new Error('No input specified');
    }

    this._currentInput.before('-loop', '1');

    if (typeof duration !== 'undefined') {
      this.duration(duration);
    }

    return this;
  };
};
<|endoftext|>


<commit_before>'use strict';

var helper = require('./package-finder-helper');
var fs = require('fs');
var Q = require('q');
module.exports = function () {
  function exec(argv, dir) {

    //read entire directory
    fs.readdir(dir, function (err, results) {
      if (err) {
        throw err;
      }

      var next = helper.userAction(argv);

      Q.all(results.map(helper.isFile)).
      then(function (fileResults) {
        return results.filter(function (file, index) {
          return fileResults[index];
        });
      }).then(function (files) {
        var jsFiles = files.filter(helper.isJavaScript);
        helper.readAndExtract(jsFiles, dir).then(next);
      });

    });
  }

  return {
    exec: exec
  };

}();
<commit_msg>refactor to make it more readable<commit_after>'use strict';

var helper = require('./package-finder-helper');
var fs = require('fs');
var Q = require('q');
module.exports = function () {

  function readDir(dir) {
    return Q.nfcall(fs.readdir, dir);
  }

  //Returns an array of file names
  function getFiles(results) {
    return Q.all(results.map(helper.isFile)).
    then(function (fileResults) {
      return results.filter(function (file, index) {
        return fileResults[index];
      });
    });

  }

  function readAllDirs(dir) {
    return readDir(dir).then(function (results) {

      return getFiles(results).then(function (files) {
        //TODO: Ensure all directories have been read.
        return files.filter(helper.isJavaScript);

      });
    });
  }

  function exec(argv, dir) {
    readAllDirs(dir).then(function (jsFiles) {
      var next = helper.userAction(argv);
      helper.readAndExtract(jsFiles, dir).then(next);
    });
  }

  return {
    exec: exec
  };

}();
<|endoftext|>


<commit_before>'use strict';
/* eslint camelcase: 0 */

var featureFactory = require('./featureModels/featureFactory');
var tagsFactory = require('./featureModels/tagsFactory.js');
var commentsFactory = require('./featureModels/commentsFactory.js');

module.exports = function Visitor() {
  var features = [];
  features.getLatest = function() { return this[this.length - 1]; };

  var tags = tagsFactory();
  var comments = commentsFactory();

  function backgroundOrScenarioParser(keyword, name, description, line) {
    var localTags = tags.flush();
    var localComments = comments.flush();

    var feature = features.getLatest();
    if (feature === undefined) {
        throw new TypeError(keyword + ' found outside of feature, giving up parsing.');
    }

    feature.addScenarioOrBackground({
      keyword: keyword,
      name: name,
      description: description,
      line: line,
      tags: localTags,
      comments: localComments
    });
  }

  return {
    comment: function(value, line) {
      comments.add(value);
    },
    tag: function(value, line) {
      tags.add(value);
    },
    feature: function(keyword, name, description, line) {

      // Add the current list of tags to the current feature and empty the tags list.
      var featureTags = tags.flush();
      var featureComments = comments.flush();
      features.push(featureFactory({
        keyword: keyword,
        name: name,
        description: description,
        line: line,
        tags: featureTags,
        comments: featureComments
      }));
    },
    background: backgroundOrScenarioParser,
    scenario: backgroundOrScenarioParser,
    scenario_outline: function(keyword, name, description, line) {
      // TODO
      console.log('*** Scenario Outline');
      console.log('keyword:' + keyword);
      console.log('name:' + name);
      console.log('description:' + description);

    },
    examples: function(keyword, name, description, line) {
      // TODO
      console.log('*** Examples');
      console.log('keyword:' + keyword);
      console.log('name:' + name);
      console.log('description:' + description);

    },
    step: function(keyword, name, line) {
      var feature = features.getLatest();

      if (feature === undefined) {
          throw new TypeError(keyword + ' found outside of feature, giving up parsing.');
      }

      feature.addStep({
        keyword: keyword,
        name: name,
        line: line
      });
    },
    doc_string: function(content_type, string, line) {
      // TODO
      console.log('*** Doc String');
      console.log('content type:' + content_type);
      console.log('string:' + string);
    },
    row: function(row, line) {
      // TODO
      console.log('*** Row');
      console.log('row:' + row);
    },
    eof: function() {
      // TODO: is this important?
      console.log('*** EOF');
    },
    getFeatures: function() {
      return features;
    }
  };
}
<commit_msg>modify vistor to treat scenario outlines the same as scenarios and backgrounds.<commit_after>'use strict';
/* eslint camelcase: 0 */

var featureFactory = require('./featureModels/featureFactory');
var tagsFactory = require('./featureModels/tagsFactory.js');
var commentsFactory = require('./featureModels/commentsFactory.js');

module.exports = function Visitor() {
  var features = [];
  features.getLatest = function() { return this[this.length - 1]; };

  var tags = tagsFactory();
  var comments = commentsFactory();

  function backgroundOrScenarioParser(keyword, name, description, line) {
    var localTags = tags.flush();
    var localComments = comments.flush();

    var feature = features.getLatest();
    if (feature === undefined) {
        throw new TypeError(keyword + ' found outside of feature, giving up parsing.');
    }

    feature.addScenarioOrBackground({
      keyword: keyword,
      name: name,
      description: description,
      line: line,
      tags: localTags,
      comments: localComments
    });
  }

  return {
    comment: function(value, line) {
      comments.add(value);
    },
    tag: function(value, line) {
      tags.add(value);
    },
    feature: function(keyword, name, description, line) {

      // Add the current list of tags to the current feature and empty the tags list.
      var featureTags = tags.flush();
      var featureComments = comments.flush();
      features.push(featureFactory({
        keyword: keyword,
        name: name,
        description: description,
        line: line,
        tags: featureTags,
        comments: featureComments
      }));
    },
    background: backgroundOrScenarioParser,
    scenario: backgroundOrScenarioParser,
    scenario_outline: backgroundOrScenarioParser,
    examples: function(keyword, name, description, line) {
      // TODO
      // console.log('*** Examples');
      // console.log('keyword:' + keyword);
      // console.log('name:' + name);
      // console.log('description:' + description);

    },
    step: function(keyword, name, line) {
      var feature = features.getLatest();

      if (feature === undefined) {
          throw new TypeError(keyword + ' found outside of feature, giving up parsing.');
      }

      feature.addStep({
        keyword: keyword,
        name: name,
        line: line
      });
    },
    doc_string: function(content_type, string, line) {
      // TODO
      // console.log('*** Doc String');
      // console.log('content type:' + content_type);
      // console.log('string:' + string);
    },
    row: function(row, line) {
      // TODO
      // console.log('*** Row');
      // console.log('row:' + row);
    },
    eof: function() {
      // TODO: is this important?
      // console.log('*** EOF');
    },
    getFeatures: function() {
      return features;
    }
  };
}
<|endoftext|>


<commit_before>var events = require('events');
var dht = require('bittorrent-dht');
var tracker = require('bittorrent-tracker');

var DEFAULT_PORT = 6881;

module.exports = function(opts) {
	var torrent;

	var port = opts.port || DEFAULT_PORT;

	var discovery = new events.EventEmitter();

	discovery.dht = null;
	discovery.tracker = null;
	discovery.niceSeeder = false;
	discovery.whenSeeder = 0;
	discovery.seedDelay = 0;

	var onpeer = function(addr) {
		discovery.emit('peer', addr);
	};

	var onforcepeer = function(addr) {
		discovery.emit('forcePeer', addr);
	};

	var createDHT = function(infoHash) {
		if (opts.dht === false) return;

		var table = dht();

		table.on('peer', function(addr) {
			if (discovery.amSeeder) {
				onforcepeer(addr);
			} else {
				onpeer(addr);
			}
		});
		table.on('ready', function() {
			table.lookup(infoHash);
		});
		table.listen();

		return table;
	};

	var createTracker = function(torrent) {
		if (discovery.amSeeder && discovery.tracker) {
			return discovery.tracker;
		}
		if (opts.trackers) {
			torrent = Object.create(torrent);
			var trackers = (opts.tracker !== false) && torrent.announce ? torrent.announce : [];
			torrent.announce = trackers.concat(opts.trackers);
		} else if (opts.tracker === false) {
			return;
		}

		if (!torrent.announce || !torrent.announce.length) return;

		var tr = new tracker.Client(new Buffer(opts.id), port, torrent);

		tr.on('peer', function(addr) {
			if (discovery.amSeeder) {
				onforcepeer(addr);
			} else {
				onpeer(addr);
			}
		});
		tr.on('error', function() { /* noop */ });

		if (discovery.amSeeder) {
			tr.complete();
		} else {
			tr.start();
		}
		return tr;
	};

	discovery.setTorrent = function(t) {
		torrent = t;

		if (discovery.tracker) {
			// If we have tracker then it had probably been created before we got infoDictionary.
			// So client do not know torrent length and can not report right information about uploads
			discovery.tracker.torrentLength = torrent.length;
		} else {
			process.nextTick(function() {
				if (!discovery.dht) discovery.dht = createDHT(torrent.infoHash);
				if (!discovery.tracker) discovery.tracker = createTracker(torrent);
			});
		}
	};

	discovery.updatePort = function(p) {
		if (port === p) return;
		port = p;
		if (discovery.tracker) discovery.tracker.stop();
		if (torrent) {
			if (discovery.dht) discovery.dht.announce(torrent.infoHash, port);
			discovery.tracker = createTracker(torrent);
		}
	};

	discovery.seedCheck = function () {
		discovery.complete();
		if (discovery.seedDelay == 180000 && discovery.whenSeeder < Date.now() - 600000) {
			// 10 min passed => 5 min interval
			discovery.seedDelay = 300000;
			var changed = true;
		} else if (discovery.seedDelay == 300000 && discovery.whenSeeder < Date.now() - 1800000) {
			// 30 min passed => 10 min interval
			discovery.seedDelay = 600000;
			var changed = true;
		} else if (discovery.seedDelay == 600000 && discovery.whenSeeder < Date.now() - 3600000) {
			// 60 min passed => 15 min interval
			discovery.seedDelay = 900000;
			var changed = true;
		} else if (discovery.seedDelay == 600000 && discovery.whenSeeder < Date.now() - 7200000) {
			// 120 min passed => 30 min interval
			discovery.seedDelay = 1800000;
			var changed = true;
		}
		if (changed) {
			clearInterval(discovery.niceSeeder);
			discovery.niceSeeder = setInterval(discovery.seedCheck, discovery.seedDelay);
		}
	};

	discovery.complete = function () {
		discovery.amSeeder = true;
		if (discovery.tracker) {
			if (!discovery.niceSeeder) {
				discovery.whenSeeder = Date.now();
				discovery.seedDelay = 180000; // start at a 3 min interval
				discovery.niceSeeder = setInterval(discovery.seedCheck, discovery.seedDelay);
			}
			discovery.tracker.complete();
		}
	};

	discovery.stop = function() {
		if (discovery.niceSeeder) {
			clearInterval(discovery.niceSeeder);
			discovery.niceSeeder = false;
		}
		if (discovery.tracker) discovery.tracker.stop();
		if (discovery.dht) discovery.dht.destroy();
	};

	discovery.restart = function() {
		if (!torrent) return;
		if (discovery.tracker) discovery.tracker.stop();
		if (discovery.dht) discovery.dht.destroy();
		discovery.tracker = createTracker(torrent);
		discovery.dht = createDHT(torrent.infoHash);
	};

	return discovery;
};
<commit_msg>Check Seeding Policy<commit_after>var events = require('events');
var dht = require('bittorrent-dht');
var tracker = require('bittorrent-tracker');

var DEFAULT_PORT = 6881;

module.exports = function(opts) {
	var torrent;

	var port = opts.port || DEFAULT_PORT;

	var discovery = new events.EventEmitter();

	discovery.dht = null;
	discovery.tracker = null;
	discovery.niceSeeder = false;
	discovery.whenSeeder = 0;
	discovery.seedDelay = 0;

	var onpeer = function(addr) {
		discovery.emit('peer', addr);
	};

	var onforcepeer = function(addr) {
		discovery.emit('forcePeer', addr);
	};

	var createDHT = function(infoHash) {
		if (opts.dht === false) return;

		var table = dht();

		table.on('peer', function(addr) {
			if (discovery.amSeeder && !window.localStorage.noSeeding) {
				onforcepeer(addr);
			} else {
				onpeer(addr);
			}
		});
		table.on('ready', function() {
			table.lookup(infoHash);
		});
		table.listen();

		return table;
	};

	var createTracker = function(torrent) {
		if (discovery.amSeeder && discovery.tracker) {
			return discovery.tracker;
		}
		if (opts.trackers) {
			torrent = Object.create(torrent);
			var trackers = (opts.tracker !== false) && torrent.announce ? torrent.announce : [];
			torrent.announce = trackers.concat(opts.trackers);
		} else if (opts.tracker === false) {
			return;
		}

		if (!torrent.announce || !torrent.announce.length) return;

		var tr = new tracker.Client(new Buffer(opts.id), port, torrent);

		tr.on('peer', function(addr) {
			if (discovery.amSeeder && !window.localStorage.noSeeding) {
				onforcepeer(addr);
			} else {
				onpeer(addr);
			}
		});
		tr.on('error', function() { /* noop */ });

		if (!window.localStorage.noSeeding) {
			if (discovery.amSeeder) {
				tr.complete();
			} else {
				tr.start();
			}
		} else tr.start();
		return tr;
	};

	discovery.setTorrent = function(t) {
		torrent = t;

		if (discovery.tracker) {
			// If we have tracker then it had probably been created before we got infoDictionary.
			// So client do not know torrent length and can not report right information about uploads
			discovery.tracker.torrentLength = torrent.length;
		} else {
			process.nextTick(function() {
				if (!discovery.dht) discovery.dht = createDHT(torrent.infoHash);
				if (!discovery.tracker) discovery.tracker = createTracker(torrent);
			});
		}
	};

	discovery.updatePort = function(p) {
		if (port === p) return;
		port = p;
		if (discovery.tracker) discovery.tracker.stop();
		if (torrent) {
			if (discovery.dht) discovery.dht.announce(torrent.infoHash, port);
			discovery.tracker = createTracker(torrent);
		}
	};

	discovery.seedCheck = function () {
		discovery.complete();
		if (discovery.seedDelay == 180000 && discovery.whenSeeder < Date.now() - 600000) {
			// 10 min passed => 5 min interval
			discovery.seedDelay = 300000;
			var changed = true;
		} else if (discovery.seedDelay == 300000 && discovery.whenSeeder < Date.now() - 1800000) {
			// 30 min passed => 10 min interval
			discovery.seedDelay = 600000;
			var changed = true;
		} else if (discovery.seedDelay == 600000 && discovery.whenSeeder < Date.now() - 3600000) {
			// 60 min passed => 15 min interval
			discovery.seedDelay = 900000;
			var changed = true;
		} else if (discovery.seedDelay == 600000 && discovery.whenSeeder < Date.now() - 7200000) {
			// 120 min passed => 30 min interval
			discovery.seedDelay = 1800000;
			var changed = true;
		}
		if (changed) {
			clearInterval(discovery.niceSeeder);
			discovery.niceSeeder = setInterval(discovery.seedCheck, discovery.seedDelay);
		}
	};

	discovery.complete = function () {
		discovery.amSeeder = true;
		if (!window.localStorage.noSeeding) {
			if (discovery.tracker) {
				if (!discovery.niceSeeder) {
					discovery.whenSeeder = Date.now();
					discovery.seedDelay = 180000; // start at a 3 min interval
					discovery.niceSeeder = setInterval(discovery.seedCheck, discovery.seedDelay);
				}
				discovery.tracker.complete();
			}
		}
	};

	discovery.stop = function() {
		if (discovery.niceSeeder) {
			clearInterval(discovery.niceSeeder);
			discovery.niceSeeder = false;
		}
		if (discovery.tracker) discovery.tracker.stop();
		if (discovery.dht) discovery.dht.destroy();
	};

	discovery.restart = function() {
		if (!torrent) return;
		if (discovery.tracker) discovery.tracker.stop();
		if (discovery.dht) discovery.dht.destroy();
		discovery.tracker = createTracker(torrent);
		discovery.dht = createDHT(torrent.infoHash);
	};

	return discovery;
};
<|endoftext|>


<commit_before>'use strict';
var standaloneMode = standaloneMode || false;

var page = require('webpage').create(),
	fs = require('fs'),
	system = require('system'),
	DEBUG = false,
	stdout = system.stdout; // for using this as a file

var combineArgsString = function(argsArr) {
	return [].join.call(argsArr, ' ') + '\n';
};

// monkey patch for directing errors to stderr
// https://github.com/ariya/phantomjs/issues/10150#issuecomment-28707859
var errorlog = function() {
	system.stderr.write(combineArgsString(arguments));
};

var debug = function() {
	if (DEBUG) errorlog('DEBUG: ' + combineArgsString(arguments));
};

//don't confuse analytics more than necessary when visiting websites
page.settings.userAgent = 'Penthouse Critical Path CSS Generator';

/* prevent page JS errors from being output to final CSS */
page.onError = function(msg, trace) {
	//do nothing
};

page.onResourceError = function(resourceError) {
  page.reason = resourceError.errorString;
  page.reason_url = resourceError.url;
};

var main = function(options) {
  debug('main(): ', JSON.stringify(options));
//final cleanup
//remove all empty rules, and remove leading/trailing whitespace
	try {
		var f = fs.open(options.css, 'r');

		//preformat css
		var cssPreformat;
		if (standaloneMode) {
			cssPreformat = cssPreformatter;
		} else {
			cssPreformat = require('./css-preformatter.js');
		}
		options.css = cssPreformat(f.read());
	} catch (e) {
		errorlog(e);
		phantom.exit(1);
	}

  // start the critical path CSS generation
  getCriticalPathCss(options);
};

function cleanup(css) {
	//remove all animation rules, as keyframes have already been removed
	css = css.replace(/(-webkit-|-moz-|-ms-|-o-)?animation[ ]?:[^;{}]*;/gm, '');
	//remove all empty rules, and remove leading/trailing whitespace
	return css.replace(/[^{}]*\{\s*\}/gm, '').trim();
}

/* Final function
 * Get's called from getCriticalPathCss when CSS extraction from page is done*/
page.onCallback = function(css) {
  debug('phantom.onCallback');

  try {
    if (css) {
      // we are done - clean up the final css
      var finalCss = cleanup(css);

      // remove unused @fontface rules
			var ffRemover;
      if (standaloneMode) {
        ffRemover = unusedFontfaceRemover;
      } else {
        ffRemover = require('./unused-fontface-remover.js');
      }
      finalCss = ffRemover(finalCss);

			if(finalCss.trim().length === 0){
				errorlog('Note: Generated critical css was empty for URL: ' + options.url);
			}

      // return the critical css!
      stdout.write(finalCss);
      phantom.exit(0);
    } else {
      // No css. This is not an error on our part
      // but still safer to warn the end user, in case they made a mistake
      errorlog('Note: Generated critical css was empty for URL: ' + options.url);
      // for consisteny, still generate output (will be empty)
      stdout.write(css);
      phantom.exit(0);
    }

  } catch (ex) {
    debug('phantom.onCallback -> error', ex);
    errorlog('error: ' + ex);
    phantom.exit(1);
  }
};

/*
 * Tests each selector in css file at specified resolution,
 * to see if any such elements appears above the fold on the page
 * modifies CSS - removes selectors that don't appear, and empty rules
 *
 * @param options.url the url as a string
 * @param options.css the css as a string
 * @param options.width the width of viewport
 * @param options.height the height of viewport
 ---------------------------------------------------------*/
function getCriticalPathCss(options) {
	debug('getCriticalPathCss():', JSON.stringify(options));

	page.viewportSize = {
		width: options.width,
		height: options.height
	};

	page.open(options.url, function(status) {
		if (status !== 'success') {
			errorlog('Error opening url \'' + page.reason_url + '\': ' + page.reason);
			phantom.exit(1);
		} else {

			debug('Starting sandboxed evaluation of CSS\n', options.css);
			// sandboxed environments - no outside references
			// arguments and return value must be primitives
			// @see http://phantomjs.org/api/webpage/method/evaluate.html
			page.evaluate(function sandboxed(css) {
				var h = window.innerHeight,
					renderWaitTime = 100, //ms TODO: user specifiable through options object
					finished = false,
					currIndex = 0,
					forceRemoveNestedRule = false;

				//split CSS so we can value the (selector) rules separately.
				//but first, handle stylesheet initial non nested @-rules.
				//they don't come with any associated rules, and should all be kept,
				//so just keep them in critical css, but don't include them in split
				var splitCSS = css.replace(/@(import|charset|namespace)[^;]*;/g, '');
				var split = splitCSS.split(/[{}]/g);

				var getNewValidCssSelector = function(i) {
					var newSel = split[i];
					/* HANDLE Nested @-rules */

					/*Case 1: @-rule with CSS properties inside [REMAIN]
						Can't remove @font-face rules here, don't know if used or not.
						Another check at end for this purpose.
					*/
					if (/@(font-face)/gi.test(newSel)) {
						//skip over this rule
						currIndex = css.indexOf('}', currIndex) + 1;
						return getNewValidCssSelector(i + 2);
					}
					/*Case 2: @-rule with CSS properties inside [REMOVE]
						@page
						This case doesn't need any special handling,
						as this "selector" won't match anything on the page,
						and will therefor be removed, together with it's css props
					*/

					/*Case 4: @-rule with full CSS (rules) inside [REMOVE]
						@media print|speech|aural, @keyframes
						Delete this rule and all its contents - doesn't belong in critical path CSS
					*/
					else if (/@(media (print|speech|aural)|(([a-z\-])*keyframes))/gi.test(newSel)) {
						//force delete on child css rules
						forceRemoveNestedRule = true;
						return getNewValidCssSelector(i + 1);
					}

					/*Case 3: @-rule with full CSS (rules) inside [REMAIN]
						This test is executed AFTER Case 4,
						since we here match every remaining @media,
						after @media print has been removed by Case 4 rule)
						- just skip this particular line (i.e. keep), and continue checking the CSS inside as normal
					*/
					else if (/@(media|(-moz-)?document|supports)/gi.test(newSel)) {
						return getNewValidCssSelector(i + 1);
					}
					/*
						Resume normal execution after end of @-media rule with inside CSS rules (Case 3)
						Also identify abrupt file end.
					*/
					else if (newSel.trim().length === 0) {
						//abrupt file end
						if (i + 1 >= split.length) {
							//end of file
							finished = true;
							return false;
						}
						//end of @-rule (Case 3)
						forceRemoveNestedRule = false;
						return getNewValidCssSelector(i + 1);
					}
					return i;
				};

				var removeSelector = function(sel, selectorsKept) {
					var selPos = css.indexOf(sel, currIndex);

					//check what comes next: { or ,
					var nextComma = css.indexOf(',', selPos);
					var nextOpenBracket = css.indexOf('{', selPos);

					if (selectorsKept > 0 || (nextComma > 0 && nextComma < nextOpenBracket)) {
						//we already kept selectors from this rule, so rule will stay

						//more selectors in selectorList, cut until (and including) next comma
						if (nextComma > 0 && nextComma < nextOpenBracket) {
							css = css.substring(0, selPos) + css.substring(nextComma + 1);
						}
						//final selector, cut until open bracket. Also remove previous comma, as the (new) last selector should not be followed by a comma.
						else {
							var prevComma = css.lastIndexOf(',', selPos);
							css = css.substring(0, prevComma) + css.substring(nextOpenBracket);
						}
					} else {
						//no part of selector (list) matched elements above fold on page - remove whole rule CSS rule
						var endRuleBracket = css.indexOf('}', nextOpenBracket);

						css = css.substring(0, selPos) + css.substring(endRuleBracket + 1);
					}
				};

				var processCssRules = function() {
					for (var i = 0; i < split.length; i = i + 2) {
						//step over non DOM CSS selectors (@-rules)
						i = getNewValidCssSelector(i);

						//reach end of CSS
						if (finished) {
							//call final function to exit outside of phantom evaluate scope
							window.callPhantom(css);
						}

						var fullSel = split[i];
						//fullSel can contain combined selectors
						//,f.e.  body, html {}
						//split and check one such selector at the time.
						var selSplit = fullSel.split(',');
						//keep track - if we remove all selectors, we also want to remove the whole rule.
						var selectorsKept = 0;
						var aboveFold;

						for (var j = 0; j < selSplit.length; j++) {
							var sel = selSplit[j];

							//some selectors can't be matched on page.
							//In these cases we test a slightly modified selectors instead, temp.
							var temp = sel;

							if (sel.indexOf(':') > -1) {
								//handle special case selectors, the ones that contain a semi colon (:)
								//many of these selectors can't be matched to anything on page via JS,
								//but that still might affect the above the fold styling

								//these psuedo selectors depend on an element,
								//so test element instead (would do the same for f.e. :hover, :focus, :active IF we wanted to keep them for critical path css, but we don't)
								temp = temp.replace(/(:?:before|:?:after)*/g, '');

								//if selector is purely psuedo (f.e. ::-moz-placeholder), just keep as is.
								//we can't match it to anything on page, but it can impact above the fold styles
								if (temp.replace(/:[:]?([a-zA-Z0-9\-\_])*/g, '').trim().length === 0) {
									currIndex = css.indexOf(sel, currIndex) + sel.length;
									selectorsKept++;
									continue;
								}

								//handle browser specific psuedo selectors bound to elements,
								//Example, button::-moz-focus-inner, input[type=number]::-webkit-inner-spin-button
								//remove browser specific pseudo and test for element
								temp = temp.replace(/:?:-[a-z-]*/g, '');
							}

							if (!forceRemoveNestedRule) {
								//now we have a selector to test, first grab any matching elements
								var el;
								try {
									el = document.querySelectorAll(temp);
								} catch (e) {
									//not a valid selector, remove it.
									removeSelector(sel, 0);
									continue;
								}

								//check if selector matched element(s) on page..
								aboveFold = false;

								for (var k = 0; k < el.length; k++) {
									var testEl = el[k];
									//temporarily force clear none in order to catch elements that clear previous content themselves and who w/o their styles could show up unstyled in above the fold content (if they rely on f.e. 'clear:both;' to clear some main content)
									testEl.style.clear = 'none';

									//check to see if any matched element is above the fold on current page
									//(in current viewport size)
									if (testEl.getBoundingClientRect().top < h) {
										//then we will save this selector
										aboveFold = true;
										selectorsKept++;

										//update currIndex so we only search from this point from here on.
										currIndex = css.indexOf(sel, currIndex);

										//set clear style back to what it was
										testEl.style.clear = '';
										//break, because matching 1 element is enough
										break;
									}
									//set clear style back to what it was
									testEl.style.clear = '';
								}
							} else {
								aboveFold = false;
							} //force removal of selector

							//if selector didn't match any elements above fold - delete selector from CSS
							if (aboveFold === false) {
								//update currIndex so we only search from this point from here on.
								currIndex = css.indexOf(sel, currIndex);
								//remove seletor (also removes rule, if nnothing left)
								removeSelector(sel, selectorsKept);
							}
						}
						//if rule stayed, move our cursor forward for matching new selectors
						if (selectorsKept > 0) {
							currIndex = css.indexOf('}', currIndex) + 1;
						}
					}

					//we're done - call final function to exit outside of phantom evaluate scope
					window.callPhantom(css);
				};

				//give some time (renderWaitTime) for sites like facebook that build their page dynamically,
				//otherwise we can miss some selectors (and therefor rules)
				//--tradeoff here: if site is too slow with dynamic content,
				//	it doesn't deserve to be in critical path.
				setTimeout(processCssRules, renderWaitTime);

			}, options.css);
		}
	});
}

var parser, parse, usage, options;

// test to see if we are running as a standalone script
// or as part of the node module
if (standaloneMode) {
	parse = parseOptions;
	usage = usageString;
} else {
	parser = require('../options-parser');
	parse = parser.parse;
	usage = parser.usage;
}

try {
	options = parse(system.args.slice(1));
} catch (ex) {

    errorlog('Caught error parsing arguments: ' + ex.message);

    // the usage string does not make sense to show if running via Node
    if(standaloneMode) {
        errorlog('\nUsage: phantomjs penthouse.js ' + usage);
    }

	phantom.exit(1);
}

// set defaults
if (!options.width) options.width = 1300;
if (!options.height) options.height = 900;

main(options);
<commit_msg>wrap phantom exit in setTimeout call<commit_after>'use strict';
var standaloneMode = standaloneMode || false;

var page = require('webpage').create(),
	fs = require('fs'),
	system = require('system'),
	DEBUG = false,
	stdout = system.stdout; // for using this as a file

var combineArgsString = function(argsArr) {
	return [].join.call(argsArr, ' ') + '\n';
};

// monkey patch for directing errors to stderr
// https://github.com/ariya/phantomjs/issues/10150#issuecomment-28707859
var errorlog = function() {
	system.stderr.write(combineArgsString(arguments));
};

var debug = function() {
	if (DEBUG) errorlog('DEBUG: ' + combineArgsString(arguments));
};

// discard stdout from phantom exit;
var phantomExit = function(code) {
	if (page) {
		page.close();
	}
	setTimeout(function() {
		phantom.exit(code);
	}, 0);
};

//don't confuse analytics more than necessary when visiting websites
page.settings.userAgent = 'Penthouse Critical Path CSS Generator';

/* prevent page JS errors from being output to final CSS */
page.onError = function(msg, trace) {
	//do nothing
};

page.onResourceError = function(resourceError) {
  page.reason = resourceError.errorString;
  page.reason_url = resourceError.url;
};

var main = function(options) {
  debug('main(): ', JSON.stringify(options));
//final cleanup
//remove all empty rules, and remove leading/trailing whitespace
	try {
		var f = fs.open(options.css, 'r');

		//preformat css
		var cssPreformat;
		if (standaloneMode) {
			cssPreformat = cssPreformatter;
		} else {
			cssPreformat = require('./css-preformatter.js');
		}
		options.css = cssPreformat(f.read());
	} catch (e) {
		errorlog(e);
		phantomExit(1);
	}

  // start the critical path CSS generation
  getCriticalPathCss(options);
};

function cleanup(css) {
	//remove all animation rules, as keyframes have already been removed
	css = css.replace(/(-webkit-|-moz-|-ms-|-o-)?animation[ ]?:[^;{}]*;/gm, '');
	//remove all empty rules, and remove leading/trailing whitespace
	return css.replace(/[^{}]*\{\s*\}/gm, '').trim();
}

/* Final function
 * Get's called from getCriticalPathCss when CSS extraction from page is done*/
page.onCallback = function(css) {
  debug('phantom.onCallback');

  try {
    if (css) {
      // we are done - clean up the final css
      var finalCss = cleanup(css);

      // remove unused @fontface rules
			var ffRemover;
      if (standaloneMode) {
        ffRemover = unusedFontfaceRemover;
      } else {
        ffRemover = require('./unused-fontface-remover.js');
      }
      finalCss = ffRemover(finalCss);

			if(finalCss.trim().length === 0){
				errorlog('Note: Generated critical css was empty for URL: ' + options.url);
			}

      // return the critical css!
      stdout.write(finalCss);
      phantomExit(0);
    } else {
      // No css. This is not an error on our part
      // but still safer to warn the end user, in case they made a mistake
      errorlog('Note: Generated critical css was empty for URL: ' + options.url);
      // for consisteny, still generate output (will be empty)
      stdout.write(css);
      phantomExit(0);
    }

  } catch (ex) {
    debug('phantom.onCallback -> error', ex);
    errorlog('error: ' + ex);
    phantomExit(1);
  }
};

/*
 * Tests each selector in css file at specified resolution,
 * to see if any such elements appears above the fold on the page
 * modifies CSS - removes selectors that don't appear, and empty rules
 *
 * @param options.url the url as a string
 * @param options.css the css as a string
 * @param options.width the width of viewport
 * @param options.height the height of viewport
 ---------------------------------------------------------*/
function getCriticalPathCss(options) {
	debug('getCriticalPathCss():', JSON.stringify(options));

	page.viewportSize = {
		width: options.width,
		height: options.height
	};

	page.open(options.url, function(status) {
		if (status !== 'success') {
			errorlog('Error opening url \'' + page.reason_url + '\': ' + page.reason);
			phantomExit(1);
		} else {

			debug('Starting sandboxed evaluation of CSS\n', options.css);
			// sandboxed environments - no outside references
			// arguments and return value must be primitives
			// @see http://phantomjs.org/api/webpage/method/evaluate.html
			page.evaluate(function sandboxed(css) {
				var h = window.innerHeight,
					renderWaitTime = 100, //ms TODO: user specifiable through options object
					finished = false,
					currIndex = 0,
					forceRemoveNestedRule = false;

				//split CSS so we can value the (selector) rules separately.
				//but first, handle stylesheet initial non nested @-rules.
				//they don't come with any associated rules, and should all be kept,
				//so just keep them in critical css, but don't include them in split
				var splitCSS = css.replace(/@(import|charset|namespace)[^;]*;/g, '');
				var split = splitCSS.split(/[{}]/g);

				var getNewValidCssSelector = function(i) {
					var newSel = split[i];
					/* HANDLE Nested @-rules */

					/*Case 1: @-rule with CSS properties inside [REMAIN]
						Can't remove @font-face rules here, don't know if used or not.
						Another check at end for this purpose.
					*/
					if (/@(font-face)/gi.test(newSel)) {
						//skip over this rule
						currIndex = css.indexOf('}', currIndex) + 1;
						return getNewValidCssSelector(i + 2);
					}
					/*Case 2: @-rule with CSS properties inside [REMOVE]
						@page
						This case doesn't need any special handling,
						as this "selector" won't match anything on the page,
						and will therefor be removed, together with it's css props
					*/

					/*Case 4: @-rule with full CSS (rules) inside [REMOVE]
						@media print|speech|aural, @keyframes
						Delete this rule and all its contents - doesn't belong in critical path CSS
					*/
					else if (/@(media (print|speech|aural)|(([a-z\-])*keyframes))/gi.test(newSel)) {
						//force delete on child css rules
						forceRemoveNestedRule = true;
						return getNewValidCssSelector(i + 1);
					}

					/*Case 3: @-rule with full CSS (rules) inside [REMAIN]
						This test is executed AFTER Case 4,
						since we here match every remaining @media,
						after @media print has been removed by Case 4 rule)
						- just skip this particular line (i.e. keep), and continue checking the CSS inside as normal
					*/
					else if (/@(media|(-moz-)?document|supports)/gi.test(newSel)) {
						return getNewValidCssSelector(i + 1);
					}
					/*
						Resume normal execution after end of @-media rule with inside CSS rules (Case 3)
						Also identify abrupt file end.
					*/
					else if (newSel.trim().length === 0) {
						//abrupt file end
						if (i + 1 >= split.length) {
							//end of file
							finished = true;
							return false;
						}
						//end of @-rule (Case 3)
						forceRemoveNestedRule = false;
						return getNewValidCssSelector(i + 1);
					}
					return i;
				};

				var removeSelector = function(sel, selectorsKept) {
					var selPos = css.indexOf(sel, currIndex);

					//check what comes next: { or ,
					var nextComma = css.indexOf(',', selPos);
					var nextOpenBracket = css.indexOf('{', selPos);

					if (selectorsKept > 0 || (nextComma > 0 && nextComma < nextOpenBracket)) {
						//we already kept selectors from this rule, so rule will stay

						//more selectors in selectorList, cut until (and including) next comma
						if (nextComma > 0 && nextComma < nextOpenBracket) {
							css = css.substring(0, selPos) + css.substring(nextComma + 1);
						}
						//final selector, cut until open bracket. Also remove previous comma, as the (new) last selector should not be followed by a comma.
						else {
							var prevComma = css.lastIndexOf(',', selPos);
							css = css.substring(0, prevComma) + css.substring(nextOpenBracket);
						}
					} else {
						//no part of selector (list) matched elements above fold on page - remove whole rule CSS rule
						var endRuleBracket = css.indexOf('}', nextOpenBracket);

						css = css.substring(0, selPos) + css.substring(endRuleBracket + 1);
					}
				};

				var processCssRules = function() {
					for (var i = 0; i < split.length; i = i + 2) {
						//step over non DOM CSS selectors (@-rules)
						i = getNewValidCssSelector(i);

						//reach end of CSS
						if (finished) {
							//call final function to exit outside of phantom evaluate scope
							window.callPhantom(css);
						}

						var fullSel = split[i];
						//fullSel can contain combined selectors
						//,f.e.  body, html {}
						//split and check one such selector at the time.
						var selSplit = fullSel.split(',');
						//keep track - if we remove all selectors, we also want to remove the whole rule.
						var selectorsKept = 0;
						var aboveFold;

						for (var j = 0; j < selSplit.length; j++) {
							var sel = selSplit[j];

							//some selectors can't be matched on page.
							//In these cases we test a slightly modified selectors instead, temp.
							var temp = sel;

							if (sel.indexOf(':') > -1) {
								//handle special case selectors, the ones that contain a semi colon (:)
								//many of these selectors can't be matched to anything on page via JS,
								//but that still might affect the above the fold styling

								//these psuedo selectors depend on an element,
								//so test element instead (would do the same for f.e. :hover, :focus, :active IF we wanted to keep them for critical path css, but we don't)
								temp = temp.replace(/(:?:before|:?:after)*/g, '');

								//if selector is purely psuedo (f.e. ::-moz-placeholder), just keep as is.
								//we can't match it to anything on page, but it can impact above the fold styles
								if (temp.replace(/:[:]?([a-zA-Z0-9\-\_])*/g, '').trim().length === 0) {
									currIndex = css.indexOf(sel, currIndex) + sel.length;
									selectorsKept++;
									continue;
								}

								//handle browser specific psuedo selectors bound to elements,
								//Example, button::-moz-focus-inner, input[type=number]::-webkit-inner-spin-button
								//remove browser specific pseudo and test for element
								temp = temp.replace(/:?:-[a-z-]*/g, '');
							}

							if (!forceRemoveNestedRule) {
								//now we have a selector to test, first grab any matching elements
								var el;
								try {
									el = document.querySelectorAll(temp);
								} catch (e) {
									//not a valid selector, remove it.
									removeSelector(sel, 0);
									continue;
								}

								//check if selector matched element(s) on page..
								aboveFold = false;

								for (var k = 0; k < el.length; k++) {
									var testEl = el[k];
									//temporarily force clear none in order to catch elements that clear previous content themselves and who w/o their styles could show up unstyled in above the fold content (if they rely on f.e. 'clear:both;' to clear some main content)
									testEl.style.clear = 'none';

									//check to see if any matched element is above the fold on current page
									//(in current viewport size)
									if (testEl.getBoundingClientRect().top < h) {
										//then we will save this selector
										aboveFold = true;
										selectorsKept++;

										//update currIndex so we only search from this point from here on.
										currIndex = css.indexOf(sel, currIndex);

										//set clear style back to what it was
										testEl.style.clear = '';
										//break, because matching 1 element is enough
										break;
									}
									//set clear style back to what it was
									testEl.style.clear = '';
								}
							} else {
								aboveFold = false;
							} //force removal of selector

							//if selector didn't match any elements above fold - delete selector from CSS
							if (aboveFold === false) {
								//update currIndex so we only search from this point from here on.
								currIndex = css.indexOf(sel, currIndex);
								//remove seletor (also removes rule, if nnothing left)
								removeSelector(sel, selectorsKept);
							}
						}
						//if rule stayed, move our cursor forward for matching new selectors
						if (selectorsKept > 0) {
							currIndex = css.indexOf('}', currIndex) + 1;
						}
					}

					//we're done - call final function to exit outside of phantom evaluate scope
					window.callPhantom(css);
				};

				//give some time (renderWaitTime) for sites like facebook that build their page dynamically,
				//otherwise we can miss some selectors (and therefor rules)
				//--tradeoff here: if site is too slow with dynamic content,
				//	it doesn't deserve to be in critical path.
				setTimeout(processCssRules, renderWaitTime);

			}, options.css);
		}
	});
}

var parser, parse, usage, options;

// test to see if we are running as a standalone script
// or as part of the node module
if (standaloneMode) {
	parse = parseOptions;
	usage = usageString;
} else {
	parser = require('../options-parser');
	parse = parser.parse;
	usage = parser.usage;
}

try {
	options = parse(system.args.slice(1));
} catch (ex) {

    errorlog('Caught error parsing arguments: ' + ex.message);

    // the usage string does not make sense to show if running via Node
    if(standaloneMode) {
        errorlog('\nUsage: phantomjs penthouse.js ' + usage);
    }

	phantomExit(1);
}

// set defaults
if (!options.width) options.width = 1300;
if (!options.height) options.height = 900;

main(options);
<|endoftext|>


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

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

/**
 * Server Default Settings
 */
var ServeDefaults = {
    port: 3000,
    autoreload: true,
    localtunnel: false
};

/*!
 * Command setup.
 */

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

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

util.inherits(AppCommand, Command);

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

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

    // optional parameters
    options.port = options.port || ServeDefaults.port;
    options.autoreload = (typeof options.autoreload === 'boolean') ? options.autoreload : ServeDefaults.autoreload;
    options.localtunnel = (typeof options.localtunnel === 'boolean') ? options.localtunnel : ServeDefaults.localtunnel;
    callback = callback || function() {};

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

    return this.phonegap;
};

/*!
 * Execute command.
 */

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

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

    var _errorHandler = function(err) {
        self.phonegap.emit('error', err);
        callback(err);
    };

    /**
     * Start Server
     */
    var startServer = function (self, options) {
        self.phonegap.emit('log', 'starting app server...');

        server.listen(options)
              .on('error', _errorHandler)
              .on('log', function(statusCode, url) {
                  self.phonegap.emit('log', statusCode, url);
              })
              .on('complete', function(data) {
                  callback(null, data);
              });
    };

    // prepare to execute appropriate cordova hooks
    // try because cordova is shitting the bed on promise fulfillment
    try {
        cordova.prepare([], function(err, data) {
            startServer(self, options);
        });
    } catch (e) {
        startServer(self, options);
    }
};
<commit_msg>Pass reference to self in options so connect-phonegap has access to cordova<commit_after>/*!
 * Module dependencies.
 */

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

/**
 * Server Default Settings
 */
var ServeDefaults = {
    port: 3000,
    autoreload: true,
    localtunnel: false
};

/*!
 * Command setup.
 */

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

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

util.inherits(AppCommand, Command);

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

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

    // optional parameters
    options.port = options.port || ServeDefaults.port;
    options.autoreload = (typeof options.autoreload === 'boolean') ? options.autoreload : ServeDefaults.autoreload;
    options.localtunnel = (typeof options.localtunnel === 'boolean') ? options.localtunnel : ServeDefaults.localtunnel;
    options.phonegap = this.phonegap;
    callback = callback || function() {};

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

    return this.phonegap;
};

/*!
 * Execute command.
 */

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

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

    var _errorHandler = function(err) {
        self.phonegap.emit('error', err);
        callback(err);
    };

    /**
     * Start Server
     */
    var startServer = function (self, options) {
        self.phonegap.emit('log', 'starting app server...');

        server.listen(options)
              .on('error', _errorHandler)
              .on('log', function(statusCode, url) {
                  self.phonegap.emit('log', statusCode, url);
              })
              .on('complete', function(data) {
                  callback(null, data);
              });
    };

    // prepare to execute appropriate cordova hooks
    // try because cordova is shitting the bed on promise fulfillment
    try {
        cordova.prepare([], function(err, data) {
            startServer(self, options);
        });
    } catch (e) {
        startServer(self, options);
    }
};
<|endoftext|>