<commit_before>#!/usr/local/bin/node --es_staging
"use strict";

class Option{

	/**
	 * Create a new Option instance.
	 *
	 * @param {String|Array} names  - Comma-separated list of names (e.g., "-l, --long-list, --length")
	 * @param {String}       params - List of args the option expects (e.g., "<num> <type>" or "[num] [type]", etc)
	 * @constructor
	 */
	constructor(names, params){
		let shortNames = {};
		let longNames  = {};

		/** Define the Option's names, both long and short forms */
		if(!Array.isArray(names))
			names = (""+names).split(/,/g);

		let match;
		for(let i of names){
			i = i.trim();

			/** Short option */
			if(match = i.match(/^-(\w)$/))
				shortNames[match[1]] = true;

			/** Long option */
			else longNames[i.replace(/^-+/, "")] = true;
		}

		this.shortNames  = Object.keys(shortNames);
		this.longNames   = Object.keys(longNames);

		/** Parameters accepted/expected by this Option */
		if(Array.isArray(params))
			params = params.join(" ");

		/** Remove punctuation added for readability */
		params = params.replace(/[<\[\]\(\)\{\}>]/g, "");

		/** Split our cleaned-up params list by whitespace */
		params = params.split(/\s+/g);

		this.params = [];
		this.values = [];
		for(let name of params){
			if(name){
				this.params.push(name);
				if(/\.{3}$/.test(name))
					this.variadic = true;
			}
		}
	}

	/**
	 * Return the number of parameters this option expects/accepts.
	 *
	 * @readonly
	 * @return {Number}
	 */
	get arity(){
		return this.params ? this.params.length : 0;
	}

	/**
	 * Return an array of all names this Option recognises, long or short.
	 *
	 * @readonly
	 * @return {Array}
	 */
	get names(){
		return this.shortNames.concat(this.longNames);
	}

	/**
	 * Whether or not the Option still has room for remaining parameters.
	 *
	 * @readonly
	 * @return {Boolean}
	 */
	get canCollect(){
		return this.variadic || this.values.length < this.params.length;
	}
}

/**
 * Convert a kebab-cased-string into a camelCasedString.
 *
 * @param {String} input
 * @return {String}
 */
function kebabToCamelCase(input){
	return input.toLowerCase().replace(/([a-z])-+([a-z])/g, function(match, a, b){
		return a + b.toUpperCase();
	});
}

function getOpts(input, optdef, config){

	/** Optional options hash controlling option-creation */
	config           = config || {};
	let noCamelCase  = config.noCamelCase;
	let ignoreEquals = config.ignoreEquals;

	let shortNames   = {};
	let longNames    = {};
	let result       = {
		options: new Object(null),
		argv:    []
	};

	for(let i in optdef){
		let option = new Option(i, optdef[i]);

		option.shortNames.forEach(n => {

			/** Don't allow duplicate option definitions */
			if(shortNames[n] !== undefined)
				throw new ReferenceError(`Short option "-${n}" already defined`);

			shortNames["-"+n] = option;
		});

		option.longNames.forEach(n => {

			/** Again, don't allow duplicate option names; that obviously isn't what the author intended */
			if(longNames[n] !== undefined)
				throw new ReferenceError(`Long option "--${n}" already defined`);

			longNames["--"+n] = option;
		});
	}

	/** Pointer to the option that's currently picking up arguments */
	let currentOption;

	/** Pushes the contents of the current option into result.options and resets the pointer */
	let wrapItUp = () => {
		let optValue = currentOption.values;

		/** Don't store solitary values in an array. Store them directly as strings */
		if(currentOption.arity === 1)
			optValue = optValue[0];

		currentOption.names.forEach(n => {

			/** Decide whether to camelCase this option name */
			if(!noCamelCase && /-/.test(n))
				n = kebabToCamelCase(n);

			result.options[n] = optValue;
		});
		currentOption = null;
	};

	/** Is pre-processing of the argument list necessary? */
	if(!ignoreEquals){

		/** Limit equals-sign expansion to items that begin with recognised option names */
		let legalNames = new RegExp("^(?:" + Object.keys(longNames).join("|") + ")=");

		for(let i = 0, l = input.length; i < l; ++i){
			let arg   = input[i];

			/** Expand "--option=value" sequences to become "--option value" */
			if(legalNames.test(arg)){
				let match = arg.match(/^([^=]+)=(.+)$/);
				input.splice(i, 1, match[1], match[2]);
				l = input.length;
				i += 2;
				continue;
			}
		}
	}

	/** Start processing the arguments we were given to handle */
	for(let i = 0, l = input.length; i < l; ++i){
		let arg = input[i];
		let opt = shortNames[arg] || longNames[arg];

		/** This argument matches a recognised option name */
		if(opt){

			/** Did we have an existing option that was collecting values? */
			if(currentOption) wrapItUp();

			/** This option takes at least one argument */
			if(opt.arity)
				currentOption = opt;

			/** This option takes no arguments, so just assign it a value of "true" */
			else opt.names.forEach(n => {result.options[n] = true});
		}

		else{
			/** A previous option is still collecting arguments */
			if(currentOption && currentOption.canCollect)
				currentOption.values.push(arg);

			/** Not an option's argument, just a... "regular" argument or something */
			else{
				result.argv.push(arg);

				/** If there was an option collecting stuff, show it the door */
				currentOption && wrapItUp();
			}
		}
	}

	/** Ended abruptly? */
	if(currentOption) wrapItUp();

	return result;
}

let process = require("process");

let pls = getOpts(process.argv, {
	"-h, --help, --usage":    "",
	"-v, --version":          "",
	"-n, --number-of-lines":  "<number>",
	"-l, --level":            "<level>",
	"-t, --type":             "<type>",
	"-z, --set-size":         "<size>",
	"-c, --set-config":       "<key> <value>",
	"-d, --delete-files":     "<safely> <files...>",
	"-s, -T, --set-type":     "<key> <type>"
});
console.log(pls);
<commit_msg>Add support for bundled options<commit_after>#!/usr/local/bin/node --es_staging
"use strict";

class Option{

	/**
	 * Create a new Option instance.
	 *
	 * @param {String|Array} names  - Comma-separated list of names (e.g., "-l, --long-list, --length")
	 * @param {String}       params - List of args the option expects (e.g., "<num> <type>" or "[num] [type]", etc)
	 * @constructor
	 */
	constructor(names, params){
		let shortNames = {};
		let longNames  = {};

		/** Define the Option's names, both long and short forms */
		if(!Array.isArray(names))
			names = (""+names).split(/,/g);

		let match;
		for(let i of names){
			i = i.trim();

			/** Short option */
			if(match = i.match(/^-(\w)$/))
				shortNames[match[1]] = true;

			/** Long option */
			else longNames[i.replace(/^-+/, "")] = true;
		}

		this.shortNames  = Object.keys(shortNames);
		this.longNames   = Object.keys(longNames);

		/** Parameters accepted/expected by this Option */
		if(Array.isArray(params))
			params = params.join(" ");

		/** Split our parameter list by whitespace */
		params = params.split(/\s+/g);

		/** Remove punctuation added for readability */
		params = params.map(e => e.replace(/^<(.+?)>$|^\[(.+?)\]$|^\((.+?)\)$/gm, function(){
			return [].slice.call(arguments, 1, 4).filter(e => e).join("");
		}));

		this.params = [];
		this.values = [];
		for(let name of params){

			/** Make sure the parameter isn't blank */
			if(name){

				/** Check for an additional regex pattern to use for disambiguating bundled options */
				let splitName = name.match(/^([^=]+)(?:=(.+)?)?$/);

				this.params.push({
					name:      splitName[1],
					pattern:   splitName[2] || ".+"
				});

				if(/\.{3}$/.test(name))
					this.variadic = true;
			}
		}
	}

	/**
	 * Return a regex string for matching this option when expressed in bundled short-form.
	 *
	 * @return {String}
	 */
	getBundlePattern(){

		/** We've already generated this regex before; just return the cached result */
		if(this._bundlePattern)
			return this._bundlePattern;

		let param = this.params.map(p => "("+p.pattern+")?").join("");
		let names = this.shortNames.length === 1 ? this.shortNames[0] : ("[" + this.shortNames.join("") + "]");
		return (this._bundlePattern = names + param);
	}

	/**
	 * Return the number of parameters this option expects/accepts.
	 *
	 * @readonly
	 * @return {Number}
	 */
	get arity(){
		return this.params ? this.params.length : 0;
	}

	/**
	 * Return an array of all names this Option recognises, long or short.
	 *
	 * @readonly
	 * @return {Array}
	 */
	get names(){
		return this.shortNames.concat(this.longNames);
	}

	/**
	 * Whether or not the Option still has room for remaining parameters.
	 *
	 * @readonly
	 * @return {Boolean}
	 */
	get canCollect(){
		return this.variadic || this.values.length < this.params.length;
	}
}

/**
 * Convert a kebab-cased-string into a camelCasedString.
 *
 * @param {String} input
 * @return {String}
 */
function kebabToCamelCase(input){
	return input.toLowerCase().replace(/([a-z])-+([a-z])/g, function(match, a, b){
		return a + b.toUpperCase();
	});
}

function getOpts(input, optdef, config){

	/** Optional options hash controlling option-creation */
	config           = config || {};
	let noCamelCase  = config.noCamelCase;
	let ignoreEquals = config.ignoreEquals;

	let shortNames   = {};
	let longNames    = {};
	let result       = {
		options: new Object(null),
		argv:    []
	};

	for(let i in optdef){
		let option = new Option(i, optdef[i]);

		option.shortNames.forEach(n => {

			/** Don't allow duplicate option definitions */
			if(shortNames[n] !== undefined)
				throw new ReferenceError(`Short option "-${n}" already defined`);

			shortNames["-"+n] = option;
		});

		option.longNames.forEach(n => {

			/** Again, don't allow duplicate option names; that obviously isn't what the author intended */
			if(longNames[n] !== undefined)
				throw new ReferenceError(`Long option "--${n}" already defined`);

			longNames["--"+n] = option;
		});
	}

	/** Pointer to the option that's currently picking up arguments */
	let currentOption;

	/** Pushes the contents of the current option into result.options and resets the pointer */
	let wrapItUp = () => {
		let optValue = currentOption.values;

		/** Don't store solitary values in an array. Store them directly as strings */
		if(currentOption.arity === 1)
			optValue = optValue[0];

		currentOption.names.forEach(n => {

			/** Decide whether to camelCase this option name */
			if(!noCamelCase && /-/.test(n))
				n = kebabToCamelCase(n);

			result.options[n] = optValue;
		});
		currentOption = null;
	};

	/** Tackle bundling: make sure there's at least one option with a short name to work with */
	let nameKeys = Object.keys(shortNames);
	let bundleMatch, bundlePatterns;

	if(nameKeys.length){
		bundlePatterns  = nameKeys.map(n => shortNames[n].getBundlePattern()).join("|");
		bundleMatch     = new RegExp("-("+bundlePatterns+")+", "g");
		bundlePatterns  = new RegExp(bundlePatterns, "g");
	}

	/** Is pre-processing of the argument list necessary? */
	if(!ignoreEquals || bundleMatch){

		/** Limit equals-sign expansion to items that begin with recognised option names */
		let legalNames = new RegExp("^(?:" + Object.keys(longNames).join("|") + ")=");

		for(let i = 0, l = input.length; i < l; ++i){
			let arg   = input[i];

			/** Expand bundled option clusters ("-mvl2" -> "-m -v -l 2") */
			if(bundleMatch.test(arg)){
				let segments = arg.match(bundlePatterns).map(m => {

					/** Obtain a pointer to the original Option instance that defined this short-name */
					let opt = shortNames["-"+m[0]];

					/** This option doesn't accept any arguments, so just keep it simple */
					if(!opt.arity)
						return ["-"+m[0]];

					let matches = m.match(new RegExp(opt.getBundlePattern())).slice(1).filter(i => i);
					return ["-"+m[0], ...matches];
				});

				segments = [].concat(...segments);
				input.splice(i, 1, ...segments);
				l = input.length;
				i += segments.length;
				console.log("Input is now: ", input);
				continue;
			}

			/** Expand "--option=value" sequences to become "--option value" */
			if(legalNames.test(arg)){
				let match = arg.match(/^([^=]+)=(.+)$/);
				input.splice(i, 1, match[1], match[2]);
				l = input.length;
				i += 2;
				continue;
			}
		}
	}

	/** Start processing the arguments we were given to handle */
	for(let i = 0, l = input.length; i < l; ++i){
		let arg = input[i];
		let opt = shortNames[arg] || longNames[arg];

		/** This argument matches a recognised option name */
		if(opt){

			/** Did we have an existing option that was collecting values? */
			if(currentOption) wrapItUp();

			/** This option takes at least one argument */
			if(opt.arity)
				currentOption = opt;

			/** This option takes no arguments, so just assign it a value of "true" */
			else opt.names.forEach(n => {result.options[n] = true});
		}

		else{
			/** A previous option is still collecting arguments */
			if(currentOption && currentOption.canCollect)
				currentOption.values.push(arg);

			/** Not an option's argument, just a... "regular" argument or something */
			else{
				result.argv.push(arg);

				/** If there was an option collecting stuff, show it the door */
				currentOption && wrapItUp();
			}
		}
	}

	/** Ended abruptly? */
	if(currentOption) wrapItUp();

	return result;
}

let process = require("process");

let pls = getOpts(process.argv, {
	"-h, --help, --usage":    "",
	"-v, --version":          "",
	"-n, --number-of-lines":  "<number=\\d+>",
	"-l, --level":            "<level>",
	"-t, --type":             "<type>",
	"-z, --set-size":         "[width=\\d+] [height=\\d+]",
	"-c, --set-config":       "<numbers=\\d+> <letters=[A-Za-z]+>",
	"-d, --delete-files":     "<safely> <files...>",
	"-s, -T, --set-type":     "<key> <type>"
});
console.log(pls);
<|endoftext|>


<commit_before>const mysql = require('mysql');
const helper = require('think-helper');
const assert = require('assert');
const Debounce = require('think-debounce');
const thinkInstance = require('think-instance');

const debug = require('debug')('think-mysql');
const debounceInstance = new Debounce();
const QUERY = Symbol('think-mysql-query');
const CONNECTION_LOST = Symbol('think-mysql-connection-lost');

const defaultConfig = {
  port: 3306,
  host: '127.0.0.1',
  user: '',
  password: '',
  database: '',
  connectionLimit: 1,
  logger: console.log.bind(console), /* eslint no-console: ["error", { allow: ["log"] }] */
  logConnect: false,
  logSql: false
};

/**
 * transaction status
 */
const TRANSACTION = {
  start: 1,
  end: 2
};

class ThinkMysql {
  /**
   * @param  {Object} config [connection options]
   */
  constructor(config) {
    config = helper.extend({}, defaultConfig, config);
    this.config = config;
    this.pool = mysql.createPool(helper.omit(config, 'logger,logConnect,logSql'));

    this.pool.on('acquire', connection => {
      debug(`acquire: Connection ${connection.threadId} acquired`);
    });
    this.pool.on('connection', () => {
      debug('connection: A new connection is made within the pool');
    });
    this.pool.on('enqueue', () => {
      debug('enqueue: Waiting for available connection slot');
    });
    this.pool.on('release', connection => {
      debug(`release: Connection ${connection.threadId} released`);
    });

    // log connect
    if (config.logConnect) {
      let connectionPath = '';
      if (config.socketPath) {
        connectionPath = config.socketPath;
      } else {
        connectionPath = `mysql://${config.user}:${config.password}@${config.host}:${config.port || 3306}/${config.database}`;
      }
      config.logger(connectionPath);
    }
  }
  /**
   * get connection
   */
  getConnection(connection) {
    if (connection && !connection[CONNECTION_LOST]) return Promise.resolve(connection);
    return helper.promisify(this.pool.getConnection, this.pool)();
  }
  /**
   * start transaction
   * @param {Object} connection
   */
  startTrans(connection) {
    return this.getConnection(connection).then(connection => {
      return this.query({
        sql: 'START TRANSACTION',
        transaction: TRANSACTION.start,
        debounce: false
      }, connection).then(() => connection);
    });
  }
  /**
   * commit transaction
   * @param {Object} connection
   */
  commit(connection) {
    return this.query({
      sql: 'COMMIT',
      transaction: TRANSACTION.end,
      debounce: false
    }, connection);
  }
  /**
   * rollback transaction
   * @param {Object} connection
   */
  rollback(connection) {
    return this.query({
      sql: 'ROLLBACK',
      transaction: TRANSACTION.end,
      debounce: false
    }, connection);
  }
  /**
   * transaction
   * @param {Function} fn
   * @param {Object} connection
   */
  transaction(fn, connection) {
    assert(helper.isFunction(fn), 'fn must be a function');
    return this.getConnection(connection).then(connection => {
      return this.startTrans(connection).then(() => {
        return fn(connection);
      }).then(data => {
        return this.commit(connection).then(() => data);
      }).catch(err => {
        return this.rollback(connection).then(() => Promise.reject(err));
      });
    });
  }
  /**
   * query data
   */
  [QUERY](sqlOptions, connection, startTime, times = 0) {
    const queryFn = helper.promisify(connection.query, connection);
    return queryFn(sqlOptions).catch(err => err).then(data => {
      this.releaseConnection(connection);

      // if server close connection, then retry it
      if (helper.isError(data) && data.code === 'PROTOCOL_CONNECTION_LOST') {
        connection[CONNECTION_LOST] = true;
        if (times < 3) {
          return this.getConnection().then(connection => {
            return this[QUERY](sqlOptions, connection, startTime, times + 1);
          });
        }
      }
      // log sql
      if (this.config.logSql) {
        const endTime = Date.now();
        this.config.logger(`SQL: ${sqlOptions.sql}, Time: ${endTime - startTime}ms`);
      }
      if (helper.isError(data)) return Promise.reject(data);
      return data;
    });
  }
  /**
   * release connection
   */
  releaseConnection(connection) {
    // if not in transaction, release connection
    if (connection.transaction !== TRANSACTION.start) {
      // connection maybe already released, it will throw an Error
      try {
        connection.release();
      } catch (e) {}
    }
  }
  /**
   * query({
   *  sql: 'SELECT * FROM `books` WHERE `author` = ?',
   *  timeout: 40000, // 40s
   *  values: ['David']
   * })
   * @param {Object} sqlOptions
   * @param {Object} connection
   */
  query(sqlOptions, connection) {
    if (helper.isString(sqlOptions)) {
      sqlOptions = {sql: sqlOptions};
    }
    if (sqlOptions.debounce === undefined) {
      sqlOptions.debounce = true;
    }
    const startTime = Date.now();
    if (sqlOptions.debounce) {
      const key = JSON.stringify(sqlOptions);
      return debounceInstance.debounce(key, () => {
        return this.getConnection(connection).then(connection => {
          return this[QUERY](sqlOptions, connection, startTime);
        });
      });
    }
    return this.getConnection(connection).then(connection => {
      // set transaction status to connection
      if (sqlOptions.transaction) {
        if (sqlOptions.transaction === TRANSACTION.start) {
          if (connection.transaction === TRANSACTION.start) return;
        } else if (sqlOptions.transaction === TRANSACTION.end) {
          if (connection.transaction !== TRANSACTION.start) {
            this.releaseConnection(connection);
            return;
          }
        }
        connection.transaction = sqlOptions.transaction;
      }
      return this[QUERY](sqlOptions, connection, startTime);
    });
  }

  /**
   * execute
   * @param  {Array} args []
   * @returns {Promise}
   */
  execute(sqlOptions, connection) {
    if (helper.isString(sqlOptions)) {
      sqlOptions = {sql: sqlOptions};
    }
    sqlOptions.debounce = false;
    return this.query(sqlOptions, connection);
  }
  /**
   * close
   * @returns {Promise}
   */
  close() {
    return helper.promisify(this.pool.end, this.pool)();
  }
}
module.exports = thinkInstance(ThinkMysql);
<commit_msg>support debunce from config<commit_after>const mysql = require('mysql');
const helper = require('think-helper');
const assert = require('assert');
const Debounce = require('think-debounce');
const thinkInstance = require('think-instance');

const debug = require('debug')('think-mysql');
const debounceInstance = new Debounce();
const QUERY = Symbol('think-mysql-query');
const CONNECTION_LOST = Symbol('think-mysql-connection-lost');

const defaultConfig = {
  port: 3306,
  host: '127.0.0.1',
  user: '',
  password: '',
  database: '',
  connectionLimit: 1,
  logger: console.log.bind(console), /* eslint no-console: ["error", { allow: ["log"] }] */
  logConnect: false,
  logSql: false
};

/**
 * transaction status
 */
const TRANSACTION = {
  start: 1,
  end: 2
};

class ThinkMysql {
  /**
   * @param  {Object} config [connection options]
   */
  constructor(config) {
    config = helper.extend({}, defaultConfig, config);
    this.config = config;
    this.pool = mysql.createPool(helper.omit(config, 'logger,logConnect,logSql'));

    this.pool.on('acquire', connection => {
      debug(`acquire: Connection ${connection.threadId} acquired`);
    });
    this.pool.on('connection', () => {
      debug('connection: A new connection is made within the pool');
    });
    this.pool.on('enqueue', () => {
      debug('enqueue: Waiting for available connection slot');
    });
    this.pool.on('release', connection => {
      debug(`release: Connection ${connection.threadId} released`);
    });

    // log connect
    if (config.logConnect) {
      let connectionPath = '';
      if (config.socketPath) {
        connectionPath = config.socketPath;
      } else {
        connectionPath = `mysql://${config.user}:${config.password}@${config.host}:${config.port || 3306}/${config.database}`;
      }
      config.logger(connectionPath);
    }
  }
  /**
   * get connection
   */
  getConnection(connection) {
    if (connection && !connection[CONNECTION_LOST]) return Promise.resolve(connection);
    return helper.promisify(this.pool.getConnection, this.pool)();
  }
  /**
   * start transaction
   * @param {Object} connection
   */
  startTrans(connection) {
    return this.getConnection(connection).then(connection => {
      return this.query({
        sql: 'START TRANSACTION',
        transaction: TRANSACTION.start,
        debounce: false
      }, connection).then(() => connection);
    });
  }
  /**
   * commit transaction
   * @param {Object} connection
   */
  commit(connection) {
    return this.query({
      sql: 'COMMIT',
      transaction: TRANSACTION.end,
      debounce: false
    }, connection);
  }
  /**
   * rollback transaction
   * @param {Object} connection
   */
  rollback(connection) {
    return this.query({
      sql: 'ROLLBACK',
      transaction: TRANSACTION.end,
      debounce: false
    }, connection);
  }
  /**
   * transaction
   * @param {Function} fn
   * @param {Object} connection
   */
  transaction(fn, connection) {
    assert(helper.isFunction(fn), 'fn must be a function');
    return this.getConnection(connection).then(connection => {
      return this.startTrans(connection).then(() => {
        return fn(connection);
      }).then(data => {
        return this.commit(connection).then(() => data);
      }).catch(err => {
        return this.rollback(connection).then(() => Promise.reject(err));
      });
    });
  }
  /**
   * query data
   */
  [QUERY](sqlOptions, connection, startTime, times = 0) {
    const queryFn = helper.promisify(connection.query, connection);
    return queryFn(sqlOptions).catch(err => err).then(data => {
      this.releaseConnection(connection);

      // if server close connection, then retry it
      if (helper.isError(data) && data.code === 'PROTOCOL_CONNECTION_LOST') {
        connection[CONNECTION_LOST] = true;
        if (times < 3) {
          return this.getConnection().then(connection => {
            return this[QUERY](sqlOptions, connection, startTime, times + 1);
          });
        }
      }
      // log sql
      if (this.config.logSql) {
        const endTime = Date.now();
        this.config.logger(`SQL: ${sqlOptions.sql}, Time: ${endTime - startTime}ms`);
      }
      if (helper.isError(data)) return Promise.reject(data);
      return data;
    });
  }
  /**
   * release connection
   */
  releaseConnection(connection) {
    // if not in transaction, release connection
    if (connection.transaction !== TRANSACTION.start) {
      // connection maybe already released, it will throw an Error
      try {
        connection.release();
      } catch (e) {}
    }
  }
  /**
   * query({
   *  sql: 'SELECT * FROM `books` WHERE `author` = ?',
   *  timeout: 40000, // 40s
   *  values: ['David']
   * })
   * @param {Object} sqlOptions
   * @param {Object} connection
   */
  query(sqlOptions, connection) {
    if (helper.isString(sqlOptions)) {
      sqlOptions = {sql: sqlOptions};
    }
    if (this.config.debounce !== undefined) {
      sqlOptions.debounce = this.config.debounce;
    }
    if (sqlOptions.debounce === undefined) {
      sqlOptions.debounce = true;
    }
    const startTime = Date.now();
    if (sqlOptions.debounce) {
      const key = JSON.stringify(sqlOptions);
      return debounceInstance.debounce(key, () => {
        return this.getConnection(connection).then(connection => {
          return this[QUERY](sqlOptions, connection, startTime);
        });
      });
    }
    return this.getConnection(connection).then(connection => {
      // set transaction status to connection
      if (sqlOptions.transaction) {
        if (sqlOptions.transaction === TRANSACTION.start) {
          if (connection.transaction === TRANSACTION.start) return;
        } else if (sqlOptions.transaction === TRANSACTION.end) {
          if (connection.transaction !== TRANSACTION.start) {
            this.releaseConnection(connection);
            return;
          }
        }
        connection.transaction = sqlOptions.transaction;
      }
      return this[QUERY](sqlOptions, connection, startTime);
    });
  }

  /**
   * execute
   * @param  {Array} args []
   * @returns {Promise}
   */
  execute(sqlOptions, connection) {
    if (helper.isString(sqlOptions)) {
      sqlOptions = {sql: sqlOptions};
    }
    sqlOptions.debounce = false;
    return this.query(sqlOptions, connection);
  }
  /**
   * close
   * @returns {Promise}
   */
  close() {
    return helper.promisify(this.pool.end, this.pool)();
  }
}
module.exports = thinkInstance(ThinkMysql);
<|endoftext|>


<commit_before>var postcss = require('postcss');
var find = require('array-find');
var referenceableNodes = [];

module.exports = postcss.plugin('postcss-reference', function (opts) {
    opts = opts || {
        debug: false
    };

    // Work with options here

    var referenceRules = [];
    var findReferenceableRules = function findReferenceableRules(css) {

        // Walk through list of rules in @reference blocks, push them into the
        // referenceRules array and then remove them from the AST so they don't
        // get output to the compiled CSS unless matched.
        css.walkAtRules('reference', function(atRule) {

            atRule.walkRules(function(rule) {
                referenceRules.push(rule);
            });

            atRule.remove();
        });
    };

    var findReferences = function findReferences(css) {

        // Now that our @reference blocks have been processed
        // Walk through our rules looking for @references declarations
        css.walkRules(function(rule) {

            // TODO :: if rule's selector has a pseudoclass, prepend matches to
            // the rule

            rule.walk(function(node) {

                if (node.type === 'atrule' &&
                    node.name === 'references') {
                    // check if the referenced node's params match any rules in
                    // our references array
                    var terms = node.params.replace('(', '').replace(')', '').split(',');
                    var matches;

                    // check for matches
                    matches = matchReferences(referenceRules, terms);

                    // if referenced and referencing rules have declarations
                    // with of same property, defer to the referencing rule
                    rule.walkDecls(function(decl) {
                        matches.decls.forEach(function(match, d, matchedDecls) {
                            if (decl.prop === match.prop) {
                                matchedDecls.splice(d, 1);
                            }
                        });
                    });

                    for (var m in matches.decls) {
                        rule.insertBefore(node, matches.decls[m]);
                    }

                    if (matches.relationships.length) {
                        // sort relationships alphabetically in descending order so
                        // they will properly append after the original rule
                        matches.relationships.sort(function (a, b) {

                            if (a < b) {
                                return 1;
                            }
                            if (a > b) {
                                return -1;
                            }
                            // a must be equal to b
                            return 0;
                        });

                        matches.relationships.forEach(function(newRule) {
                            css.insertAfter(rule, newRule);
                        });
                    }

                    node.remove();
                }
            });
        });
    };

    var findDuplicates = function findDuplicates(matchArray, node, childParam) {
        var dup = null;

        find(matchArray, function(match, index, array) {
            if (match[childParam] === node[childParam]) {
                dup = index;
            }
        });

        return dup;
    };

    var extractMatchingDecls = function extractMatchingDecls(matchArray, rule) {

        rule.walkDecls(function(decl) {
            var dup = null;

            // check for duplicates in our list of matches
            dup = findDuplicates(matchArray, decl, "prop");

            if (dup !== null) {
                // if it's a dupe, replace existing rule
                matchArray[dup].replaceWith(decl);
            } else {
                // otherwise add to the declarations list
                matchArray.push(decl);
            }
        });
    };

    var extractMatchingRelationships = function extractMatchingRelationships(matchArray, rule) {
        var dup = null;

        dup = findDuplicates(matchArray, rule, "selector");

        if (dup !== null) {
            // TODO :: instead of replacing the rule outright, create mergeDuplicates
            // function to to compare rules with matching selector names and run
            // findDuplicates on their declarations
            matchArray[dup].replaceWith(rule);
        } else {
            // otherwise add to the declarations list
            matchArray.push(rule);
        }
    };

    var matchReferences = function matchReferences(referenceRules, terms) {
        var matches = {
            decls: [],
            relationships: []
        };

        referenceRules.forEach(function (rule, index, rules) {
            // make sure any comma separated selector lists are broken out
            var selectors = rule.selectors;

            selectors.forEach(function(selector, sindex, selectors) {

                terms.forEach(function(term, tindex, terms) {
                    var flagAll = (term.indexOf(" all") !== -1);
                    var tmpSelectorsArray = selectors;
                    // strip " all" from term now that flag is set
                    term = term.replace(" all", '');

                    // clean any whitespaces which might surround commas
                    term = term.trim();

                    // strip out selectors from list that don't start with current term
                    if (tmpSelectorsArray.length > 1 && tmpSelectorsArray.indexOf(term) !== -1) {
                        for (var i = 0; i < tmpSelectorsArray.length; i++){
                            var cleanSelector = tmpSelectorsArray[i].trim();
                        	if (cleanSelector.indexOf(term) !== 0) {
                                tmpSelectorsArray.splice(i, 1);
                            }
                        }

                        // then if we had to splice any selectors out of our
                        // rule's selector list, reassemble the array back to a string
                        selector = tmpSelectorsArray.join();
                    }

                    if (term === selector) {
                        extractMatchingDecls(matches.decls, rule);
                    } else if (rule.selector.indexOf(term) === 0 && flagAll) {
                        console.log(rule.selector + " is related to " + term);
                        // if the it's not an explicit match, but the 'all' flag is set
                        // and the selector describes a relationships to the term, gather
                        // those rules for our matches array
                        // i.e. prevent matches with .button like .button-primary, but allow
                        // matches like .button .primary, .button.primary, or .button > .primary
                        var safeChars = [" ", ".", "#", "+", "~", ">", ":"];

                        if (rule.selector.length > term.length &&
                            rule.selector.charAt(term.length + 1).indexOf(safeChars)) {
                                extractMatchingRelationships(matches.relationships, rule);
                        }
                    }
                });
            });
        });

        return matches;
    };

    return function (css, result) {
        findReferenceableRules(css);
        findReferences(css);
        console.log(css);
    };
});
<commit_msg>Initial pass at merge rules process in extractMatchingRelationships()<commit_after>var postcss = require('postcss');
var find = require('array-find');
var referenceableNodes = [];

module.exports = postcss.plugin('postcss-reference', function (opts) {
    opts = opts || {
        debug: false
    };

    // Work with options here

    var referenceRules = [];
    var findReferenceableRules = function findReferenceableRules(css) {

        // Walk through list of rules in @reference blocks, push them into the
        // referenceRules array and then remove them from the AST so they don't
        // get output to the compiled CSS unless matched.
        css.walkAtRules('reference', function(atRule) {

            atRule.walkRules(function(rule) {
                referenceRules.push(rule);
            });

            atRule.remove();
        });
    };

    var findReferences = function findReferences(css) {

        // Now that our @reference blocks have been processed
        // Walk through our rules looking for @references declarations
        css.walkRules(function(rule) {

            // TODO :: if rule's selector has a pseudoclass, prepend matches to
            // the rule

            rule.walk(function(node) {

                if (node.type === 'atrule' &&
                    node.name === 'references') {
                    // check if the referenced node's params match any rules in
                    // our references array
                    var terms = node.params.replace('(', '').replace(')', '').split(',');
                    var matches;

                    // check for matches
                    matches = matchReferences(referenceRules, terms);

                    // if referenced and referencing rules have declarations
                    // with of same property, defer to the referencing rule
                    rule.walkDecls(function(decl) {
                        matches.decls.forEach(function(match, d, matchedDecls) {
                            if (decl.prop === match.prop) {
                                matchedDecls.splice(d, 1);
                            }
                        });
                    });

                    for (var m in matches.decls) {
                        rule.insertBefore(node, matches.decls[m]);
                    }

                    if (matches.relationships.length) {
                        // sort relationships alphabetically in descending order so
                        // they will properly append after the original rule
                        matches.relationships.sort(function (a, b) {

                            if (a < b) {
                                return 1;
                            }
                            if (a > b) {
                                return -1;
                            }
                            // a must be equal to b
                            return 0;
                        });

                        matches.relationships.forEach(function(newRule) {
                            css.insertAfter(rule, newRule);
                        });
                    }

                    node.remove();
                }
            });
        });
    };

    var matchReferences = function matchReferences(referenceRules, terms) {
        var matches = {
            decls: [],
            relationships: []
        };

        referenceRules.forEach(function (reference, index, references) {
            // make sure any comma separated selector lists are broken out
            var refSelectors = reference.selectors;

            refSelectors.forEach(function(refSelector, sindex, refSelectors) {

                terms.forEach(function(term, tindex, terms) {
                    var flagAll = (term.indexOf(" all") !== -1);
                    var tmpSelectorsArray = refSelectors;
                    // strip " all" from term now that flag is set
                    term = term.replace(" all", '');

                    // clean any whitespaces which might surround commas
                    term = term.trim();

                    // strip out selectors from list that don't start with current term
                    if (tmpSelectorsArray.length > 1 && tmpSelectorsArray.indexOf(term) !== -1) {
                        for (var i = 0; i < tmpSelectorsArray.length; i++){
                            var cleanSelector = tmpSelectorsArray[i].trim();
                        	if (cleanSelector.indexOf(term) !== 0) {
                                tmpSelectorsArray.splice(i, 1);
                            }
                        }

                        // then if we had to splice any selectors out of our
                        // reference's selector list, reassemble the array back to a string
                        refSelector = tmpSelectorsArray.join();
                    }

                    if (term === refSelector) {
                        extractMatchingDecls(matches.decls, reference);
                    } else if (reference.selector.indexOf(term) === 0 && flagAll) {
                        console.log(reference.selector + " is related to " + term);
                        // if the it's not an explicit match, but the 'all' flag is set
                        // and the selector describes a relationships to the term, gather
                        // those references for our matches array
                        // i.e. prevent matches with .button like .button-primary, but allow
                        // matches like .button .primary, .button.primary, or .button > .primary
                        var safeChars = [" ", ".", "#", "+", "~", ">", ":"];

                        if (reference.selector.length > term.length &&
                            reference.selector.charAt(term.length + 1).indexOf(safeChars)) {
                                extractMatchingRelationships(matches.relationships, reference);
                        }
                    }
                });
            });
        });

        return matches;
    };

    var findDuplicates = function findDuplicates(matchArray, node, childParam) {
        var dup = null;

        find(matchArray, function(match, index, array) {
            if (match[childParam] === node[childParam]) {
                dup = index;
            }
        });

        return dup;
    };

    var extractMatchingDecls = function extractMatchingDecls(matchArray, rule) {

        rule.walkDecls(function(decl) {
            var dup = null;

            // check for duplicates in our list of matches
            dup = findDuplicates(matchArray, decl, "prop");

            if (dup !== null) {
                // if it's a dupe, replace existing rule
                matchArray[dup].replaceWith(decl);
            } else {
                // otherwise add to the declarations list
                matchArray.push(decl);
            }
        });
    };

    var extractMatchingRelationships = function extractMatchingRelationships(matchArray, rule) {

        matchArray.forEach(function(match) {
            var dup = null;

            dup = findDuplicates(matchArray, rule, "selector");

            if (dup !== null) {
                // TODO :: instead of replacing the rule outright, create mergeDuplicates
                // function to to compare rules with matching selector names and run
                // findDuplicates on their declarations
                // matchArray[dup].replaceWith(rule);

                // walk through each decl in rule and discard all matching decls
                // from dup before merging remaining decls
                rule.walkDecls(function(decl) {
                    var dupDecl = findDuplicates(matchArray[dup].nodes, rule.nodes[decl], "prop");

                    matchArray[dup].nodes[dupDecl].remove();
                });
            } else {
                // otherwise add to the declarations list
                matchArray.push(rule);
            }
        });
    };

    return function (css, result) {
        findReferenceableRules(css);
        findReferences(css);
        console.log(css);
    };
});
<|endoftext|>


<commit_before>var bpack = require('browser-pack');
var bresolve = require('browser-resolve');
var clone = require('clone');
var crypto = require('crypto');
var detective = require('detective');
var flatten = require('flatten');
var fs = require('fs');
var glob = require('glob');
var minimatch = require('minimatch');
var path = require('path');
var Q = require('q');
var sliced = require('sliced');
var through2 = require('through2');

// property on module objects that stores deps in the current bundle only,
// i.e. excluding external deps -- used when piping things to bpack
var BUNDLED_DEPS = Symbol();

// property on module objects linking to promises for resolving dep ids
var RESOLVING_REQUIRES = Symbol();

// caches transformed module source code along with mtime
var SOURCE_CACHE = new Map();

function moduleHash(filename, source) {
    var sha1 = crypto.createHash('sha1');
    sha1.update(JSON.stringify([filename, source]));
    return sha1.digest('hex').substring(0, 7);
}

function setSource(module, source) {
    module.source = source;
    module.id = moduleHash(module.sourceFile, source);
}

function strcmp(a, b) {
    return a.localeCompare(b);
}

function Bundle(files, options) {
    this._options = clone(options || {}, true, 1);

    // browserify-compatible source transforms
    this._transforms = [];

    // files the execute when the bundle is loaded
    this._entries = flatten([].concat(files).map(function (pattern) {
        return glob.sync(pattern).map(function (p) {return path.resolve(p)});
    }));

    // other exports included in the bundle
    this._requires = [];

    // external bundles whose modules will be excluded from our own
    this._externals = [];

    // custom dependency names
    this._exposed = new Map();

    // caches the filename -> module mapping
    this._bundledFileMapping = null;

    // holds a promise to _bundledFileMapping
    this._deferredFileMapping = null;

    // caches pending module promises
    this._loadingFiles = new Map();

    // controls whether bpack prelude includes require= prefix
    this._hasExports = false;

    // filename -> module cache
    this._moduleCache = new Map();
}

Bundle.prototype.transform = function () {
    this._transforms.push(sliced(arguments));
    return this;
};

Bundle.prototype.require = function (file, options) {
    file = path.resolve(file);

    this._requires.push(file);

    if (options && options.expose) {
        this._exposed.set(options.expose, file);
    }

    return this;
};

Bundle.prototype.external = function (bundle) {
    this._externals.push(bundle);
    bundle._hasExports = true;
    return this;
};

Bundle.prototype.bundle = function () {
    var pack = bpack({raw: true, hasExports: this._hasExports});

    // Source code may have changed
    this._bundledFileMapping = null;
    this._deferredFileMapping = null;

    this._getBundledModules(
        function (mapping) {
            var sorted = [];

            for (var filename of mapping.keys()) {
                sorted.push(filename);
            }

            sorted.sort().forEach(function (filename) {
                pack.write(mapping.get(filename));
            });

            pack.end();
        },
        function (error) {
            throw error;
        }
    );

    return pack;
};

Bundle.prototype._getBundledModules = function (success, failure) {
    if (this._bundledFileMapping) {
        success(this._bundledFileMapping);
        return;
    }

    if (this._deferredFileMapping) {
        this._deferredFileMapping.then(success, failure);
        return;
    }

    var self = this;
    var mapping = new Map();
    var deferred = Q.defer();
    this._deferredFileMapping = deferred.promise;

    var addSelfAndDeps = function (m) {
        mapping.set(m.sourceFile, m);
        addModules(m[BUNDLED_DEPS]);
    };

    var addModules = function (modules) {
        modules.forEach(addSelfAndDeps);
    };

    Q.all(this._entries.concat(this._requires).map(this._add, this)).then(
        function (modules) {
            addModules(modules);
            self._bundledFileMapping = mapping;
            success(mapping);
            deferred.resolve(mapping);
        },
        function (error) {
            failure(error);
            deferred.reject(error);
        }
    );
};

Bundle.prototype._add = function (filename) {
    if (this._loadingFiles.has(filename)) {
        return this._loadingFiles.get(filename);
    }

    var self = this;
    var deferred = Q.defer();

    Q.nfcall(fs.stat, filename).then(
        function (stats) {
            if (stats.isFile()) {
                var cached = SOURCE_CACHE.get(filename);
                var module = self._findOrCreateModule(filename);

                if (!cached || cached.mtime < stats.mtime) {
                    var deferredSource = Q.defer();

                    SOURCE_CACHE.set(filename, {
                        mtime: stats.mtime,
                        source: deferredSource.promise
                    });

                    self._loadModule(module).then(
                        function () {
                            deferredSource.resolve(module.source);
                            deferred.resolve(module);
                            self._loadingFiles.delete(filename);
                        },
                        function (error) {
                            deferred.reject(error);
                            self._loadingFiles.delete(filename);
                        }
                    );
                } else {
                    // mtime hasn't changed, return cached entry
                    cached.source.done(function (source) {
                        setSource(module, source);
                        deferred.resolve(module);
                    });
                }
            } else {
                deferred.reject();
            }
        },
        deferred.reject
    );

    this._loadingFiles.set(filename, deferred.promise);
    return deferred.promise;
};

Bundle.prototype._loadModule = function (module) {
    var self = this;
    var source = '';
    var deferred = Q.defer();
    var stream = fs.createReadStream(module.sourceFile);

    // clear previous load results
    module.deps = {};
    module[BUNDLED_DEPS] = [];

    if (self._transforms) {
        self._transforms.forEach(function (args) {
            var transform = args[0];

            if (typeof transform === 'string') {
                transform = require(transform);
            }

            stream = stream.pipe(
                transform.apply(null, [module.sourceFile].concat(args.slice(1)))
            );
        });
    }

    stream
        .on('error', deferred.reject)
        .on('end', function () {
            setSource(module, source);

            self._resolveRequires(module).then(
                function () {deferred.resolve(module)},
                deferred.reject
            );
        })
        .pipe(through2(function (chunk, enc, cb) {
            source += chunk.toString();
            cb();
        }));

    return deferred.promise;
};

Bundle.prototype._resolveRequires = function (module) {
    // Recursively resolve all require()'d modules in the source
    var self = this;

    return Q.all(detective(module.source).map(function (id) {
        return self._resolveRequire(module, id);
    }));
};

Bundle.prototype._resolveRequire = function (module, id) {
    var resolving = module[RESOLVING_REQUIRES];

    if (resolving.has(id)) {
        return resolving.get(id);
    }

    var self = this;
    var deferred = Q.defer();
    resolving.set(id, deferred.promise);

    var resolved = function (dep) {
        module.deps[id] = dep.id;
        deferred.resolve();
        resolving.delete(id);
    };

    // check if the id is exposed by an external bundle
    this._findExternal(function (b, mapping) {
        var filename = b._exposed.get(id);
        if (filename) {
            return mapping.get(filename);
        }
    }).then(
        resolved,
        function () {
            bresolve(id, {filename: module.sourceFile}, function (error, depFilename) {
                if (error) {
                    deferred.reject(error);
                    return;
                }

                // check if the file exists in an external bundle
                self._findExternal(function (b, mapping) {
                    return mapping.get(depFilename);
                }).then(
                    resolved,
                    function () {
                        // id is not a path; make sure we expose the actual resolved path,
                        // so dependent bundles can require the same identifer from us.
                        if (!/^(\.\/|\/|\.\.\/)/.test(id) && !self._exposed.has(id)) {
                            self._exposed.set(id, depFilename);
                        }

                        // wasn't found in any external bundle, add it to ours
                        self._add(depFilename).done(function (dep) {
                            module[BUNDLED_DEPS].push(dep);
                            resolved(dep);
                        });
                    }
                );
            });
        }
    );

    return deferred.promise;
};

Bundle.prototype._findExternal = function (callback) {
    var self = this;
    var externals = this._externals;
    var deferred = Q.defer();

    if (externals && externals.length) {
        Q.any(externals.map(function (b) {
            var deferredMatch = Q.defer();

            b._getBundledModules(
                function (mapping) {
                    var module = callback(b, mapping);
                    if (module) {
                        deferredMatch.resolve(module);
                    } else {
                        deferredMatch.reject();
                    }
                },
                function (error) {
                    throw error;
                }
            );

            return deferredMatch.promise;
        })).then(deferred.resolve, deferred.reject);
    } else {
        deferred.reject();
    }

    return deferred.promise;
};

Bundle.prototype._findOrCreateModule = function (filename) {
    var module = this._moduleCache.get(filename);

    if (module) {
        return module;
    }

    module = {
        deps: {},
        sourceFile: filename,
        entry: this._entries.some(function (e) {return minimatch(filename, e)}),
        nomap: !this._options.debug
    };

    Object.defineProperty(module, BUNDLED_DEPS, {
        value: [],
        configurable: false,
        enumerable: false,
        writable: true
    });

    Object.defineProperty(module, RESOLVING_REQUIRES, {
        value: new Map(),
        configurable: false,
        enumerable: false,
        writable: false
    });

    this._moduleCache.set(filename, module);
    return module;
};

module.exports = function (options) {
    return new Bundle(options);
};
<commit_msg>Use finally() where appropriate<commit_after>var bpack = require('browser-pack');
var bresolve = require('browser-resolve');
var clone = require('clone');
var crypto = require('crypto');
var detective = require('detective');
var flatten = require('flatten');
var fs = require('fs');
var glob = require('glob');
var minimatch = require('minimatch');
var path = require('path');
var Q = require('q');
var sliced = require('sliced');
var through2 = require('through2');

// property on module objects that stores deps in the current bundle only,
// i.e. excluding external deps -- used when piping things to bpack
var BUNDLED_DEPS = Symbol();

// property on module objects linking to promises for resolving dep ids
var RESOLVING_REQUIRES = Symbol();

// caches transformed module source code along with mtime
var SOURCE_CACHE = new Map();

function moduleHash(filename, source) {
    var sha1 = crypto.createHash('sha1');
    sha1.update(JSON.stringify([filename, source]));
    return sha1.digest('hex').substring(0, 7);
}

function setSource(module, source) {
    module.source = source;
    module.id = moduleHash(module.sourceFile, source);
}

function strcmp(a, b) {
    return a.localeCompare(b);
}

function Bundle(files, options) {
    this._options = clone(options || {}, true, 1);

    // browserify-compatible source transforms
    this._transforms = [];

    // files the execute when the bundle is loaded
    this._entries = flatten([].concat(files).map(function (pattern) {
        return glob.sync(pattern).map(function (p) {return path.resolve(p)});
    }));

    // other exports included in the bundle
    this._requires = [];

    // external bundles whose modules will be excluded from our own
    this._externals = [];

    // custom dependency names
    this._exposed = new Map();

    // caches the filename -> module mapping
    this._bundledFileMapping = null;

    // holds a promise to _bundledFileMapping
    this._deferredFileMapping = null;

    // caches pending module promises
    this._loadingFiles = new Map();

    // controls whether bpack prelude includes require= prefix
    this._hasExports = false;

    // filename -> module cache
    this._moduleCache = new Map();
}

Bundle.prototype.transform = function () {
    this._transforms.push(sliced(arguments));
    return this;
};

Bundle.prototype.require = function (file, options) {
    file = path.resolve(file);

    this._requires.push(file);

    if (options && options.expose) {
        this._exposed.set(options.expose, file);
    }

    return this;
};

Bundle.prototype.external = function (bundle) {
    this._externals.push(bundle);
    bundle._hasExports = true;
    return this;
};

Bundle.prototype.bundle = function () {
    var pack = bpack({raw: true, hasExports: this._hasExports});

    // Source code may have changed
    this._bundledFileMapping = null;
    this._deferredFileMapping = null;

    this._getBundledModules(
        function (mapping) {
            var sorted = [];

            for (var filename of mapping.keys()) {
                sorted.push(filename);
            }

            sorted.sort().forEach(function (filename) {
                pack.write(mapping.get(filename));
            });

            pack.end();
        },
        function (error) {
            throw error;
        }
    );

    return pack;
};

Bundle.prototype._getBundledModules = function (success, failure) {
    if (this._bundledFileMapping) {
        success(this._bundledFileMapping);
        return;
    }

    if (this._deferredFileMapping) {
        this._deferredFileMapping.then(success, failure);
        return;
    }

    var self = this;
    var mapping = new Map();
    var deferred = Q.defer();
    this._deferredFileMapping = deferred.promise;

    var addSelfAndDeps = function (m) {
        mapping.set(m.sourceFile, m);
        addModules(m[BUNDLED_DEPS]);
    };

    var addModules = function (modules) {
        modules.forEach(addSelfAndDeps);
    };

    Q.all(this._entries.concat(this._requires).map(this._add, this)).then(
        function (modules) {
            addModules(modules);
            self._bundledFileMapping = mapping;
            success(mapping);
            deferred.resolve(mapping);
        },
        function (error) {
            failure(error);
            deferred.reject(error);
        }
    );
};

Bundle.prototype._add = function (filename) {
    if (this._loadingFiles.has(filename)) {
        return this._loadingFiles.get(filename);
    }

    var self = this;
    var deferred = Q.defer();

    Q.nfcall(fs.stat, filename).then(
        function (stats) {
            if (stats.isFile()) {
                var cached = SOURCE_CACHE.get(filename);
                var module = self._findOrCreateModule(filename);

                if (!cached || cached.mtime < stats.mtime) {
                    var deferredSource = Q.defer();

                    SOURCE_CACHE.set(filename, {
                        mtime: stats.mtime,
                        source: deferredSource.promise
                    });

                    var loading = self._loadModule(module);

                    loading.then(
                        function () {
                            deferredSource.resolve(module.source);
                            deferred.resolve(module);
                        },
                        function (error) {
                            deferred.reject(error);
                        }
                    );

                    loading.finally(function () {
                        self._loadingFiles.delete(filename);
                    });
                } else {
                    // mtime hasn't changed, return cached entry
                    cached.source.done(function (source) {
                        setSource(module, source);
                        deferred.resolve(module);
                    });
                }
            } else {
                deferred.reject();
            }
        },
        deferred.reject
    );

    this._loadingFiles.set(filename, deferred.promise);
    return deferred.promise;
};

Bundle.prototype._loadModule = function (module) {
    var self = this;
    var source = '';
    var deferred = Q.defer();
    var stream = fs.createReadStream(module.sourceFile);

    // clear previous load results
    module.deps = {};
    module[BUNDLED_DEPS] = [];

    if (self._transforms) {
        self._transforms.forEach(function (args) {
            var transform = args[0];

            if (typeof transform === 'string') {
                transform = require(transform);
            }

            stream = stream.pipe(
                transform.apply(null, [module.sourceFile].concat(args.slice(1)))
            );
        });
    }

    stream
        .on('error', deferred.reject)
        .on('end', function () {
            setSource(module, source);

            self._resolveRequires(module).then(
                function () {deferred.resolve(module)},
                deferred.reject
            );
        })
        .pipe(through2(function (chunk, enc, cb) {
            source += chunk.toString();
            cb();
        }));

    return deferred.promise;
};

Bundle.prototype._resolveRequires = function (module) {
    // Recursively resolve all require()'d modules in the source
    var self = this;

    return Q.all(detective(module.source).map(function (id) {
        return self._resolveRequire(module, id);
    }));
};

Bundle.prototype._resolveRequire = function (module, id) {
    var resolving = module[RESOLVING_REQUIRES];

    if (resolving.has(id)) {
        return resolving.get(id);
    }

    var self = this;
    var deferred = Q.defer();
    resolving.set(id, deferred.promise);

    var resolved = function (dep) {
        module.deps[id] = dep.id;
        deferred.resolve();
    };

    deferred.promise.finally(function () {
        resolving.delete(id);
    });

    // check if the id is exposed by an external bundle
    this._findExternal(function (b, mapping) {
        var filename = b._exposed.get(id);
        if (filename) {
            return mapping.get(filename);
        }
    }).then(
        resolved,
        function () {
            bresolve(id, {filename: module.sourceFile}, function (error, depFilename) {
                if (error) {
                    deferred.reject(error);
                    return;
                }

                // check if the file exists in an external bundle
                self._findExternal(function (b, mapping) {
                    return mapping.get(depFilename);
                }).then(
                    resolved,
                    function () {
                        // id is not a path; make sure we expose the actual resolved path,
                        // so dependent bundles can require the same identifer from us.
                        if (!/^(\.\/|\/|\.\.\/)/.test(id) && !self._exposed.has(id)) {
                            self._exposed.set(id, depFilename);
                        }

                        // wasn't found in any external bundle, add it to ours
                        self._add(depFilename).done(function (dep) {
                            module[BUNDLED_DEPS].push(dep);
                            resolved(dep);
                        });
                    }
                );
            });
        }
    );

    return deferred.promise;
};

Bundle.prototype._findExternal = function (callback) {
    var self = this;
    var externals = this._externals;
    var deferred = Q.defer();

    if (externals && externals.length) {
        Q.any(externals.map(function (b) {
            var deferredMatch = Q.defer();

            b._getBundledModules(
                function (mapping) {
                    var module = callback(b, mapping);
                    if (module) {
                        deferredMatch.resolve(module);
                    } else {
                        deferredMatch.reject();
                    }
                },
                function (error) {
                    throw error;
                }
            );

            return deferredMatch.promise;
        })).then(deferred.resolve, deferred.reject);
    } else {
        deferred.reject();
    }

    return deferred.promise;
};

Bundle.prototype._findOrCreateModule = function (filename) {
    var module = this._moduleCache.get(filename);

    if (module) {
        return module;
    }

    module = {
        deps: {},
        sourceFile: filename,
        entry: this._entries.some(function (e) {return minimatch(filename, e)}),
        nomap: !this._options.debug
    };

    Object.defineProperty(module, BUNDLED_DEPS, {
        value: [],
        configurable: false,
        enumerable: false,
        writable: true
    });

    Object.defineProperty(module, RESOLVING_REQUIRES, {
        value: new Map(),
        configurable: false,
        enumerable: false,
        writable: false
    });

    this._moduleCache.set(filename, module);
    return module;
};

module.exports = function (options) {
    return new Bundle(options);
};
<|endoftext|>


<commit_before>"use strict";

// Async helper.  Like async.parallel.  Calls done on first non null error
// or when all tasks completed.
function parallel(tasks, done){
    var remaining = tasks.length,
        resolved = false;

    tasks.map(function(task){
        task(function(err, res){
            if(!resolved){
                if(err){
                    resolved = true;
                    return done(err);
                }
                remaining--;
                if(remaining === 0){
                    done();
                }
            }
        });
    });
}

// XHR fetch asset.
function fetch(url, done){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onload = function(e) {
        if (e.target.status !== 200) {
            return done(new Error('wtf?: ' + e.target.status));
        }
        return done(null, e);
    };
    xhr.onerror = function(){
        done(new Error('XHR error'));
    };
    xhr.send();
}

//Map of assetType to function to drop it on the DOM.
var append = {
    'js': function(src){
        var el = document.createElement('script');
        el.type = 'text/javascript';
        if(src.indexOf('file://') === 0){
            el.src = src;
        }
        else {
            el.innerHTML = src; // Blob
        }
        document.head.appendChild(el);
        return el;
    },
    'css': function(src){
        var el;
        if(src.indexOf('file://') === 0){
            el = document.createElement('link');
            el.rel = 'stylesheet';
            el.type = 'text/css';
            el.href = src;
        }
        else {
            el = document.createElement('style');
            el.type = 'text/css';
            el.innerHTML = src; // Blob
        }
        document.head.appendChild(el);
        return el;
    }
};

// Loader instance handles everything for you.
//
// @param {String} url The base url for assets
// @param {Array} assets just a list of assets to manage, ie /app.css, /app.js, etc.
// @param {String} bootstrapPath optionally where bootstrap data can be found
function Loader(url, assets, bootstrapPath){
    this.url = url;
    this.assets = assets;
    this.bootstrapPath = (bootstrapPath ? url + bootstrapPath : url + '/sterno-bootstrap.json');
    this.versions = {};
    this.appVersion = localStorage.getItem('appVersion');

    // Hashes of files we've already seen.
    // Gets lazy loaded from localstorage
    this.localVersions = {};

    this.incomingVersion = {};
    this.version = {};
    if(this.appVersion){
        this.version = this.parseVersion(this.appVersion);
    }
}

// Use data from the bootstrap to check if a file is different from our
// local copy.
//
// @param {String} src Source of the file, ie /app.js
Loader.prototype.hasChanged = function(src){
    if(!this.localVersions[src]){
        this.localVersions[src] = localStorage.getItem('versions_' + src);
    }

    return (!this.localVersions[src]) ? true : (this.versions[src] !== this.localVersions[src]);
};

// If the file had a version in the bootstrap JSON, use it to point at the
// file we actually want.
//
// @param {String} src Source of the file, ie /app.js
Loader.prototype.getVersionedUrl = function(src){
    var version = this.versions[src],
        url = this.url + src;

    if(!process.env || process.env.environment === 'development'){
        url += '?bust' + Math.random();
    }
    else if(version){
        url  += '/' + version;
    }

    return url;
};

// Use our decision tree to check if we should actually pull down a nwe version
// of an asset.  Check that we're online, the file has changed and the version
// in the bootstrap is actually one we can use.
//
// @param {String} src Source of the file, ie /app.js
Loader.prototype.shouldUpgradeAsset = function(src){
    if(!navigator.onLine || !this.hasChanged(src)){
        return false;
    }

    return (this.incomingVersion.major === this.version.major && this.incomingVersion.minor === this.version.minor);
};

// Just blow up a version string into a map of major, minor and patch.
//
// @param {String} v any version string, ie 0.1.1
Loader.prototype.parseVersion = function(v){
    var matches = /(\d+)\.(\d+)\.(\d+)/.exec(v);
    return {
        'major': matches[1],
        'minor': matches[2],
        'patch': matches[3]
    };
};

// Insert all of our assets into the DOM.
// Takes care of managing all the versions and fall backs.
//
// @param {Function} done
Loader.prototype.insert = function(done){
    var self = this;
    parallel(this.assets.map(function(src){
        var assetType = /.*\.(js|css)/.exec(src)[1];
        return function(callback){
            // Can we use this version and are we online?
            if(self.shouldUpgradeAsset(src)){
                // Yes -> download, add to dom, next tick save to disk
                fetch(self.getVersionedUrl(src), function(err, event){
                    if(err){
                        return callback(err);
                    }

                    append[assetType](event.target.response);

                    self.write(src, event.target.response, function(err){
                        if(err){
                            return callback(err);
                        }
                        localStorage.setItem('versions_' + src,  self.versions[src]);
                        callback();
                    });
                });
            }
            else {
                // No -> check FS. not in fs, set to local URI
                self.read(src, function(err, blob){
                    if(err){ // Doesn't exist or no FS.
                        return append[assetType]('file://' + src);
                    }
                    append[assetType](blob);
                });
            }
        };
    }), function(err){
        done(err);
    });
};

// Cleaner API for reading from the local fs and handing back contents.
//
// @param {String} src
// @param {Function} done
Loader.prototype.read = function(src, done){
    if(!this.fs){
        return done(new Error('Filesystem not available?'));
    }

    this.fs.root.getFile(src, {}, function(entry){
        entry.file(function(file){
            var reader;
            if(window.cordova){
                var FileReader = cordova.require('cordova/plugin/FileReader');
                reader = new FileReader();
            }
            else {
                reader = window.FileReader();
            }
            reader.onloadend = function(e){
                done(null, e.target.result);
            };
            reader.readAsText(file);
        }, done);
    });
};

// Cleaner API for writing to local fs.
//
// @param {String} dest
// @param {String} blob
// @param {Function} done
Loader.prototype.write = function(dest, blob, done){
    var self = this;
    this.fs.root.getFile(dest, {'create': true, 'exclusive': false}, function(entry){
        entry.createWriter(function(writer){
            writer.onwriteend = function(e){
                done();
            };
            writer.write(blob);
        }, done);
    }, done);
};

// Kicks everything off.
// If we're in chrome, on window load, call device ready.
// If we're in cordova land, wait for device ready to actually fire.
//
// @param {Function} done
Loader.prototype.load = function(done){
    var self = this;
    window.addEventListener('load', function(){
        if(!window.cordova){
            return self.deviceReady(done);
        }
        document.addEventListener('deviceready', function(){
            self.deviceReady(done);
        });
    });
    return this;
};

// Called by window load or device ready depeneding on where we're running.
// Tries to get permissions for local filesystem and gets bootstrapping JSON
// data from the remote.  After it gets all that, does some version parsing to
// set up the data used to determine if an asset should be upgraded.
// When thats all good, calls `insert` to start loading our assets into the DOM.
Loader.prototype.deviceReady = function(done){
    done = done || function(){};
    var self = this;
    parallel([
        function getFS(callback){
            var requestFS = window.requestFileSystem || window.webkitRequestFileSystem;
            if(!requestFS){
                return callback();
            }
            var winFS = window.LocalFileSystem|| window;
            requestFS(winFS.PERSISTENT, 50 * 1024 * 1024, function(fs){
                self.fs = fs;
                callback();
            }, callback);
        },
        function getBootstrap(callback){
            fetch(self.bootstrapPath + '?' + Math.random(), function(err, event){
                if(err){
                    return callback(err);
                }
                self.versions = JSON.parse(event.target.response);
                // If we haven't bootstrapped yet, default to whats there now.
                if(!self.appVersion){
                    self.appVersion = self.versions.version;
                    self.version = self.parseVersion(self.appVersion);
                }
                self.incomingVersion = self.parseVersion(self.versions.version);
                callback();
            });
        }
    ], function(err){
        self.insert(done);
    });
};

// Usage:
// require('sterno')(process.env.url, ['/app.js']);
//
// on load / device ready:
//
// # get local file system for reading / writing file caches
// # load bootstrap json from remote
// # insert assets to the dom, and cache locally if need be
module.exports = function(url, assets, done){
    return new Loader(url, assets).load(done);
};

<commit_msg>removed 'setting versions to' logging<commit_after>"use strict";

// Async helper.  Like async.parallel.  Calls done on first non null error
// or when all tasks completed.
function parallel(tasks, done){
    var remaining = tasks.length,
        resolved = false;

    tasks.map(function(task){
        task(function(err, res){
            if(!resolved){
                if(err){
                    resolved = true;
                    return done(err);
                }
                remaining--;
                if(remaining === 0){
                    done();
                }
            }
        });
    });
}

// XHR fetch asset.
function fetch(url, done){
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onload = function(e) {
        if (e.target.status !== 200) {
            return done(new Error('wtf?: ' + e.target.status));
        }
        return done(null, e);
    };
    xhr.onerror = function(){
        done(new Error('XHR error'));
    };
    xhr.send();
}

//Map of assetType to function to drop it on the DOM.
var append = {
    'js': function(src){
        var el = document.createElement('script');
        el.type = 'text/javascript';
        if(src.indexOf('file://') === 0){
            el.src = src;
        }
        else {
            el.innerHTML = src; // Blob
        }
        document.head.appendChild(el);
        return el;
    },
    'css': function(src){
        var el;
        if(src.indexOf('file://') === 0){
            el = document.createElement('link');
            el.rel = 'stylesheet';
            el.type = 'text/css';
            el.href = src;
        }
        else {
            el = document.createElement('style');
            el.type = 'text/css';
            el.innerHTML = src; // Blob
        }
        document.head.appendChild(el);
        return el;
    }
};

// Loader instance handles everything for you.
//
// @param {String} url The base url for assets
// @param {Array} assets just a list of assets to manage, ie /app.css, /app.js, etc.
// @param {String} bootstrapPath optionally where bootstrap data can be found
function Loader(url, assets, bootstrapPath){
    this.url = url;
    this.assets = assets;
    this.bootstrapPath = (bootstrapPath ? url + bootstrapPath : url + '/sterno-bootstrap.json');
    this.versions = {};
    this.appVersion = localStorage.getItem('appVersion');

    // Hashes of files we've already seen.
    // Gets lazy loaded from localstorage
    this.localVersions = {};

    this.incomingVersion = {};
    this.version = {};
    if(this.appVersion){
        this.version = this.parseVersion(this.appVersion);
    }
}

// Use data from the bootstrap to check if a file is different from our
// local copy.
//
// @param {String} src Source of the file, ie /app.js
Loader.prototype.hasChanged = function(src){
    if(!this.localVersions[src]){
        this.localVersions[src] = localStorage.getItem('versions_' + src);
    }

    return (!this.localVersions[src]) ? true : (this.versions[src] !== this.localVersions[src]);
};

// If the file had a version in the bootstrap JSON, use it to point at the
// file we actually want.
//
// @param {String} src Source of the file, ie /app.js
Loader.prototype.getVersionedUrl = function(src){
    var version = this.versions[src],
        url = this.url + src;

    if(!process.env || process.env.environment === 'development'){
        url += '?bust' + Math.random();
    }
    else if(version){
        url  += '/' + version;
    }

    return url;
};

// Use our decision tree to check if we should actually pull down a nwe version
// of an asset.  Check that we're online, the file has changed and the version
// in the bootstrap is actually one we can use.
//
// @param {String} src Source of the file, ie /app.js
Loader.prototype.shouldUpgradeAsset = function(src){
    if(!navigator.onLine || !this.hasChanged(src)){
        return false;
    }

    return (this.incomingVersion.major === this.version.major && this.incomingVersion.minor === this.version.minor);
};

// Just blow up a version string into a map of major, minor and patch.
//
// @param {String} v any version string, ie 0.1.1
Loader.prototype.parseVersion = function(v){
    var matches = /(\d+)\.(\d+)\.(\d+)/.exec(v);
    return {
        'major': matches[1],
        'minor': matches[2],
        'patch': matches[3]
    };
};

// Insert all of our assets into the DOM.
// Takes care of managing all the versions and fall backs.
//
// @param {Function} done
Loader.prototype.insert = function(done){
    var self = this;
    parallel(this.assets.map(function(src){
        var assetType = /.*\.(js|css)/.exec(src)[1];
        return function(callback){
            // Can we use this version and are we online?
            if(self.shouldUpgradeAsset(src)){
                // Yes -> download, add to dom, next tick save to disk
                fetch(self.getVersionedUrl(src), function(err, event){
                    if(err){
                        return callback(err);
                    }

                    append[assetType](event.target.response);

                    self.write(src, event.target.response, function(err){
                        if(err){
                            return callback(err);
                        }
                        localStorage.setItem('versions_' + src,  self.versions[src]);
                        callback();
                    });
                });
            }
            else {
                // No -> check FS. not in fs, set to local URI
                self.read(src, function(err, blob){
                    if(err){ // Doesn't exist or no FS.
                        return append[assetType]('file://' + src);
                    }
                    append[assetType](blob);
                });
            }
        };
    }), function(err){
        done(err);
    });
};

// Cleaner API for reading from the local fs and handing back contents.
//
// @param {String} src
// @param {Function} done
Loader.prototype.read = function(src, done){
    if(!this.fs){
        return done(new Error('Filesystem not available?'));
    }

    this.fs.root.getFile(src, {}, function(entry){
        entry.file(function(file){
            var reader;
            if(window.cordova){
                var FileReader = cordova.require('cordova/plugin/FileReader');
                reader = new FileReader();
            }
            else {
                reader = window.FileReader();
            }
            reader.onloadend = function(e){
                done(null, e.target.result);
            };
            reader.readAsText(file);
        }, done);
    });
};

// Cleaner API for writing to local fs.
//
// @param {String} dest
// @param {String} blob
// @param {Function} done
Loader.prototype.write = function(dest, blob, done){
    var self = this;
    this.fs.root.getFile(dest, {'create': true, 'exclusive': false}, function(entry){
        entry.createWriter(function(writer){
            writer.onwriteend = function(e){
                done();
            };
            writer.write(blob);
        }, done);
    }, done);
};

// Kicks everything off.
// If we're in chrome, on window load, call device ready.
// If we're in cordova land, wait for device ready to actually fire.
//
// @param {Function} done
Loader.prototype.load = function(done){
    var self = this;
    window.addEventListener('load', function(){
        if(!window.cordova){
            return self.deviceReady(done);
        }
        document.addEventListener('deviceready', function(){
            self.deviceReady(done);
        });
    });
    return this;
};

// Called by window load or device ready depeneding on where we're running.
// Tries to get permissions for local filesystem and gets bootstrapping JSON
// data from the remote.  After it gets all that, does some version parsing to
// set up the data used to determine if an asset should be upgraded.
// When thats all good, calls `insert` to start loading our assets into the DOM.
Loader.prototype.deviceReady = function(done){
    done = done || function(){};
    var self = this;
    parallel([
        function getFS(callback){
            var requestFS = window.requestFileSystem || window.webkitRequestFileSystem;
            if(!requestFS){
                return callback();
            }
            var winFS = window.LocalFileSystem|| window;
            requestFS(winFS.PERSISTENT, 50 * 1024 * 1024, function(fs){
                self.fs = fs;
                callback();
            }, callback);
        },
        function getBootstrap(callback){
            fetch(self.bootstrapPath + '?' + Math.random(), function(err, event){
                if(err){
                    return callback(err);
                }
                self.versions = JSON.parse(event.target.response);
                // If we haven't bootstrapped yet, default to whats there now.
                if(!self.appVersion){
                    self.appVersion = self.versions.version;
                    self.version = self.parseVersion(self.appVersion);
                }
                self.incomingVersion = self.parseVersion(self.versions.version);
                callback();
            });
        }
    ], function(err){
        self.insert(done);
    });
};

// Usage:
// require('sterno')(process.env.url, ['/app.js']);
//
// on load / device ready:
//
// # get local file system for reading / writing file caches
// # load bootstrap json from remote
// # insert assets to the dom, and cache locally if need be
module.exports = function(url, assets, done){
    return new Loader(url, assets).load(done);
};
<|endoftext|>


<commit_before>'use strict';

var util = require('./lib/util');
util.boil = require('./lib/boil');

exports = module.exports = Parth;

function Parth(){
  if(!(this instanceof Parth)){ return new Parth(); }
  this.store = Object.create(null);
  this.regex = Object.create(null);
  this.master = Object.create(null);
  this.master.length = 0;
}

// ## Parth.set(path[, opt])
// > convert a string or array path to a regexp
// > TODO: implement regexp input
//
// arguments
//  - `path` type `string` or `array`
//  - `opt` type `object` optional holding all extra information
//
// return `this`
// --
// api.public
// --
//

util.paramRE = /(^|\W)\:([^()?#\.\/ ]+)(\(+[^ ]*\)+)?/g;

Parth.prototype.set = function(p){
  var o = Object.create(null);
  if(!util.boil(p, o)){ return null; }
  if(this.store[o.path]){
    o = this.store[o.path];
    return o.regex;
  } // ^ already defined

  // # of default and custom regex
  var sep; o.custom = o.default = 0;
  o.regex = '^' + o.path.replace(/\S+/g, function(stem){
    if((/\//).test(stem)){ sep = '/#?'; } else
    if((/\./).test(stem)){ sep = '.';   } else { sep = ''; }
    return stem.replace(util.paramRE, function($0, $1, $2, $3){
      if($3){ o.custom++; } else { o.default++; }
      return $1 + ($3 || '([^' + sep + '^]+)');
    });
  }).replace(/[^\/ ]*\/\S+/g, '$&\\/?(?:[^ ])?') // includes /
    .replace(/\^\]\+/g, ' ]+'); // default params;

  // update depths
  o.depth = o.argv.length;
  if(!this.regex[o.depth]){
    this.regex[o.depth] = [ ];
    while(this.master.length < o.depth){
      this.master[++this.master.length] = null;
    }
  }

  // attach relevant info.
  o.regex = new RegExp(o.regex, 'i');
  o.regex.path = o.path; o.regex.argv = o.argv;
  o.regex.def = o.default; o.regex.cust = o.custom;

  // reorder them
  this.regex[o.depth].push(o.regex);
  this.regex[o.depth] = this.regex[o.depth].sort(function(a, b){
    return ((a.def - a.cust) || -Infinity) - (b.def - b.cust);
  });

  // sum up all learned: void groups and make it one
  this.master[o.depth] =
    new RegExp(this.regex[o.depth].map(function(re){
      return '(' + re.source.replace(/\((?=[^?])/g, '(?:') + ')';
    }).join('|'), 'i');

  // clean, save & return
  delete o.default; delete o.custom;
  this.store[o.path] = o;
  return o.regex;
};

// ## Parth.get
// > take a string or array, return the matching path
//
// arguments
//  - `path` type `string` or `array`
//  - `o` type `object` optional holding all extra information
//
// return `o`
// --
// api.public
// --
//
Parth.prototype.get = function(p, o){
  o = o || { }; o.notFound = true;
  if(!util.boil(p, o)){ return null; }
  if(this.store[o.path]){
    util.merge(o, this.store[o.path]);
    o.match = o.path; return o.regex;
  }

  var found = this.master;
  var index = this.master.length;
  while(index > -1){
    if(found[index] && found[index].test(o.path)){
      found = o.path.match(found[index]).slice(1);
      o.depth = index; index = -1;
    } else if(--index < 1){ return null; }
    // ^ depth starts at 1 :), notFound
  }

  o.match = found.join('');
  index = found.indexOf(o.match);
  var regex = this.regex[o.depth][index];
  o.params = {_: o.path.match(regex).slice(1)};

  index = 0;
  regex.path.replace(util.paramRE, function($0, $1, $2){
    var p = o.params._[index];
    var num = Number(p) + 1;
    o.params[$2] = o.params._[index] = num ? num-1 : p;
    o.params._[index++] = $2;
  });

  o.notFound = !(/[ ]/).test(o.path.replace(o.match, '')[0] || ' ');
  return regex;
};
<commit_msg>improvements: - less checks on instantiation - better more consistent docs - fixed bug with paramsRE where /:foo(regex)/:bar(regex) was not correctly taken into account - Now it is possible to escape properly all outside parens :) - Lint everything - fixed bug for root url / where it did not get the optional query - do not give raw path advantadge for regex ordering for now - o.notFound has now the portion from the path that did not match, so in case is an empty string notFound will be falsy<commit_after>'use strict';

var util = require('./lib/util');
util.boil = require('./lib/boil');

exports = module.exports = Parth;

function Parth(){
  if(this instanceof Parth){
    this.store = Object.create(null);
    this.regex = Object.create(null);
    this.master = {length: 0};
    return this;
  }
  return new Parth();
}

// ## parth.set(path)
// > convert a path to a regular expression
// > TODO: regexp input
//
// arguments
//  - path, type `string`
//
// throws error if the path type is not supported
//
// returns a regular expression for the path
//
// --
// api.public
// --
//

util.paramRE = /(^|\W)\:([^(?#/.: ]+)(\([^)]*?\)+)?/g;

Parth.prototype.set = function(p){
  var o = util.boil(p);
  if(this.store[o.path]){
    o.match = o.path;
    return this.store[o.path].regex;
  }

  // # of default and custom regex
  var sep; o.custom = o.default = 0;
  o.regex = '^' + o.path.replace(/\S+/g, function(stem){
    if((/\//).test(stem)){ sep = '/#?'; } else
    if((/\./).test(stem)){ sep = '.';   } else { sep = ''; }
    return stem.replace(util.paramRE, function($0, $1, $2, $3){
      if($3){ o.custom++; } else { o.default++; }
      return $1 + ($3 || '([^' + sep + '^]+)');
    });
  }).replace(/[^\/ ]*\/\S*/g, '$&/?(?:[^\\/ ]+)?') // includes /
    .replace(/\^\]\+/g, ' ]+') // default params
    .replace(/[^()]+(?=\(|$)/g, function($0){ // scape all outside parens
      return $0.replace(/[\/\.]/g, '\\$&');
    });

  // update depths
  o.depth = o.argv.length;
  if(!this.regex[o.depth]){
    this.regex[o.depth] = [ ];
    while(this.master.length < o.depth){
      this.master[++this.master.length] = null;
    }
  }

  // attach relevant info.
  o.regex = new RegExp(o.regex, 'i');
  o.regex.path = o.path; o.regex.argv = o.argv;
  o.regex.def = o.default; o.regex.cust = o.custom;

  // reorder them
  this.regex[o.depth].push(o.regex);
  this.regex[o.depth] = this.regex[o.depth].sort(function(a, b){
    return (a.def - a.cust) - (b.def - b.cust);
  });

  // sum up all learned: void groups and make it one
  this.master[o.depth] =
    new RegExp(this.regex[o.depth].map(function(re){
      return '(' + re.source.replace(/\((?=[^?])/g, '(?:') + ')';
    }).join('|'), 'i');

  // clean, save & return
  delete o.default; delete o.custom;
  this.store[o.path] = o;
  return o.regex;
};

// ## parth.get(path[, options])
// > take a string or array, return the matching path
//
// arguments
//  - path, type `string` or `array`
//  - options, type `object` optional holding all extra information
//
// returns regex matching path
// --
// api.public
// --
//
Parth.prototype.get = function(p, o){
  o = util.boil(p, o);
  if(this.store[o.path]){
    return util.merge(o, this.store[o.path]).regex;
  }

  o.notFound = true;
  var found = this.master;
  var index = (this.store[o.argv.length] || found).length;

  while(index > -1){
    if(found[index] && found[index].test(o.path)){
      found = o.path.match(found[index]).slice(1);
      o.depth = index; index = -1;
    } else if(--index < 1){ return null; }
    // ^ depth starts at 1 :), notFound
  }

  o.match = found.join('');
  index = found.indexOf(o.match);
  var regex = this.regex[o.depth][index];
  o.params = {_: o.path.match(regex).slice(1)};

  index = 0;
  regex.path.replace(util.paramRE, function($0, $1, $2){
    o.params[$2] = o.params._[index];
    o.params._[index++] = $2;
  });

  o.notFound = o.path.replace(o.match, '');
  return regex;
};
<|endoftext|>


<commit_before>var io = require('socket.io')();
var haversine = require('haversine');

/*
Stores all client (user) data in memory
user_id: {
	data: {user, lat, long, nearby},
	nearby: [list of users],
	socket_id
}
*/
var clients = {};

// Stores the socket objects by socket ID
// Socket id => Socket
var connections = {};

// Radius in kilometers to use for computing nearby users
var DAP_RADIUS = 1.0;

// Port to run socket.io server on
var SERVER_PORT = 80;

function get_data() {
	// Get public user data to send back to clients
	var data = [];
	for (var client_id in clients) {
		var client = clients[client_id];
		// Ensure there is data from this user before sending it
		if (client.data && Object.keys(client.data).length > 0) {
			data.push(client.data);
		}
	}
	return data;
}

function push_to_all() {
	// Send data to all clients
	var client_data = get_data();
	io.emit('user_data', client_data);
}

// Returns the distance in kilometers between two geographic locations
function get_distance(source, dest) {
	if (source != dest) {
		var source_client = clients[source].data;
		var dest_client = clients[dest].data;
		if ('lat' in source_client && 'lng' in source_client
				&& 'lat' in dest_client && 'lng' in dest_client) {
			// Get start and end points
			var start = {
				latitude: source_client.lat,
				longitude: source_client.lng
			};
			var end = {
				latitude: dest_client.lat,
				longitude: dest_client.lng
			};

			// Compute the distance using haversine
			return haversine(start, end);
		}
	}
	return null;
}

// Returns a list of nearby user IDs based on dap radius
function get_nearby_users(source, radius) {
	var nearby_users = [];
	radius = radius || DAP_RADIUS

	for (var dest in clients) {
		var distance = get_distance(source, dest);
		if (distance && distance < radius) {
			nearby_users.push(dest);
		}
	}

	return nearby_users;
}

// Sets the nearby user lists and booleans
function update_nearby_users() {
	for (var source in clients) {
		var nearby_users = get_nearby_users(source);

		// Nearby user list
		clients[source].nearby = nearby_users;

		// Nearby user boolean
		clients[source].data.nearby = (nearby_users.length > 0);
	}
}

function sync_users() {
	// Delete users without socket connections
	for (var user_id in clients) {
		var socket_id = clients[user_id];
		if (!(socket_id in connections)) {
			console.log("User disconnected: " + user_id);
			delete clients[user_id];
		}
	}
}

io.on('connection', function(socket) {

	// Send data to newly connected client
	var client_data = get_data();
	socket.emit('user_data', client_data);

	// Keep socket object to use for future communications
	connections[socket.id] = socket;

	console.log('Client ' + socket.id + ' connected');

	sync_users();

	socket.on('user_data', function(data) {
		if ('user' in data) {
			// Add new user if needed
			if (data.user in clients) {
				// Merge fields from user's message
				// TODO: Make this generic and move "nearby" and other data originating from server to another parent in the dictionary
				var client = clients[data.user];
				client.data.user = data.user;
				client.data.lat = data.lat;
				client.data.lng = data.lng;
			} else {
				// Create new client with data from message
				clients[data.user] = {
					data: data,
					nearby: [],
					socket_id: socket.id
				};
			}

			// Compute nearby users
			update_nearby_users();

			// Send to all clients (nearby may have changed)
			push_to_all();
		}
	});

	socket.on('notification', function(data) {
		// Received notification from client
		if ('user' in data && data.user in clients) {
			var nearby = clients[data.user].nearby;

			// Forward message to all nearby clients
			for (var user in nearby) {
				connections[clients[nearby[user]].socket_id].emit('notification', {
					'source_user': data.user
				});
			}
		}
	});

	socket.on('disconnect', function() {
		// Remove socket and user when they disconnect
		delete connections[socket.id];
		sync_users();

		// Notify other users that this client disconnected
		push_to_all();
	});
});

// Start socket.io server
io.listen(SERVER_PORT);
<commit_msg>Refactored socket events into functions<commit_after>var io = require('socket.io')();
var haversine = require('haversine');

/*
Stores all client (user) data in memory
user_id: {
	data: {user, lat, long, nearby},
	nearby: [list of users],
	socket_id
}
*/
var clients = {};

// Stores the socket objects by socket ID
// Socket id => Socket
var connections = {};

// Radius in kilometers to use for computing nearby users
var DAP_RADIUS = 1.0;

// Port to run socket.io server on
var SERVER_PORT = 80;

function get_data() {
	// Get public user data to send back to clients
	var data = [];
	for (var client_id in clients) {
		var client = clients[client_id];
		// Ensure there is data from this user before sending it
		if (client.data && Object.keys(client.data).length > 0) {
			data.push(client.data);
		}
	}
	return data;
}

function push_to_all() {
	// Send data to all clients
	var client_data = get_data();
	io.emit('user_data', client_data);
}

// Returns the distance in kilometers between two geographic locations
function get_distance(source, dest) {
	if (source != dest) {
		var source_client = clients[source].data;
		var dest_client = clients[dest].data;
		if ('lat' in source_client && 'lng' in source_client
				&& 'lat' in dest_client && 'lng' in dest_client) {
			// Get start and end points
			var start = {
				latitude: source_client.lat,
				longitude: source_client.lng
			};
			var end = {
				latitude: dest_client.lat,
				longitude: dest_client.lng
			};

			// Compute the distance using haversine
			return haversine(start, end);
		}
	}
	return null;
}

// Returns a list of nearby user IDs based on dap radius
function get_nearby_users(source, radius) {
	var nearby_users = [];
	radius = radius || DAP_RADIUS

	for (var dest in clients) {
		var distance = get_distance(source, dest);
		if (distance && distance < radius) {
			nearby_users.push(dest);
		}
	}

	return nearby_users;
}

// Sets the nearby user lists and booleans
function update_nearby_users() {
	for (var source in clients) {
		var nearby_users = get_nearby_users(source);

		// Nearby user list
		clients[source].nearby = nearby_users;

		// Nearby user boolean
		clients[source].data.nearby = (nearby_users.length > 0);
	}
}

function sync_users() {
	// Delete users without socket connections
	for (var user_id in clients) {
		var socket_id = clients[user_id];
		if (!(socket_id in connections)) {
			console.log("User disconnected: " + user_id);
			delete clients[user_id];
		}
	}
}

function handle_on_connection(socket) {
	// Send data to newly connected client
	var client_data = get_data();
	socket.emit('user_data', client_data);

	// Keep socket object to use for future communications
	connections[socket.id] = socket;

	console.log('Client ' + socket.id + ' connected');

	sync_users();
}

function handle_user_data(socket, data) {
	if ('user' in data) {
		// Add new user if needed
		if (data.user in clients) {
			// Merge fields from user's message
			// TODO: Make this generic and move "nearby" and other data originating from server to another parent in the dictionary
			var client = clients[data.user];
			client.data.user = data.user;
			client.data.lat = data.lat;
			client.data.lng = data.lng;
		} else {
			// Create new client with data from message
			clients[data.user] = {
				data: data,
				nearby: [],
				socket_id: socket.id
			};
		}

		// Compute nearby users
		update_nearby_users();

		// Send to all clients (nearby may have changed)
		push_to_all();
	}
}

function handle_notification(socket, data) {
	// Received notification from client
	if ('user' in data && data.user in clients) {
		var nearby = clients[data.user].nearby;

		// Forward message to all nearby clients
		for (var user in nearby) {
			connections[clients[nearby[user]].socket_id].emit('notification', {
				'source_user': data.user
			});
		}
	}
}

function handle_disconnect(socket) {
	// Remove socket and user when they disconnect
	delete connections[socket.id];
	sync_users();

	// Notify other users that this client disconnected
	push_to_all();
}

function register_socket_handlers(socket) {
	socket.on('user_data', function(data) {
		handle_user_data(socket, data);
	});

	socket.on('notification', function(data) {
		handle_notification(socket, data);
	});

	socket.on('disconnect', function() {
		handle_disconnect(socket);
	});
}

// Handle when new clients connect
io.on('connection', function(socket) {
	handle_on_connection(socket);
	register_socket_handlers(socket);
});

// Start socket.io server
io.listen(SERVER_PORT);
<|endoftext|>