<commit_before>var express = require('express');
var args = process.argv.join('|');
var port = /\-\-port\|(\d+)(?:\||$)/.test(args) ? ~~RegExp.$1 : 8080;
var path = require('path');
var DOCUMENT_ROOT = path.resolve(/\-\-root\|(.*?)(?:\||$)/.test(args) ? RegExp.$1 : process.cwd());
var app = express();

console.log(DOCUMENT_ROOT);

// server.conf 功能
// 支持 test/ 目录下面 .js js 脚本功能和 json 预览功能。
// 注意这里面的.js，不是一般的.js 文件，而是相当于 express 的 route.
app.use(require('yog-devtools')({
    view_path: '',    // 避免报错。
    rewrite_file: path.join(DOCUMENT_ROOT, 'config', 'server.conf'),
    data_path: path.join(DOCUMENT_ROOT, 'test')
}));

// 静态文件列表。
app.use((function() {
    var url = require('url');
    var fs = require('fs');

    return function(req, res, next) {
        var pathname = url.parse(req.url).pathname;
        var fullpath = path.join(DOCUMENT_ROOT, pathname);

        if (/\/$/.test(pathname) && fs.existsSync(fullpath)) {
            var stat = fs.statSync(fullpath);

            if (stat.isDirectory()) {
                var html = '';

                var files = fs.readdirSync(fullpath);

                html = '<!doctype html>';
                html += '';
                html += '';
                html += '<title>' + pathname + '</title>';
                html += '';
                html += '';
                html += '
 - ' + pathname + '
';
                html += '
';
                html += '
';

                if(pathname != '/'){
                    html += '
..
';
                }

                files.forEach(function(item) {
                    var s_url = path.join(pathname, item);
                    html += '
'+ item + '
';
                });

                html += '
';
                html += '
';
                html += '';
                html += '';

                res.send(html);
                return;
            }
        }

        next();
    };
})());

// 静态文件输出
app.use(express.static(DOCUMENT_ROOT, {
    index: ['index.html', 'index.htm', 'default.html', 'default.htm'],
    extensions: ['html', 'htm']
}));

// 错误捕获。
app.use(function(err, req, res, next) {
    console.log(err);
});

// Bind to a port
var server = app.listen(port, function() {
    console.log('Listening on http://localhost:%d', port);
});

// 在接收到关闭信号的时候，关闭所有的 socket 连接。
(function() {
    var sockets = [];

    server.on('connection', function (socket) {
        sockets.push(socket);

        socket.on('close', function() {
            var idx = sockets.indexOf(socket);
            ~idx && sockets.splice(idx, 1);
        });
    });

    var finalize = function() {
        // Disconnect from cluster master
        process.disconnect && process.disconnect();
    }

    // 关掉服务。
    process.on('SIGTERM', function() {
        sockets.length ? sockets.forEach(function(socket) {
            socket.destroy();
            finalize();
        }): server.close(finalize);
    });
})(server);
<commit_msg>update<commit_after>var express = require('express');
var args = process.argv.join('|');
var port = /\-\-port\|(\d+)(?:\||$)/.test(args) ? ~~RegExp.$1 : 8080;
var path = require('path');
var DOCUMENT_ROOT = path.resolve(/\-\-root\|(.*?)(?:\||$)/.test(args) ? RegExp.$1 : process.cwd());
var app = express();

console.log(DOCUMENT_ROOT);

// server.conf 功能
// 支持 test/ 目录下面 .js js 脚本功能和 json 预览功能。
// 注意这里面的.js，不是一般的.js 文件，而是相当于 express 的 route.
app.use(require('yog-devtools')({
    view_path: '',    // 避免报错。
    rewrite_file: path.join(DOCUMENT_ROOT, 'config', 'server.conf'),
    data_path: path.join(DOCUMENT_ROOT, 'test')
}));

// 静态文件列表。
app.use((function() {
    var url = require('url');
    var fs = require('fs');

    return function(req, res, next) {
        var pathname = url.parse(req.url).pathname;
        var fullpath = path.join(DOCUMENT_ROOT, pathname);

        if (/\/$/.test(pathname) && fs.existsSync(fullpath)) {
            var stat = fs.statSync(fullpath);

            if (stat.isDirectory()) {
                var html = '';

                var files = fs.readdirSync(fullpath);

                html = '<!doctype html>';
                html += '';
                html += '';
                html += '<title>' + pathname + '</title>';
                html += '';
                html += '';
                html += '
 - ' + pathname + '
';
                html += '
';
                html += '
';

                if(pathname != '/'){
                    html += '
..
';
                }

                files.forEach(function(item) {
                    var s_url = path.join(pathname, item);
                    html += '
'+ item + '
';
                });

                html += '
';
                html += '
';
                html += '';
                html += '';

                res.send(html);
                return;
            }
        }

        next();
    };
})());

// 静态文件输出
app.use(express.static(DOCUMENT_ROOT, {
    index: ['index.html', 'index.htm', 'default.html', 'default.htm'],
    extensions: ['html', 'htm']
}));

// 错误捕获。
app.use(function(err, req, res, next) {
    console.log(err);
});

// Bind to a port
var server = app.listen(port, function() {
    console.log('Listening on http://localhost:%d', port);
});

// 在接收到关闭信号的时候，关闭所有的 socket 连接。
(function() {
    var sockets = [];

    server.on('connection', function (socket) {
        sockets.push(socket);

        socket.on('close', function() {
            var idx = sockets.indexOf(socket);
            ~idx && sockets.splice(idx, 1);
        });
    });

    var finalize = function() {
        // Disconnect from cluster master
        process.disconnect && process.disconnect();
    }

    // 关掉服务。
    process.on('SIGTERM', function() {
        sockets.length ? sockets.forEach(function(socket) {
            socket.destroy();
            finalize();
        }): server.close(finalize);
    });
})(server);
<|endoftext|>


<commit_before>'use strict';

const Promise = require('bluebird');

const chalk = require('chalk');
const del = require('del');
const nvl = require('nvl');
const ora = require('ora');

const MODE = require('../mode');
const spawnNPM = require('./spawnNPM');

const lineEncoder = require('../lineEncoder');

const validLineModes = ['CRLF', 'LF', 'CR'];
const fs = require('fs-extra-promise');
const ddd = require('fs')

/**
 * Spawn blast (remove) command.
 * @param  {String}  [lineMode=all] Blast mode ('crlf', 'lf', 'cr')
 * @param  {Boolean} [verbose=false] Display more information.
 * @return {Promise}                 Promise of spawnBlast.
 */
function spawnerLines(lineMode, verbose) {
  verbose = nvl(verbose, false);
  let spinner = ora({
    color: 'yellow'
  });

  // starting enter
  console.log();

  const upperLineMode = (typeof lineMode === 'string' ? lineMode : '').toUpperCase();

  if (validLineModes.indexOf(upperLineMode) < 0) {
    const invalidOptionMessage = `${chalk.red('Invalid line mode')}: ${upperLineMode}; Must be: ${chalk.gray(validLineModes.join(', '))}`;
    console.error(invalidOptionMessage);
    return Promise.reject(invalidOptionMessage);
  }

  let normalizeItems = ['package.json', 'package-lock.json'];

  let itemsAsString = normalizeItems.map(p => `'${chalk.bgBlack.gray.bold(p)}'`).join(' ');

  spinner.text = `${chalk.yellow('Normalizing lines')} ${itemsAsString}...`;
  spinner.start();

  const normalizePromises = normalizeItems.map(fileName => {
    return fs.readFileAsync(fileName, { throws: false }).then(data => {
      const result = lineEncoder(data.toString(), upperLineMode);
      const retGood = Object.assign({}, {
        fileName,
      }, result);
      if (result.changed) {
        return fs.writeFileAsync(fileName, result.output).then(() => retGood).catch(err => {
          return {
            error: 'writeError',
            errorObject: err,
            lineResult: retGood,
          }
        })
      } else {
        return retGood;
      }
    }).catch(err => {
      return {
        error: (err.code === 'ENOENT') ? 'notFound' : 'otherError',
        errorObject: err,
        fileName: fileName
      };
    });
  });

  let normalizePromise = Promise.all(normalizePromises).catch(err => {
    spinner.fail(`${chalk.red('Normalize lines FAILED')}: ${blastItems}; Err: ${chalk.gray(err)}`);
    throw err;
  });

  return normalizePromise
    // .then(() => new Promise(function (resolve, reject) {
    //   setTimeout(function () {
    //     resolve();
    //   }, 100)
    // }))
    .then((results) => {

      const notFound = results.filter(p => p.error === 'notFound');
      const otherError = results.filter(p => p.error && (p.error !== 'notFound'));

      const noErrors = results.filter(p => !p.error);

      const changedItems = noErrors.filter(p => p.changed);
      const notChangedItems = noErrors.filter(p => !p.changed);
      // const writeError = results.filter(p => p.error === 'writeError');
      // const otherError = results.filter(p => (p.error !== 'writeError') && (p.error !== 'notFound'));

      let notFoundString = notFound.map(p => `'${chalk.yellow(p.fileName)}'`).join(' ');
      let changedItemsString = changedItems.map(p => `'${chalk.green(p.fileName)}'`).join(' ');
      let notChangedItemsString = notChangedItems.map(p => `'${chalk.green(p.fileName)}'`).join(' ');

      let successString = '';
      if (changedItemsString) {
        successString += `${chalk.yellow(`Changed to ${upperLineMode}`)}: ${changedItemsString}; `
      }
      if (notChangedItemsString) {
        successString += `${chalk.white.bgGreen(`Already ${upperLineMode}`)}: ${notChangedItemsString}; `
      }
      if (notFoundString) {
        successString += `${chalk.magenta('Not Found')}: ${notFoundString}; `
      }

      if (otherError.length > 0) {

        const writeErrorString = otherError.map(p => `${chalk.red(p.error)}: '${chalk.gray(p.fileName)}'; Error: ${chalk.gray(p.errorObject)}`).join(`
 - `);
        let failMessage = `${chalk.red.bold('Normalize Failures')} ${otherError.length} error(s) occured:

 - ${writeErrorString}`;

        if (successString) {
          failMessage += `

${chalk.green('Some success here:')}

${successString}
`
        }
        spinner.fail(failMessage);
      }
      else {
        spinner.succeed(`${chalk.green('Normalized')} ${successString}`);
      }
      return Promise.resolve();
    })
    .catch(err => {
      process.stderr.write(err.toString());
      return err;
    });
}

/**
 * Spawn blast (remove) command.
 * @param  {String}  [blastMode=all] Blast mode (all|node_modules|lock_file)
 * @param  {Boolean} [verbose=false] Display more information.
 * @return {Promise}                 Promise of spawnBlast.
 */
function spawnerBlast(blastMode, verbose) {
  verbose = nvl(verbose, false);
  let spinner = ora({
    color: 'yellow'
  });

  let blastItems = [];

  if (blastMode === 'all' || blastMode === 'node_modules') {
    blastItems.push('node_modules/');
  }
  if (blastMode === 'all' || blastMode === 'lock_file') {
    blastItems.push('package-lock.json');

  }

  let blastItemsAsString = blastItems.map(p => `'${chalk.bgBlack.gray.bold(p)}'`).join(' ');

  spinner.text = `${chalk.yellow('Blasting (removing)')} ${blastItemsAsString}...`;
  spinner.start();

  let blastPromise = del(blastItems).catch(err => {
    spinner.fail(`${chalk.red('Blast (removal) failed')}: ${blastItems}; Err: ${chalk.gray(err)}`);
    throw err;
  });

  return blastPromise
    // .then(() => new Promise(function (resolve, reject) {
    //   setTimeout(function () {
    //     resolve();
    //   }, 100)
    // }))
    .then(() => {
      spinner.succeed(`${chalk.green('Blasted (removed)')} ${blastItemsAsString}`);
      return Promise.resolve();
    })
    .catch(err => {
      process.stderr.write(err.toString());
      return err;
    });
}

/**
 * Spawn NPM.
 * @param  {String[]} npmArgs         args to pass to npm.
 * @param  {Boolean}  [verbose=false] Display more information.
 * @return {Promise}                  Promise of spawn.
 */
function spawner(npmArgs, verbose) {
  verbose = nvl(verbose, false);
  let spinner = ora({
    color: 'yellow'
  });
  let argsAsString = chalk.cyan.bold(npmArgs.join(' '));

  const startMessage = `${chalk.yellow('Running')} ${chalk.cyan('npm')} ${argsAsString} ...`;;
  if (verbose) {
    console.log(`
${chalk.yellow('-')} ${startMessage}
`)
  } else {
    spinner.text = startMessage;
    spinner.start();
  }

  return spawnNPM(npmArgs, verbose)
    .then(() => {
      const finishMessage = `${chalk.green('Finished')} ${chalk.cyan('npm')} ${argsAsString}`;
      if (verbose) {
        console.log(`
${chalk.green('√')} ${finishMessage}`);
      } else {
        spinner.succeed(finishMessage);
      }
      return Promise.resolve();
    })
    .catch(err => {
      process.stderr.write(err.toString());
      return err;
    });
}

module.exports = spawner;
module.exports.blast = spawnerBlast;
module.exports.lines = spawnerLines;
<commit_msg>Added `console.log` to start of `spawnBlast` to provide a line break.<commit_after>'use strict';

const Promise = require('bluebird');

const chalk = require('chalk');
const del = require('del');
const nvl = require('nvl');
const ora = require('ora');

const MODE = require('../mode');
const spawnNPM = require('./spawnNPM');

const lineEncoder = require('../lineEncoder');

const validLineModes = ['CRLF', 'LF', 'CR'];
const fs = require('fs-extra-promise');
const ddd = require('fs')

/**
 * Spawn blast (remove) command.
 * @param  {String}  [lineMode=all] Blast mode ('crlf', 'lf', 'cr')
 * @param  {Boolean} [verbose=false] Display more information.
 * @return {Promise}                 Promise of spawnBlast.
 */
function spawnerLines(lineMode, verbose) {
  verbose = nvl(verbose, false);
  let spinner = ora({
    color: 'yellow'
  });

  // starting enter
  console.log();

  const upperLineMode = (typeof lineMode === 'string' ? lineMode : '').toUpperCase();

  if (validLineModes.indexOf(upperLineMode) < 0) {
    const invalidOptionMessage = `${chalk.red('Invalid line mode')}: ${upperLineMode}; Must be: ${chalk.gray(validLineModes.join(', '))}`;
    console.error(invalidOptionMessage);
    return Promise.reject(invalidOptionMessage);
  }

  let normalizeItems = ['package.json', 'package-lock.json'];

  let itemsAsString = normalizeItems.map(p => `'${chalk.bgBlack.gray.bold(p)}'`).join(' ');

  spinner.text = `${chalk.yellow('Normalizing lines')} ${itemsAsString}...`;
  spinner.start();

  const normalizePromises = normalizeItems.map(fileName => {
    return fs.readFileAsync(fileName, { throws: false }).then(data => {
      const result = lineEncoder(data.toString(), upperLineMode);
      const retGood = Object.assign({}, {
        fileName,
      }, result);
      if (result.changed) {
        return fs.writeFileAsync(fileName, result.output).then(() => retGood).catch(err => {
          return {
            error: 'writeError',
            errorObject: err,
            lineResult: retGood,
          }
        })
      } else {
        return retGood;
      }
    }).catch(err => {
      return {
        error: (err.code === 'ENOENT') ? 'notFound' : 'otherError',
        errorObject: err,
        fileName: fileName
      };
    });
  });

  let normalizePromise = Promise.all(normalizePromises).catch(err => {
    spinner.fail(`${chalk.red('Normalize lines FAILED')}: ${blastItems}; Err: ${chalk.gray(err)}`);
    throw err;
  });

  return normalizePromise
    // .then(() => new Promise(function (resolve, reject) {
    //   setTimeout(function () {
    //     resolve();
    //   }, 100)
    // }))
    .then((results) => {

      const notFound = results.filter(p => p.error === 'notFound');
      const otherError = results.filter(p => p.error && (p.error !== 'notFound'));

      const noErrors = results.filter(p => !p.error);

      const changedItems = noErrors.filter(p => p.changed);
      const notChangedItems = noErrors.filter(p => !p.changed);
      // const writeError = results.filter(p => p.error === 'writeError');
      // const otherError = results.filter(p => (p.error !== 'writeError') && (p.error !== 'notFound'));

      let notFoundString = notFound.map(p => `'${chalk.yellow(p.fileName)}'`).join(' ');
      let changedItemsString = changedItems.map(p => `'${chalk.green(p.fileName)}'`).join(' ');
      let notChangedItemsString = notChangedItems.map(p => `'${chalk.green(p.fileName)}'`).join(' ');

      let successString = '';
      if (changedItemsString) {
        successString += `${chalk.yellow(`Changed to ${upperLineMode}`)}: ${changedItemsString}; `
      }
      if (notChangedItemsString) {
        successString += `${chalk.white.bgGreen(`Already ${upperLineMode}`)}: ${notChangedItemsString}; `
      }
      if (notFoundString) {
        successString += `${chalk.magenta('Not Found')}: ${notFoundString}; `
      }

      if (otherError.length > 0) {

        const writeErrorString = otherError.map(p => `${chalk.red(p.error)}: '${chalk.gray(p.fileName)}'; Error: ${chalk.gray(p.errorObject)}`).join(`
 - `);
        let failMessage = `${chalk.red.bold('Normalize Failures')} ${otherError.length} error(s) occured:

 - ${writeErrorString}`;

        if (successString) {
          failMessage += `

${chalk.green('Some success here:')}

${successString}
`
        }
        spinner.fail(failMessage);
      }
      else {
        spinner.succeed(`${chalk.green('Normalized')} ${successString}`);
      }
      return Promise.resolve();
    })
    .catch(err => {
      process.stderr.write(err.toString());
      return err;
    });
}

/**
 * Spawn blast (remove) command.
 * @param  {String}  [blastMode=all] Blast mode (all|node_modules|lock_file)
 * @param  {Boolean} [verbose=false] Display more information.
 * @return {Promise}                 Promise of spawnBlast.
 */
function spawnerBlast(blastMode, verbose) {
  verbose = nvl(verbose, false);
  let spinner = ora({
    color: 'yellow'
  });

  console.log();

  let blastItems = [];

  if (blastMode === 'all' || blastMode === 'node_modules') {
    blastItems.push('node_modules/');
  }
  if (blastMode === 'all' || blastMode === 'lock_file') {
    blastItems.push('package-lock.json');

  }

  let blastItemsAsString = blastItems.map(p => `'${chalk.bgBlack.gray.bold(p)}'`).join(' ');

  spinner.text = `${chalk.yellow('Blasting (removing)')} ${blastItemsAsString}...`;
  spinner.start();

  let blastPromise = del(blastItems).catch(err => {
    spinner.fail(`${chalk.red('Blast (removal) failed')}: ${blastItems}; Err: ${chalk.gray(err)}`);
    throw err;
  });

  return blastPromise
    // .then(() => new Promise(function (resolve, reject) {
    //   setTimeout(function () {
    //     resolve();
    //   }, 100)
    // }))
    .then(() => {
      spinner.succeed(`${chalk.green('Blasted (removed)')} ${blastItemsAsString}`);
      return Promise.resolve();
    })
    .catch(err => {
      process.stderr.write(err.toString());
      return err;
    });
}

/**
 * Spawn NPM.
 * @param  {String[]} npmArgs         args to pass to npm.
 * @param  {Boolean}  [verbose=false] Display more information.
 * @return {Promise}                  Promise of spawn.
 */
function spawner(npmArgs, verbose) {
  verbose = nvl(verbose, false);
  let spinner = ora({
    color: 'yellow'
  });
  let argsAsString = chalk.cyan.bold(npmArgs.join(' '));

  const startMessage = `${chalk.yellow('Running')} ${chalk.cyan('npm')} ${argsAsString} ...`;;
  if (verbose) {
    console.log(`
${chalk.yellow('-')} ${startMessage}
`)
  } else {
    spinner.text = startMessage;
    spinner.start();
  }

  return spawnNPM(npmArgs, verbose)
    .then(() => {
      const finishMessage = `${chalk.green('Finished')} ${chalk.cyan('npm')} ${argsAsString}`;
      if (verbose) {
        console.log(`
${chalk.green('√')} ${finishMessage}`);
      } else {
        spinner.succeed(finishMessage);
      }
      return Promise.resolve();
    })
    .catch(err => {
      process.stderr.write(err.toString());
      return err;
    });
}

module.exports = spawner;
module.exports.blast = spawnerBlast;
module.exports.lines = spawnerLines;
<|endoftext|>


<commit_before>'use strict';

const _sinon = require('sinon');

/**
 * Class that creates a stub and contains useful methods to track the response
 * returned on each call. This is especially useful when tracking responses
 * from multiple async calls.
 */
class Stub {
    /**
     * @param {Object} instance The object instance on which the method will be
     *        stubbed.
     * @param {String} methodName The name of the method on the object that
     *        needs to be stubbed.
     * @param {Object|Function} returnValue A return value that will be
     *        returned by the stub. If a function is passed in, the function
     *        will be invoked, and ther return value of the function will be
     *        returned as the response.
     * @param {Boolean} [isAsync=false] An optional parameter that indicates if
     *        method being stubbed is an async method.
     * @param {Boolean} [create=false] An optional parameter that indicates if
     *        missing methods need to be created using an empty function.
     */
    constructor(instance, methodName, returnValue, create, isAsync) {
        if(create && typeof instance[methodName] !== 'function') {
            instance[methodName] = () => {};
        }

        this._responses = [];
        const func = this._initReplaceFunction(isAsync, returnValue);
        this._stub = _sinon.stub(instance, methodName, func);
    }

    /**
     * Creates a replacement function that will be used to replace the stubbed
     * method.
     *
     * @private
     * @param {Boolean} isAsync Determines if the method being stubbed is an
     *        async function.
     * @param {Object|Function} returnValue A return value that will be
     *        returned by the stub. If a function is passed in, the function
     *        will be invoked, and ther return value of the function will be
     *        returned as the response.
     *
     * @return {Function} A replacement function that will be added to the stub.
     */
    _initReplaceFunction(isAsync, returnValue) {
        const responses = this.responses;
        if(!isAsync) {
            return function() {
                let ret = returnValue;
                if(typeof returnValue === 'function') {
                    ret = returnValue();
                }
                responses.push({
                    isAsync: false,
                    args: Array.prototype.slice.call(arguments, 0),
                    ret
                });
                return ret;
            };
        } else {
            return function() {
                const info = {
                    isAsync: true,
                    args: Array.prototype.slice.call(arguments, 0),
                };
                info.promise = new Promise((resolve, reject) => {
                    info.resolve  = resolve;
                    info.reject = reject;
                });
                info.ret = info.promise;
                responses.push(info);
                return info.promise;
            };
        }
    }

    /**
     * An array containing return value information for each call to the stub.
     *
     * @return {Array} An array of return values.
     */
    get responses() {
        return this._responses;
    }

    /**
     * The return value from the first call to the stub.
     *
     * @return {Object} The return value from the first call to the stub.
     */
    get ret() {
        return this.responses.length > 0? this.responses[0].ret:undefined;
    }

    /**
     * The promise value returned by the first call to the stub.
     *
     * @return {Object} The promise value returned by the first call to the stub.
     */
    get promise() {
        return this.responses.length > 0? this.responses[0].promise:undefined;
    }

    /**
     * The promise resolution function for the first call to the stub.
     *
     * @return {Object} The promise resolution function for the first call to
     *         the stub.
     */
    get resolve() {
        return this.responses.length > 0? this.responses[0].resolve:undefined;
    }

    /**
     * The promise rejection function for the first call to the stub.
     *
     * @return {Object} The promise rejection function for the first call to
     *         the stub.
     */
    get reject() {
        return this.responses.length > 0? this.responses[0].reject:undefined;
    }

    /**
     * A reference to the created stub.
     *
     * @return {Object} Reference to the sinon stub object.
     */
    get stub() {
        return this._stub;
    }

    /**
     * Clears all responses that have been tracked up to this point.
     */
    clearResponses() {
        this.responses.splice(0);
    }

    /**
     * Clears all responses that have been tracked up to this point, and also
     * resets the stub.
     */
    reset() {
        this.stub.reset();
        this.responses.splice(0);
    }
}

/**
 * A mocker object that can be used to mock specific methods on an object.
 */
class ObjectMock {
    /**
     * @param {Object} [instance={}] An optional object that represents the
     *        container of the mock methods. If omitted, a default empty object
     *        will be used.
     */
    constructor(instance) {
        if(!instance || (instance instanceof Array) || typeof instance !== 'object') {
            instance = {};
        }
        this._instance = instance;
        this._ctor = _sinon.stub().returns(this._instance);
        this._methods = {};
    }

    /**
     * Returns a reference to the constructor of the mocked object.
     *
     * @return {Object} Reference to the object constructor.
     */
    get ctor() {
        return this._ctor;
    }

    /**
     * Reference to the original instance.
     *
     * @return {Object} Reference to the object instance.
     */
    get instance() {
        return this._instance;
    }

    /**
     * Reference to an object containing method stubs and additional
     * information regarding the stubs (for async methods).
     *
     * @return {Object} A map containing the info about defined stubs
     */
    get methods() {
        return this._methods;
    }

    /**
     * Adds a mock for the specified method.
     *
     * @param {Array|String} methods The name (or array of names) of the
     *        method(s) that will be mocked.  If the method does not exist,
     *        an error will be thrown, unless the create parameter is set to
     *        true.
     * @param {Object} [returnValue=undefined] An optional return value that
     *        will returned by the mock.
     * @param {Boolean} [create=false] If set to true, creates the method if
     *        one does not exist.
     *
     * @return {Object} A reference to the mock object (can be used to chain
     *         method calls)
     */
    addMock(methods, returnValue, create) {
        if(!(methods instanceof Array)) {
            methods = [ methods ];
        }
        methods.forEach((method) => {
            this.methods[method] = new Stub(this.instance, method, returnValue, create, false);
        });
        return this;
    }

    /**
     * Adds a mock for the specified async method. This method returns a promise
     * that can be resolved/rejected to continue the flow.
     *
     * @param {Array|String} methods The name (or array of names) of the
     *        method(s) that will be mocked.  If the method does not exist,
     *        an error will be thrown, unless the create parameter is set to
     *        true.
     * @param {Boolean} [create=false] If set to true, creates the method if
     *        one does not exist.
     *
     * @return {Object} A reference to the mock object (can be used to chain
     *         method calls)
     */
    addAsyncMock(methods, create) {
        if(!(methods instanceof Array)) {
            methods = [ methods ];
        }
        methods.forEach((method) => {
            this.methods[method] = new Stub(this.instance, method, undefined, create, true);
        });
        return this;
    }

    /**
     * Removes a mock for the specified method (sync or async). If no mocks have
     * been defined for the specified method name, no changes will be made for
     * that method.
     *
     * @param {Array|String} methods The name (or array of names) of the
     *        method(s) for which mocks will be removed.
     *
     * @return {Object} A reference to the mock object (can be used to chain
     *         method calls)
     */
    removeMock(methods) {
        if(!(methods instanceof Array)) {
            methods = [ methods ];
        }
        methods.forEach((method) => {
            if(this.methods[method]) {
                this.methods[method].stub.restore();
                delete this.methods[method];
            }
        });
    }
}

module.exports = ObjectMock;
<commit_msg>Add placeholder promise for async methods<commit_after>'use strict';

const _sinon = require('sinon');

/**
 * Class that creates a stub and contains useful methods to track the response
 * returned on each call. This is especially useful when tracking responses
 * from multiple async calls.
 */
class Stub {
    /**
     * @param {Object} instance The object instance on which the method will be
     *        stubbed.
     * @param {String} methodName The name of the method on the object that
     *        needs to be stubbed.
     * @param {Object|Function} returnValue A return value that will be
     *        returned by the stub. If a function is passed in, the function
     *        will be invoked, and ther return value of the function will be
     *        returned as the response.
     * @param {Boolean} [isAsync=false] An optional parameter that indicates if
     *        method being stubbed is an async method.
     * @param {Boolean} [create=false] An optional parameter that indicates if
     *        missing methods need to be created using an empty function.
     */
    constructor(instance, methodName, returnValue, create, isAsync) {
        if(create && typeof instance[methodName] !== 'function') {
            instance[methodName] = () => {};
        }

        this._firstAsync = {
            resolve: null,
            reject: null
        };
        this._firstAsync.promise = new Promise((resolve, reject) => {
            this._firstAsync.resolve = resolve;
            this._firstAsync.reject = reject;
        });
        this._firstAsync.promise.catch((err) => {});
        this._responses = [];
        const func = this._initReplaceFunction(isAsync, returnValue);
        this._stub = _sinon.stub(instance, methodName, func);
    }

    /**
     * Creates a replacement function that will be used to replace the stubbed
     * method.
     *
     * @private
     * @param {Boolean} isAsync Determines if the method being stubbed is an
     *        async function.
     * @param {Object|Function} returnValue A return value that will be
     *        returned by the stub. If a function is passed in, the function
     *        will be invoked, and ther return value of the function will be
     *        returned as the response.
     *
     * @return {Function} A replacement function that will be added to the stub.
     */
    _initReplaceFunction(isAsync, returnValue) {
        const responses = this.responses;
        if(!isAsync) {
            return function() {
                let ret = returnValue;
                if(typeof returnValue === 'function') {
                    ret = returnValue();
                }
                responses.push({
                    isAsync: false,
                    args: Array.prototype.slice.call(arguments, 0),
                    ret
                });
                return ret;
            };
        } else {
            const firstAsync = this._firstAsync;
            return function() {
                const info = {
                    isAsync: true,
                    args: Array.prototype.slice.call(arguments, 0),
                };
                if(responses.length === 0) {
                    info.promise = firstAsync.promise;
                    info.resolve = firstAsync.resolve;
                    info.reject = firstAsync.reject;
                } else {
                    info.promise = new Promise((resolve, reject) => {
                        info.resolve = resolve;
                        info.reject = reject;
                    });
                }
                info.ret = info.promise;
                responses.push(info);
                return info.promise;
            };
        }
    }

    /**
     * An array containing return value information for each call to the stub.
     *
     * @return {Array} An array of return values.
     */
    get responses() {
        return this._responses;
    }

    /**
     * The return value from the first call to the stub.
     *
     * @return {Object} The return value from the first call to the stub.
     */
    get ret() {
        return this.responses.length > 0? this.responses[0].ret:undefined;
    }

    /**
     * The promise value returned by the first call to the stub.
     *
     * @return {Object} The promise value returned by the first call to the stub.
     */
    get promise() {
        return this.responses.length > 0? this.responses[0].promise:this._firstAsync.promise;
    }

    /**
     * The promise resolution function for the first call to the stub.
     *
     * @return {Object} The promise resolution function for the first call to
     *         the stub.
     */
    get resolve() {
        return this.responses.length > 0? this.responses[0].resolve:this._firstAsync.resolve;
    }

    /**
     * The promise rejection function for the first call to the stub.
     *
     * @return {Object} The promise rejection function for the first call to
     *         the stub.
     */
    get reject() {
        return this.responses.length > 0? this.responses[0].reject:this._firstAsync.reject;
    }

    /**
     * A reference to the created stub.
     *
     * @return {Object} Reference to the sinon stub object.
     */
    get stub() {
        return this._stub;
    }

    /**
     * Clears all responses that have been tracked up to this point.
     */
    clearResponses() {
        this.responses.splice(0);
    }

    /**
     * Clears all responses that have been tracked up to this point, and also
     * resets the stub.
     */
    reset() {
        this.stub.reset();
        this.responses.splice(0);
    }
}

/**
 * A mocker object that can be used to mock specific methods on an object.
 */
class ObjectMock {
    /**
     * @param {Object} [instance={}] An optional object that represents the
     *        container of the mock methods. If omitted, a default empty object
     *        will be used.
     */
    constructor(instance) {
        if(!instance || (instance instanceof Array) || typeof instance !== 'object') {
            instance = {};
        }
        this._instance = instance;
        this._ctor = _sinon.stub().returns(this._instance);
        this._methods = {};
    }

    /**
     * Returns a reference to the constructor of the mocked object.
     *
     * @return {Object} Reference to the object constructor.
     */
    get ctor() {
        return this._ctor;
    }

    /**
     * Reference to the original instance.
     *
     * @return {Object} Reference to the object instance.
     */
    get instance() {
        return this._instance;
    }

    /**
     * Reference to an object containing method stubs and additional
     * information regarding the stubs (for async methods).
     *
     * @return {Object} A map containing the info about defined stubs
     */
    get methods() {
        return this._methods;
    }

    /**
     * Adds a mock for the specified method.
     *
     * @param {Array|String} methods The name (or array of names) of the
     *        method(s) that will be mocked.  If the method does not exist,
     *        an error will be thrown, unless the create parameter is set to
     *        true.
     * @param {Object} [returnValue=undefined] An optional return value that
     *        will returned by the mock.
     * @param {Boolean} [create=false] If set to true, creates the method if
     *        one does not exist.
     *
     * @return {Object} A reference to the mock object (can be used to chain
     *         method calls)
     */
    addMock(methods, returnValue, create) {
        if(!(methods instanceof Array)) {
            methods = [ methods ];
        }
        methods.forEach((method) => {
            this.methods[method] = new Stub(this.instance, method, returnValue, create, false);
        });
        return this;
    }

    /**
     * Adds a mock for the specified async method. This method returns a promise
     * that can be resolved/rejected to continue the flow.
     *
     * @param {Array|String} methods The name (or array of names) of the
     *        method(s) that will be mocked.  If the method does not exist,
     *        an error will be thrown, unless the create parameter is set to
     *        true.
     * @param {Boolean} [create=false] If set to true, creates the method if
     *        one does not exist.
     *
     * @return {Object} A reference to the mock object (can be used to chain
     *         method calls)
     */
    addAsyncMock(methods, create) {
        if(!(methods instanceof Array)) {
            methods = [ methods ];
        }
        methods.forEach((method) => {
            this.methods[method] = new Stub(this.instance, method, undefined, create, true);
        });
        return this;
    }

    /**
     * Removes a mock for the specified method (sync or async). If no mocks have
     * been defined for the specified method name, no changes will be made for
     * that method.
     *
     * @param {Array|String} methods The name (or array of names) of the
     *        method(s) for which mocks will be removed.
     *
     * @return {Object} A reference to the mock object (can be used to chain
     *         method calls)
     */
    removeMock(methods) {
        if(!(methods instanceof Array)) {
            methods = [ methods ];
        }
        methods.forEach((method) => {
            if(this.methods[method]) {
                this.methods[method].stub.restore();
                delete this.methods[method];
            }
        });
    }
}

module.exports = ObjectMock;
<|endoftext|>


<commit_before>// http://www.w3.org/TR/CSS21/grammar.html
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g

module.exports = function(css, options){
  options = options || {};

  /**
   * Positional.
   */

  var lineno = 1;
  var column = 1;

  /**
   * Update lineno and column based on `str`.
   */

  function updatePosition(str) {
    var lines = str.match(/\n/g);
    if (lines) lineno += lines.length;
    var i = str.lastIndexOf('\n');
    column = ~i ? str.length - i : column + str.length;
  }

  /**
   * Mark position and patch `node.position`.
   */

  function position() {
    var start = { line: lineno, column: column };
    return function(node){
      node.position = new Position(start);
      whitespace();
      return node;
    };
  }

  /**
   * Store position information for a node
   */

  function Position(start) {
    this.start = start;
    this.end = { line: lineno, column: column };
    this.source = options.source;
  }

  /**
   * Non-enumerable source string
   */

  Position.prototype.content = css;

  /**
   * Error `msg`.
   */

  function error(msg) {
    if (options.silent === true) {
      return false;
    }

    var err = new Error(msg + ' near line ' + lineno + ':' + column);
    err.filename = options.source;
    err.line = lineno;
    err.column = column;
    err.source = css;
    throw err;
  }

  /**
   * Parse stylesheet.
   */

  function stylesheet() {
    return {
      type: 'stylesheet',
      stylesheet: {
        rules: rules()
      }
    };
  }

  /**
   * Opening brace.
   */

  function open() {
    return match(/^{\s*/);
  }

  /**
   * Closing brace.
   */

  function close() {
    return match(/^}/);
  }

  /**
   * Parse ruleset.
   */

  function rules() {
    var node;
    var rules = [];
    whitespace();
    comments(rules);
    while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
      if (node !== false) {
        rules.push(node);
        comments(rules);
      }
    }
    return rules;
  }

  /**
   * Match `re` and return captures.
   */

  function match(re) {
    var m = re.exec(css);
    if (!m) return;
    var str = m[0];
    updatePosition(str);
    css = css.slice(str.length);
    return m;
  }

  /**
   * Parse whitespace.
   */

  function whitespace() {
    match(/^\s*/);
  }

  /**
   * Parse comments;
   */

  function comments(rules) {
    var c;
    rules = rules || [];
    while (c = comment()) {
      if (c !== false) {
        rules.push(c);
      }
    }
    return rules;
  }

  /**
   * Parse comment.
   */

  function comment() {
    var pos = position();
    if ('/' != css.charAt(0) || '*' != css.charAt(1)) return;

    var i = 2;
    while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i;
    i += 2;

    if ("" === css.charAt(i-1)) {
      return error('End of comment missing');
    }

    var str = css.slice(2, i - 2);
    column += 2;
    updatePosition(str);
    css = css.slice(i);
    column += 2;

    return pos({
      type: 'comment',
      comment: str
    });
  }

  /**
   * Parse selector.
   */

  function selector() {
    var m = match(/^([^{]+)/);
    if (!m) return;
    /* @fix Remove all comments from selectors
     * http://ostermiller.org/findcomment.html */
    return trim(m[0])
      .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
      .replace(/(?:"[^"]*"|'[^']*')/g, function(m) {
        return m.replace(/,/g, '\u200C');
      })
      .split(/\s*(?![^(]*\)),\s*/)
      .map(function(s) {
        return s.replace(/\u200C/g, ',');
      });
  }

  /**
   * Parse declaration.
   */

  function declaration() {
    var pos = position();

    // prop
    var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
    if (!prop) return;
    prop = trim(prop[0]);

    // :
    if (!match(/^:\s*/)) return error("property missing ':'");

    // val
    var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);

    var ret = pos({
      type: 'declaration',
      property: prop.replace(commentre, ''),
      value: val ? trim(val[0]).replace(commentre, '') : ''
    });

    // ;
    match(/^[;\s]*/);

    return ret;
  }

  /**
   * Parse declarations.
   */

  function declarations() {
    var decls = [];

    if (!open()) return error("missing '{'");
    comments(decls);

    // declarations
    var decl;
    while (decl = declaration()) {
      if (decl !== false) {
        decls.push(decl);
        comments(decls);
      }
    }

    if (!close()) return error("missing '}'");
    return decls;
  }

  /**
   * Parse keyframe.
   */

  function keyframe() {
    var m;
    var vals = [];
    var pos = position();

    while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
      vals.push(m[1]);
      match(/^,\s*/);
    }

    if (!vals.length) return;

    return pos({
      type: 'keyframe',
      values: vals,
      declarations: declarations()
    });
  }

  /**
   * Parse keyframes.
   */

  function atkeyframes() {
    var pos = position();
    var m = match(/^@([-\w]+)?keyframes */);

    if (!m) return;
    var vendor = m[1];

    // identifier
    var m = match(/^([-\w]+)\s*/);
    if (!m) return error("@keyframes missing name");
    var name = m[1];

    if (!open()) return error("@keyframes missing '{'");

    var frame;
    var frames = comments();
    while (frame = keyframe()) {
      frames.push(frame);
      frames = frames.concat(comments());
    }

    if (!close()) return error("@keyframes missing '}'");

    return pos({
      type: 'keyframes',
      name: name,
      vendor: vendor,
      keyframes: frames
    });
  }

  /**
   * Parse supports.
   */

  function atsupports() {
    var pos = position();
    var m = match(/^@supports *([^{]+)/);

    if (!m) return;
    var supports = trim(m[1]);

    if (!open()) return error("@supports missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@supports missing '}'");

    return pos({
      type: 'supports',
      supports: supports,
      rules: style
    });
  }

  /**
   * Parse host.
   */

  function athost() {
    var pos = position();
    var m = match(/^@host */);

    if (!m) return;

    if (!open()) return error("@host missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@host missing '}'");

    return pos({
      type: 'host',
      rules: style
    });
  }

  /**
   * Parse media.
   */

  function atmedia() {
    var pos = position();
    var m = match(/^@media *([^{]+)/);

    if (!m) return;
    var media = trim(m[1]);

    if (!open()) return error("@media missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@media missing '}'");

    return pos({
      type: 'media',
      media: media,
      rules: style
    });
  }

  /**
   * Parse custom-media.
   */

  function atcustommedia() {
    var pos = position();
    var m = match(/^@custom-media (--[^\s]+) *([^{;]+);/);
    if (!m) return;

    return pos({
      type: 'custom-media',
      name: trim(m[1]),
      media: trim(m[2])
    });
  }

  /**
   * Parse paged media.
   */

  function atpage() {
    var pos = position();
    var m = match(/^@page */);
    if (!m) return;

    var sel = selector() || [];

    if (!open()) return error("@page missing '{'");
    var decls = comments();

    // declarations
    var decl;
    while (decl = declaration()) {
      decls.push(decl);
      decls = decls.concat(comments());
    }

    if (!close()) return error("@page missing '}'");

    return pos({
      type: 'page',
      selectors: sel,
      declarations: decls
    });
  }

  /**
   * Parse document.
   */

  function atdocument() {
    var pos = position();
    var m = match(/^@([-\w]+)?document *([^{]+)/);
    if (!m) return;

    var vendor = trim(m[1]);
    var doc = trim(m[2]);

    if (!open()) return error("@document missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@document missing '}'");

    return pos({
      type: 'document',
      document: doc,
      vendor: vendor,
      rules: style
    });
  }

  /**
   * Parse font-face.
   */

  function atfontface() {
    var pos = position();
    var m = match(/^@font-face */);
    if (!m) return;

    if (!open()) return error("@font-face missing '{'");
    var decls = comments();

    // declarations
    var decl;
    while (decl = declaration()) {
      decls.push(decl);
      decls = decls.concat(comments());
    }

    if (!close()) return error("@font-face missing '}'");

    return pos({
      type: 'font-face',
      declarations: decls
    });
  }

  /**
   * Parse import
   */

  var atimport = _compileAtrule('import');

  /**
   * Parse charset
   */

  var atcharset = _compileAtrule('charset');

  /**
   * Parse namespace
   */

  var atnamespace = _compileAtrule('namespace');

  /**
   * Parse non-block at-rules
   */

  function _compileAtrule(name) {
    var re = new RegExp('^@' + name + ' *([^;\\n]+);');
    return function() {
      var pos = position();
      var m = match(re);
      if (!m) return;
      var ret = { type: name };
      ret[name] = m[1].trim();
      return pos(ret);
    }
  }

  /**
   * Parse at rule.
   */

  function atrule() {
    if (css[0] != '@') return;

    return atkeyframes()
      || atmedia()
      || atcustommedia()
      || atsupports()
      || atimport()
      || atcharset()
      || atnamespace()
      || atdocument()
      || atpage()
      || athost()
      || atfontface();
  }

  /**
   * Parse rule.
   */

  function rule() {
    var pos = position();
    var sel = selector();

    if (!sel) return error('selector missing');
    comments();

    return pos({
      type: 'rule',
      selectors: sel,
      declarations: declarations()
    });
  }

  return addParent(stylesheet());
};

/**
 * Trim `str`.
 */

function trim(str) {
  return str ? str.replace(/^\s+|\s+$/g, '') : '';
}

/**
 * Adds non-enumerable parent node reference to each node.
 */

function addParent(obj, parent) {
  var isNode = obj && typeof obj.type === 'string';
  var childParent = isNode ? obj : parent;

  for (var k in obj) {
    var value = obj[k];
    if (Array.isArray(value)) {
      value.forEach(function(v) { addParent(v, childParent); });
    } else if (value && typeof value === 'object') {
      addParent(value, childParent);
    }
  }

  if (isNode) {
    Object.defineProperty(obj, 'parent', {
      configurable: true,
      writable: true,
      enumerable: false,
      value: parent || null
    });
  }

  return obj;
}
<commit_msg>Use GNU-style error message format.<commit_after>// http://www.w3.org/TR/CSS21/grammar.html
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g

module.exports = function(css, options){
  options = options || {};

  /**
   * Positional.
   */

  var lineno = 1;
  var column = 1;

  /**
   * Update lineno and column based on `str`.
   */

  function updatePosition(str) {
    var lines = str.match(/\n/g);
    if (lines) lineno += lines.length;
    var i = str.lastIndexOf('\n');
    column = ~i ? str.length - i : column + str.length;
  }

  /**
   * Mark position and patch `node.position`.
   */

  function position() {
    var start = { line: lineno, column: column };
    return function(node){
      node.position = new Position(start);
      whitespace();
      return node;
    };
  }

  /**
   * Store position information for a node
   */

  function Position(start) {
    this.start = start;
    this.end = { line: lineno, column: column };
    this.source = options.source;
  }

  /**
   * Non-enumerable source string
   */

  Position.prototype.content = css;

  /**
   * Error `msg`.
   */

  function error(msg) {
    if (options.silent === true) {
      return false;
    }

    var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg);
    err.filename = options.source;
    err.line = lineno;
    err.column = column;
    err.source = css;
    throw err;
  }

  /**
   * Parse stylesheet.
   */

  function stylesheet() {
    return {
      type: 'stylesheet',
      stylesheet: {
        rules: rules()
      }
    };
  }

  /**
   * Opening brace.
   */

  function open() {
    return match(/^{\s*/);
  }

  /**
   * Closing brace.
   */

  function close() {
    return match(/^}/);
  }

  /**
   * Parse ruleset.
   */

  function rules() {
    var node;
    var rules = [];
    whitespace();
    comments(rules);
    while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
      if (node !== false) {
        rules.push(node);
        comments(rules);
      }
    }
    return rules;
  }

  /**
   * Match `re` and return captures.
   */

  function match(re) {
    var m = re.exec(css);
    if (!m) return;
    var str = m[0];
    updatePosition(str);
    css = css.slice(str.length);
    return m;
  }

  /**
   * Parse whitespace.
   */

  function whitespace() {
    match(/^\s*/);
  }

  /**
   * Parse comments;
   */

  function comments(rules) {
    var c;
    rules = rules || [];
    while (c = comment()) {
      if (c !== false) {
        rules.push(c);
      }
    }
    return rules;
  }

  /**
   * Parse comment.
   */

  function comment() {
    var pos = position();
    if ('/' != css.charAt(0) || '*' != css.charAt(1)) return;

    var i = 2;
    while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i;
    i += 2;

    if ("" === css.charAt(i-1)) {
      return error('End of comment missing');
    }

    var str = css.slice(2, i - 2);
    column += 2;
    updatePosition(str);
    css = css.slice(i);
    column += 2;

    return pos({
      type: 'comment',
      comment: str
    });
  }

  /**
   * Parse selector.
   */

  function selector() {
    var m = match(/^([^{]+)/);
    if (!m) return;
    /* @fix Remove all comments from selectors
     * http://ostermiller.org/findcomment.html */
    return trim(m[0])
      .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
      .replace(/(?:"[^"]*"|'[^']*')/g, function(m) {
        return m.replace(/,/g, '\u200C');
      })
      .split(/\s*(?![^(]*\)),\s*/)
      .map(function(s) {
        return s.replace(/\u200C/g, ',');
      });
  }

  /**
   * Parse declaration.
   */

  function declaration() {
    var pos = position();

    // prop
    var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
    if (!prop) return;
    prop = trim(prop[0]);

    // :
    if (!match(/^:\s*/)) return error("property missing ':'");

    // val
    var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);

    var ret = pos({
      type: 'declaration',
      property: prop.replace(commentre, ''),
      value: val ? trim(val[0]).replace(commentre, '') : ''
    });

    // ;
    match(/^[;\s]*/);

    return ret;
  }

  /**
   * Parse declarations.
   */

  function declarations() {
    var decls = [];

    if (!open()) return error("missing '{'");
    comments(decls);

    // declarations
    var decl;
    while (decl = declaration()) {
      if (decl !== false) {
        decls.push(decl);
        comments(decls);
      }
    }

    if (!close()) return error("missing '}'");
    return decls;
  }

  /**
   * Parse keyframe.
   */

  function keyframe() {
    var m;
    var vals = [];
    var pos = position();

    while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
      vals.push(m[1]);
      match(/^,\s*/);
    }

    if (!vals.length) return;

    return pos({
      type: 'keyframe',
      values: vals,
      declarations: declarations()
    });
  }

  /**
   * Parse keyframes.
   */

  function atkeyframes() {
    var pos = position();
    var m = match(/^@([-\w]+)?keyframes */);

    if (!m) return;
    var vendor = m[1];

    // identifier
    var m = match(/^([-\w]+)\s*/);
    if (!m) return error("@keyframes missing name");
    var name = m[1];

    if (!open()) return error("@keyframes missing '{'");

    var frame;
    var frames = comments();
    while (frame = keyframe()) {
      frames.push(frame);
      frames = frames.concat(comments());
    }

    if (!close()) return error("@keyframes missing '}'");

    return pos({
      type: 'keyframes',
      name: name,
      vendor: vendor,
      keyframes: frames
    });
  }

  /**
   * Parse supports.
   */

  function atsupports() {
    var pos = position();
    var m = match(/^@supports *([^{]+)/);

    if (!m) return;
    var supports = trim(m[1]);

    if (!open()) return error("@supports missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@supports missing '}'");

    return pos({
      type: 'supports',
      supports: supports,
      rules: style
    });
  }

  /**
   * Parse host.
   */

  function athost() {
    var pos = position();
    var m = match(/^@host */);

    if (!m) return;

    if (!open()) return error("@host missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@host missing '}'");

    return pos({
      type: 'host',
      rules: style
    });
  }

  /**
   * Parse media.
   */

  function atmedia() {
    var pos = position();
    var m = match(/^@media *([^{]+)/);

    if (!m) return;
    var media = trim(m[1]);

    if (!open()) return error("@media missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@media missing '}'");

    return pos({
      type: 'media',
      media: media,
      rules: style
    });
  }

  /**
   * Parse custom-media.
   */

  function atcustommedia() {
    var pos = position();
    var m = match(/^@custom-media (--[^\s]+) *([^{;]+);/);
    if (!m) return;

    return pos({
      type: 'custom-media',
      name: trim(m[1]),
      media: trim(m[2])
    });
  }

  /**
   * Parse paged media.
   */

  function atpage() {
    var pos = position();
    var m = match(/^@page */);
    if (!m) return;

    var sel = selector() || [];

    if (!open()) return error("@page missing '{'");
    var decls = comments();

    // declarations
    var decl;
    while (decl = declaration()) {
      decls.push(decl);
      decls = decls.concat(comments());
    }

    if (!close()) return error("@page missing '}'");

    return pos({
      type: 'page',
      selectors: sel,
      declarations: decls
    });
  }

  /**
   * Parse document.
   */

  function atdocument() {
    var pos = position();
    var m = match(/^@([-\w]+)?document *([^{]+)/);
    if (!m) return;

    var vendor = trim(m[1]);
    var doc = trim(m[2]);

    if (!open()) return error("@document missing '{'");

    var style = comments().concat(rules());

    if (!close()) return error("@document missing '}'");

    return pos({
      type: 'document',
      document: doc,
      vendor: vendor,
      rules: style
    });
  }

  /**
   * Parse font-face.
   */

  function atfontface() {
    var pos = position();
    var m = match(/^@font-face */);
    if (!m) return;

    if (!open()) return error("@font-face missing '{'");
    var decls = comments();

    // declarations
    var decl;
    while (decl = declaration()) {
      decls.push(decl);
      decls = decls.concat(comments());
    }

    if (!close()) return error("@font-face missing '}'");

    return pos({
      type: 'font-face',
      declarations: decls
    });
  }

  /**
   * Parse import
   */

  var atimport = _compileAtrule('import');

  /**
   * Parse charset
   */

  var atcharset = _compileAtrule('charset');

  /**
   * Parse namespace
   */

  var atnamespace = _compileAtrule('namespace');

  /**
   * Parse non-block at-rules
   */

  function _compileAtrule(name) {
    var re = new RegExp('^@' + name + ' *([^;\\n]+);');
    return function() {
      var pos = position();
      var m = match(re);
      if (!m) return;
      var ret = { type: name };
      ret[name] = m[1].trim();
      return pos(ret);
    }
  }

  /**
   * Parse at rule.
   */

  function atrule() {
    if (css[0] != '@') return;

    return atkeyframes()
      || atmedia()
      || atcustommedia()
      || atsupports()
      || atimport()
      || atcharset()
      || atnamespace()
      || atdocument()
      || atpage()
      || athost()
      || atfontface();
  }

  /**
   * Parse rule.
   */

  function rule() {
    var pos = position();
    var sel = selector();

    if (!sel) return error('selector missing');
    comments();

    return pos({
      type: 'rule',
      selectors: sel,
      declarations: declarations()
    });
  }

  return addParent(stylesheet());
};

/**
 * Trim `str`.
 */

function trim(str) {
  return str ? str.replace(/^\s+|\s+$/g, '') : '';
}

/**
 * Adds non-enumerable parent node reference to each node.
 */

function addParent(obj, parent) {
  var isNode = obj && typeof obj.type === 'string';
  var childParent = isNode ? obj : parent;

  for (var k in obj) {
    var value = obj[k];
    if (Array.isArray(value)) {
      value.forEach(function(v) { addParent(v, childParent); });
    } else if (value && typeof value === 'object') {
      addParent(value, childParent);
    }
  }

  if (isNode) {
    Object.defineProperty(obj, 'parent', {
      configurable: true,
      writable: true,
      enumerable: false,
      value: parent || null
    });
  }

  return obj;
}
<|endoftext|>


<commit_before>var _ = require('lodash');

'use strict';

function DefaultPartitioner() {
    this.getPartition = function (partitions) {
        if (partitions && _.isArray(partitions) && partitions.length > 0) {
            return partitions[0];
        } else {
            return 0;
        }
    }
}

function RandomPartitioner() {
    this.getPartition = function (partitions) {
        return partitions[Math.floor(Math.random() * partitions.length)];
    }
}

function KeyedPartitioner() {
    // Taken from oid package (Dan Bornstein)
    // Copyright The Obvious Corporation.
    function hashCode(string) {
        var hash = 0;
        var length = string.length;

        for (var i = 0; i < length; i++) {
            hash = ((hash * 31) + string.charCodeAt(i)) & 0x7fffffff;
        }

        return (hash === 0) ? 1 : hash;
    }

    this.getPartition = function (partitions, key) {
        key = key || ''

        var index = hashCode(key) % partitions.length;
        return partitions[index];
    }
}

exports.DefaultPartitioner = DefaultPartitioner;
exports.RandomPartitioner = RandomPartitioner;
exports.KeyedPartitioner = KeyedPartitioner;<commit_msg>Refactor Partitioner class: add class hierarchy.<commit_after>var util = require('util');
var _ = require('lodash');

'use strict';

var Partitioner = function () {
}

var DefaultPartitioner = function () {
}
util.inherits(DefaultPartitioner, Partitioner);

DefaultPartitioner.prototype.getPartition = function (partitions) {
    if (partitions && _.isArray(partitions) && partitions.length > 0) {
        return partitions[0];
    } else {
        return 0;
    }
}

var RandomPartitioner = function () {
}
util.inherits(RandomPartitioner, Partitioner);

RandomPartitioner.prototype.getPartition = function (partitions) {
    return partitions[Math.floor(Math.random() * partitions.length)];
}

var KeyedPartitioner = function () {
}
util.inherits(KeyedPartitioner, Partitioner);

// Taken from oid package (Dan Bornstein)
// Copyright The Obvious Corporation.
KeyedPartitioner.prototype.hashCode = function(string) {
    var hash = 0;
    var length = string.length;

    for (var i = 0; i < length; i++) {
        hash = ((hash * 31) + string.charCodeAt(i)) & 0x7fffffff;
    }

    return (hash === 0) ? 1 : hash;
}

KeyedPartitioner.prototype.getPartition = function (partitions, key) {
    key = key || ''

    var index = this.hashCode(key) % partitions.length;
    return partitions[index];
}

exports.DefaultPartitioner = DefaultPartitioner;
exports.RandomPartitioner = RandomPartitioner;
exports.KeyedPartitioner = KeyedPartitioner;<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash')
  , moment = require('moment')
  , times = require('../times')
  , utils = require('../utils')();

function init() {

  var iob = {
    name: 'iob'
    , label: 'Insulin-on-Board'
    , pluginType: 'pill-major'
  };

  iob.RECENCY_THRESHOLD = times.mins(30).msecs;

  iob.setProperties = function setProperties(sbx) {
    sbx.offerProperty('iob', function setIOB ( ) {
      return iob.calcTotal(sbx.data.treatments, sbx.data.devicestatus, sbx.data.profile, sbx.time);
    });
  };

  iob.calcTotal = function calcTotal(treatments, devicestatus, profile, time, spec_profile) {
    if (time === undefined) {
      time = Date.now();
    }

    var result = iob.lastIOBDeviceStatus(devicestatus, time);

    var treatmentResult = (treatments !== undefined && treatments.length) ? iob.fromTreatments(treatments, profile, time, spec_profile) : {};

    if (_.isEmpty(result)) {
      result = treatmentResult;
    } else if (treatmentResult.iob) {
      result.treatmentIob = treatmentResult.iob;
    }

    return addDisplay(result);
  };

  function addDisplay(iob) {
    if (_.isEmpty(iob) || iob.iob === undefined) {
      return {};
    }
    var display = utils.toFixed(iob.iob);
    return _.merge(iob, {
      display: display
      , displayLine: 'IOB: ' + display + 'U'
    });
  }

  iob.lastIOBDeviceStatus = function lastIOBDeviceStatus(devicestatus, time) {
    if (time && time.getTime) {
      time = time.getTime();
    }
    var futureMills = time + times.mins(5).msecs; //allow for clocks to be a little off
    var recentMills = time - iob.RECENCY_THRESHOLD;
    return  _.chain(devicestatus)
      .map(iob.fromDeviceStatus)
      .reject(_.isEmpty)
      .filter(function (iobStatus) {
        return iobStatus.mills <= futureMills && iobStatus.mills >= recentMills;
      })
      .sortBy('mills')
      .last( )
      .value( );
  };

  iob.fromDeviceStatus = function fromDeviceStatus(devicestatusEntry) {
    var iobObj;
    if (_.get(devicestatusEntry, 'openaps.iob') !== undefined) {

      //hacks to support AMA iob array with time fields instead of timestamp fields
      iobObj = _.isArray(devicestatusEntry.openaps.iob) ? devicestatusEntry.openaps.iob[0] : devicestatusEntry.openaps.iob;

      // array could still be empty, handle as null
      if (_.isEmpty(iobObj)) {
        return {};
      }

      if (iobObj.time) {
        iobObj.timestamp = iobObj.time;
      }

      return {
        iob: iobObj.iob
        , basaliob: iobObj.basaliob
        , activity: iobObj.activity
        , source: 'OpenAPS'
        , device: devicestatusEntry.device
        , mills: moment(iobObj.timestamp).valueOf( )
      };
    } else if (_.get(devicestatusEntry, 'loop.iob') !== undefined) {
      iobObj = devicestatusEntry.loop.iob.iob;
      return {
        iob: iobObj.iob
        , source: 'Loop'
        , device: devicestatusEntry.device
        , mills: moment(iobObj.timestamp).valueOf( )
      };
    } else if (_.get(devicestatusEntry, 'pump.iob') !== undefined) {
      iobObj = devicestatusEntry.pump.iob.iob;
      iobObj = iobObj !== undefined ? iobObj : devicestatusEntry.pump.iob.bolusiob;
      return {
        iob: iobObj
        , source: devicestatusEntry.connect !== undefined ? 'MM Connect' : undefined
        , device: devicestatusEntry.device
        , mills: devicestatusEntry.mills
      };
    } else {
      return {};
    }
  };

  iob.fromTreatments = function fromTreatments(treatments, profile, time, spec_profile) {
    var totalIOB = 0
      , totalActivity = 0;

    var lastBolus = null;

    _.each(treatments, function eachTreatment(treatment) {
      if (treatment.mills <= time) {
        var tIOB = iob.calcTreatment(treatment, profile, time, spec_profile);
        if (tIOB.iobContrib > 0) {
          lastBolus = treatment;
        }
        if (tIOB && tIOB.iobContrib) { totalIOB += tIOB.iobContrib; }
        // units: BG (mg/dL or mmol/L)
        if (tIOB && tIOB.activityContrib) { totalActivity += tIOB.activityContrib; }
      }
    });

    return {
      iob: totalIOB
      , activity: totalActivity
      , lastBolus: lastBolus
      , source: 'Care Portal'
    };
  };

  iob.calcTreatment = function calcTreatment(treatment, profile, time, spec_profile) {

    var dia = 3
      , sens = 0;

    if (profile !== undefined) {
      dia = profile.getDIA(time, spec_profile) || 3;
      sens = profile.getSensitivity(time, spec_profile);
    }

    var scaleFactor = 3.0 / dia
      , peak = 75
      , result = {
          iobContrib: 0
          , activityContrib: 0
        };

    if (treatment.insulin) {
      var bolusTime = treatment.mills;
      var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60;

      if (minAgo < peak) {
        var x1 = minAgo / 5 + 1;
        result.iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1);
        // units: BG (mg/dL)  = (BG/U) *    U insulin     * scalar
        result.activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo;

      } else if (minAgo < 180) {
        var x2 = (minAgo - 75) / 5;
        result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - 0.054233 * x2 + 0.55556);
        result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak));
      }

    }

    return result;

  };

  iob.updateVisualisation = function updateVisualisation(sbx) {
    var info = [];

    var prop = sbx.properties.iob;

    if (prop.lastBolus) {
      var when = moment(prop.lastBolus.mills).format('lll');
      var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U';
      info.push({ label: 'Last Bolus', value: amount + ' @ ' + when });
    }
    if (prop.basaliob !== undefined) {
      info.push({ label: 'Basal IOB', value: prop.basaliob.toFixed(2) });
    }
    if (prop.source !== undefined) {
      info.push({ label: 'Source', value: prop.source });
    }
    if (prop.device !== undefined) {
      info.push({ label: 'Device', value: prop.device });
    }

    if (prop.treatmentIob !== undefined) {
      info.push({label: '------------', value: ''});
      info.push({ label: 'Treatments IOB', value: prop.treatmentIob.toFixed(2) });
    }

    var value = (prop.display !== undefined ? sbx.roundInsulinForDisplayFormat(prop.display) : '---') + 'U';

    sbx.pluginBase.updatePillText(iob, {
      value: value
      , label: 'IOB'
      , info: info
    });

  };

  return iob;

}

module.exports = init;
<commit_msg>fix bug with loop iob<commit_after>'use strict';

var _ = require('lodash')
  , moment = require('moment')
  , times = require('../times')
  , utils = require('../utils')();

function init() {

  var iob = {
    name: 'iob'
    , label: 'Insulin-on-Board'
    , pluginType: 'pill-major'
  };

  iob.RECENCY_THRESHOLD = times.mins(30).msecs;

  iob.setProperties = function setProperties(sbx) {
    sbx.offerProperty('iob', function setIOB ( ) {
      return iob.calcTotal(sbx.data.treatments, sbx.data.devicestatus, sbx.data.profile, sbx.time);
    });
  };

  iob.calcTotal = function calcTotal(treatments, devicestatus, profile, time, spec_profile) {
    if (time === undefined) {
      time = Date.now();
    }

    var result = iob.lastIOBDeviceStatus(devicestatus, time);

    var treatmentResult = (treatments !== undefined && treatments.length) ? iob.fromTreatments(treatments, profile, time, spec_profile) : {};

    if (_.isEmpty(result)) {
      result = treatmentResult;
    } else if (treatmentResult.iob) {
      result.treatmentIob = treatmentResult.iob;
    }

    return addDisplay(result);
  };

  function addDisplay(iob) {
    if (_.isEmpty(iob) || iob.iob === undefined) {
      return {};
    }
    var display = utils.toFixed(iob.iob);
    return _.merge(iob, {
      display: display
      , displayLine: 'IOB: ' + display + 'U'
    });
  }

  iob.lastIOBDeviceStatus = function lastIOBDeviceStatus(devicestatus, time) {
    if (time && time.getTime) {
      time = time.getTime();
    }
    var futureMills = time + times.mins(5).msecs; //allow for clocks to be a little off
    var recentMills = time - iob.RECENCY_THRESHOLD;
    return  _.chain(devicestatus)
      .map(iob.fromDeviceStatus)
      .reject(_.isEmpty)
      .filter(function (iobStatus) {
        return iobStatus.mills <= futureMills && iobStatus.mills >= recentMills;
      })
      .sortBy('mills')
      .last( )
      .value( );
  };

  iob.fromDeviceStatus = function fromDeviceStatus(devicestatusEntry) {
    var iobObj;
    if (_.get(devicestatusEntry, 'openaps.iob') !== undefined) {

      //hacks to support AMA iob array with time fields instead of timestamp fields
      iobObj = _.isArray(devicestatusEntry.openaps.iob) ? devicestatusEntry.openaps.iob[0] : devicestatusEntry.openaps.iob;

      // array could still be empty, handle as null
      if (_.isEmpty(iobObj)) {
        return {};
      }

      if (iobObj.time) {
        iobObj.timestamp = iobObj.time;
      }

      return {
        iob: iobObj.iob
        , basaliob: iobObj.basaliob
        , activity: iobObj.activity
        , source: 'OpenAPS'
        , device: devicestatusEntry.device
        , mills: moment(iobObj.timestamp).valueOf( )
      };
    } else if (_.get(devicestatusEntry, 'loop.iob') !== undefined) {
      iobObj = devicestatusEntry.loop.iob;
      return {
        iob: iobObj.iob
        , source: 'Loop'
        , device: devicestatusEntry.device
        , mills: moment(iobObj.timestamp).valueOf( )
      };
    } else if (_.get(devicestatusEntry, 'pump.iob') !== undefined) {
      iobObj = devicestatusEntry.pump.iob.iob;
      iobObj = iobObj !== undefined ? iobObj : devicestatusEntry.pump.iob.bolusiob;
      return {
        iob: iobObj
        , source: devicestatusEntry.connect !== undefined ? 'MM Connect' : undefined
        , device: devicestatusEntry.device
        , mills: devicestatusEntry.mills
      };
    } else {
      return {};
    }
  };

  iob.fromTreatments = function fromTreatments(treatments, profile, time, spec_profile) {
    var totalIOB = 0
      , totalActivity = 0;

    var lastBolus = null;

    _.each(treatments, function eachTreatment(treatment) {
      if (treatment.mills <= time) {
        var tIOB = iob.calcTreatment(treatment, profile, time, spec_profile);
        if (tIOB.iobContrib > 0) {
          lastBolus = treatment;
        }
        if (tIOB && tIOB.iobContrib) { totalIOB += tIOB.iobContrib; }
        // units: BG (mg/dL or mmol/L)
        if (tIOB && tIOB.activityContrib) { totalActivity += tIOB.activityContrib; }
      }
    });

    return {
      iob: totalIOB
      , activity: totalActivity
      , lastBolus: lastBolus
      , source: 'Care Portal'
    };
  };

  iob.calcTreatment = function calcTreatment(treatment, profile, time, spec_profile) {

    var dia = 3
      , sens = 0;

    if (profile !== undefined) {
      dia = profile.getDIA(time, spec_profile) || 3;
      sens = profile.getSensitivity(time, spec_profile);
    }

    var scaleFactor = 3.0 / dia
      , peak = 75
      , result = {
          iobContrib: 0
          , activityContrib: 0
        };

    if (treatment.insulin) {
      var bolusTime = treatment.mills;
      var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60;

      if (minAgo < peak) {
        var x1 = minAgo / 5 + 1;
        result.iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1);
        // units: BG (mg/dL)  = (BG/U) *    U insulin     * scalar
        result.activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo;

      } else if (minAgo < 180) {
        var x2 = (minAgo - 75) / 5;
        result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - 0.054233 * x2 + 0.55556);
        result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak));
      }

    }

    return result;

  };

  iob.updateVisualisation = function updateVisualisation(sbx) {
    var info = [];

    var prop = sbx.properties.iob;

    if (prop.lastBolus) {
      var when = moment(prop.lastBolus.mills).format('lll');
      var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U';
      info.push({ label: 'Last Bolus', value: amount + ' @ ' + when });
    }
    if (prop.basaliob !== undefined) {
      info.push({ label: 'Basal IOB', value: prop.basaliob.toFixed(2) });
    }
    if (prop.source !== undefined) {
      info.push({ label: 'Source', value: prop.source });
    }
    if (prop.device !== undefined) {
      info.push({ label: 'Device', value: prop.device });
    }

    if (prop.treatmentIob !== undefined) {
      info.push({label: '------------', value: ''});
      info.push({ label: 'Treatments IOB', value: prop.treatmentIob.toFixed(2) });
    }

    var value = (prop.display !== undefined ? sbx.roundInsulinForDisplayFormat(prop.display) : '---') + 'U';

    sbx.pluginBase.updatePillText(iob, {
      value: value
      , label: 'IOB'
      , info: info
    });

  };

  return iob;

}

module.exports = init;
<|endoftext|>


<commit_before>'use strict';

function src(curPage) {
  var self = this;
  var $    = curPage.$;
  if (!$) { return; }
  if (self.isExternal(curPage.url)) { return; }

  $('img').each(function () {
    var src = $(this).attr('src');
    if (validSrc(src)) {
      self.add(src, curPage.url);
    }
  });
}

function validSrc(src) {
  if (!src) { return false; }
  if (/^(data:)/.test(src)) { return false; }
  return true;
}

module.exports = src;
<commit_msg>updated src<commit_after>'use strict';

function src(curPage) {
  var self = this;
  if (!curPage.$) { return; }
  if (self.isExternal(curPage.url)) { return; }

  curPage.$('img').each(function () {
    var src = $(this).attr('src');
    if (validSrc(src)) {
      self.add(src, curPage.url);
    }
  });
}

function validSrc(src) {
  if (!src) { return false; }
  if (/^(data:)/.test(src)) { return false; }
  return true;
}

module.exports = src;
<|endoftext|>


<commit_before>var URL = require('url');
var queue = require('queue-async');
var CacheDebounce = require('cache-debounce');
var type = require('type-is');
var cookie = require('cookie');
var signature = require('cookie-signature');

module.exports = DomProxy;

function DomProxy(raja, opts) {
	if (!(this instanceof DomProxy)) return new DomProxy(raja, opts);
	this.raja = raja;
	var Dom = this.Dom = opts.dom;

	var ioreg;

	var self = this;

	Dom.use(function(h, req, res) {
		h.page.run(function(client, ns, done) {
			var node = document.getElementById('script');
			if (!node) node = document.createElement('script');
			node.id = 'raja';
			node.setAttribute('client', client);
			node.setAttribute('namespace', ns);
			node.setAttribute('room', document.location.href);
			node.type = "text/javascript";
			node.textContent = "(function(r) {if (r && r.ready) r.ready();})(window.raja);";
			if (!node.parentNode) {
				var tn = document.createTextNode("\n");
				document.head.insertBefore(node, document.head.firstChild);
				document.head.insertBefore(tn, node);
			}
			done();
		}, [opts.client, opts.namespace]);
	}, 'before');

	if (opts.disable) return;
	var listener = function(msg) {
		var url = raja.keyObj(msg.key || msg.url).url;
		var parent = raja.keyObj(msg.parents.slice(-1).pop()).url;
		var h = this;
		if (url == h.view.url && msg.mtime > h.view.mtime) {
			// this is probably going to be rewritten as with msg.parents below
			h.view.mtime = msg.mtime;
			raja.store.invalidate(h.view.url, raja.error);
			return;
		}
		var inst = parent && Dom.pages[parent];
		if (!inst) return;

		msg.parents.forEach(function(key) {
			var url = raja.keyObj(key).url;
			// url is a key but all comparisons should be fine in these cases
			if (url == inst.author.url && msg.mtime > inst.author.mtime && (!inst.author.itime || msg.mtime > inst.author.itime)) {
				inst.author.itime = msg.mtime;
				raja.store.invalidate(inst.author.url, raja.error);
			} else if (url == inst.user.url && msg.mtime > inst.user.mtime && (!inst.user.itime || msg.mtime > inst.user.itime)) {
				inst.user.itime = msg.mtime;
				raja.store.invalidate(inst.user.key, raja.error);
			} else {
				var hasChild = inst.user.resources[key];
				if (!hasChild) return;
				raja.retrieve(key, function(err, child) {
					if (err) return raja.error(err);
					if (!child) return;
					if (child && child.headers && type.is(child.headers['Content-Type'], '*/javascript')) {
						// force reload of file on next page reload
						console.info("Detected change in", url);
						if (!inst.reloads) inst.reloads = {};
						inst.reloads[url] = msg.mtime;
					}
				});
			}
		});
	};

	Dom.Handler.prototype.init = function() {
		this.listener = listener.bind(this);
		raja.on('message', this.listener);
	};

	Dom.Handler.prototype.loadLocal = raja.proxies.local.get.bind(raja.proxies.local);

	Dom.Handler.prototype.loadRemote = function(url, opts, cb) {
		raja.proxies.remote.get(url, opts, function(err, res) {
			cb(err, res && res.data);
		});
	};

	var build = Dom.Handler.prototype.build;
	Dom.Handler.prototype.build = CacheDebounce(function(inst, req, res, cb) {
		res.set('X-Raja', opts.namespace);
		build.call(this, inst, req, res, cb);
	}, function(inst) {
		return inst.user.url;
	});

	Dom.author(domProxyPlugin, 'before');
	Dom.use(domProxyPlugin, 'before');
	Dom.use(domProxyBusyPlugin, 'before');

	function domProxyPlugin(inst) {
		inst.page.on('unload', function() {}); // keep it running !
		inst.page.on('error', raja.error.bind(raja));
		inst.page.on('request', function(req) {
			if (!inst.user.resources) {
				return;
			}
			if (inst.existings && inst.existings[req.uri]) {
				// copy back because some of them won't have response because of the cache
				inst.user.depend(req.uri);
			}
			if (inst.reloads && inst.reloads[req.uri]) {
				req.headers['Cache-Control'] = 'no-cache';
			}
			if (ioreg && ioreg.test(URL.parse(req.uri).pathname)) {
				req.ignore = true;
			}
		});
		inst.page.on('response', function(res) {
			if (res.uri == inst.user.url) return; // ignore view and author
			if (!inst.user.resources) return;
			if (res.status >= 200 && res.status < 400) {
				if (!ioreg) {
					var iopath = raja.io && raja.io.socket && raja.io.socket.io.opts.path;
					if (iopath) ioreg = new RegExp("^" + iopath);
				}
				if (ioreg && ioreg.test(URL.parse(res.uri).pathname)) {
					if (type.is(res.mime, 'javascript') == false) inst.live = true;
				} else {
					var resource = raja.create(res.uri, res);
					inst.user.depend(resource);
					resource.save();

					// this covers the case where a resource finishes loading after idle
					if (inst.user.valid) {
						inst.user.itime = new Date();
						inst.user.valid = false;
						inst.user.save();
					}
				}
			}
		});
	}

	function domProxyBusyPlugin(inst) {
		inst.page.on('busy', function() {
			if (inst.user.itime && inst.user.itime > inst.user.mtime) {
				delete inst.user.itime;
				// update resources to make sure instances loaded on server receive messages
				inst.user.valid = false;
				inst.user.save();
			}
		});
	}

	var _getView = Dom.Handler.prototype.getView;
	Dom.Handler.prototype.getView = function(req, cb) {
		var h = this;
		raja.upsert(h.view, function(err, resource) {
			if (err) return cb(err);
			h.view = resource;
			if (resource.valid) return cb();
			_getView.call(h, req, function(err) {
				if (err) return cb(err);
				h.view.save(cb);
			});
		});
	};

	var _getAuthored = Dom.Handler.prototype.getAuthored;
	Dom.Handler.prototype.getAuthored = function(inst, req, res, cb) {
		var h = this;
		raja.upsert(inst.author, function(err, resource) {
			if (err) return cb(err);
			inst.author = resource;
			if (!resource.headers) resource.headers = {};
			if (!resource.resources) resource.resources = {};
			if (resource.valid) return cb();
			_getAuthored.call(h, inst, req, res, function(err) {
				if (err) return cb(err);
				resource.headers['Content-Type'] = 'text/html';
				resource.depend(h.view);
				resource.save(cb);
			});
		});
	};

	var _getUsed = Dom.Handler.prototype.getUsed;
	Dom.Handler.prototype.getUsed = function(inst, req, res, cb) {
		var h = this;
		inst.user.headers = raja.headers(req);
		raja.upsert(inst.user, function(err, resource) {
			if (err) return cb(err);
			inst.user = resource;
			resource.headers['Content-Type'] = 'text/html';
			if (!resource.resources) resource.resources = {};
			if (inst.page && inst.live && !inst.reloads) {
				inst.page.locked = true;
				inst.output(inst.page, function(err, str) {
					inst.page.locked = false;
					if (str) {
						resource.data = str;
					}
					next(err);
				});
			} else {
				inst.existings = resource.resources;
				resource.resources = {};
				resource.depend(inst.author);
				resource.valid = false;
				if (!inst.opts) inst.opts = {};
				var sessionCookie = stealSession(req);
				if (sessionCookie) {
					inst.opts.cookies = sessionCookie;
				}
				_getUsed.call(h, inst, req, res, next);
			}
			function next(err) {
				if (err) return cb(err);
				if (inst.opts.cookies) delete inst.opts.cookies;
				if (inst.existings) delete inst.existings;
				if (inst.reloads) delete inst.reloads;
				if (!resource.itime || resource.itime <= resource.mtime) {
					resource.valid = true;
					resource.mtime = new Date();
				} else if (!resource.mtime) {
					resource.mtime = new Date();
				}
				updateGrants(resource, function(err) {
					// must be done at the very end, since the key varies on the resources grants
					if (err) return cb(err);
					var rights = req.get('X-Right');
					if (rights != resource.headers['X-Grant']) {
						console.warn('Permissions mismatch while accessing', resource.url, rights, resource.headers);
					}
					resource.save(cb);
				});
			}
		});
	};
}

function updateGrants(resource, cb) {
	var q = queue();
	var store = resource.raja.store;
	for (var key in resource.resources) {
		q.defer(store.get.bind(store), key);
	}
	q.awaitAll(function(err, resources) {
		if (err) return cb(err);
		var grants = mergeGrants({}, resource.headers);
		resources.forEach(function(item) {
			mergeGrants(grants, item.headers);
		});
		grants = Object.keys(grants);
		if (grants.length) {
			mergeHeader(resource.headers, 'Vary', 'X-Grant');
			resource.headers['X-Grant'] = grants.sort().join(',');
		} else {
			// update ?
		}
		cb();
	});
}

function mergeGrants(obj, headers) {
	var vary = headers.Vary;
	if (!vary || vary.split(',').indexOf('X-Grant') < 0) return obj;
	var header = headers['X-Grant'];
	if (!header) return obj;
	header.split(',').forEach(function(val) {
		obj[val.trim()] = true;
	});
	return obj;
}

function mergeHeader(headers, name, val) {
	var header = headers[name];
	header = header ? header.split(',') : [];
	if (header.indexOf(val) < 0) header.push(val);
	headers[name] = header.join(',');
}

function stealSession(req) {
	var session = req.session;
	var store = req.sessionStore;
	var opts = store.opts;
	// 1) generate a new session
	var decoy = {sessionStore: store};
	store.generate(decoy);
	// 2) give it same data
	for (var key in session) {
		if (decoy.session[key] != null || key == 'passport') continue;
		decoy.session[key] = session[key];
	}
	decoy.session.save();
	// 3) set that session cookie into inst.opts.cookies
	var cookieVal = decoy.sessionID;
	if (opts.secret) {
		cookieVal = 's:' + signature.sign(cookieVal, opts.secret);
	}
	var name = opts.name || opts.key || 'connect.sid';
	var cookieOpts = session.cookie.data;
	// webkitgtk must be able to set cookie using document.cookie
	if (cookieOpts.httpOnly) delete cookieOpts.httpOnly;
	return cookie.serialize(name, cookieVal, cookieOpts);
}

<commit_msg>More details on rights errors<commit_after>var URL = require('url');
var queue = require('queue-async');
var CacheDebounce = require('cache-debounce');
var type = require('type-is');
var cookie = require('cookie');
var signature = require('cookie-signature');

module.exports = DomProxy;

function DomProxy(raja, opts) {
	if (!(this instanceof DomProxy)) return new DomProxy(raja, opts);
	this.raja = raja;
	var Dom = this.Dom = opts.dom;

	var ioreg;

	var self = this;

	Dom.use(function(h, req, res) {
		h.page.run(function(client, ns, done) {
			var node = document.getElementById('script');
			if (!node) node = document.createElement('script');
			node.id = 'raja';
			node.setAttribute('client', client);
			node.setAttribute('namespace', ns);
			node.setAttribute('room', document.location.href);
			node.type = "text/javascript";
			node.textContent = "(function(r) {if (r && r.ready) r.ready();})(window.raja);";
			if (!node.parentNode) {
				var tn = document.createTextNode("\n");
				document.head.insertBefore(node, document.head.firstChild);
				document.head.insertBefore(tn, node);
			}
			done();
		}, [opts.client, opts.namespace]);
	}, 'before');

	if (opts.disable) return;
	var listener = function(msg) {
		var url = raja.keyObj(msg.key || msg.url).url;
		var parent = raja.keyObj(msg.parents.slice(-1).pop()).url;
		var h = this;
		if (url == h.view.url && msg.mtime > h.view.mtime) {
			// this is probably going to be rewritten as with msg.parents below
			h.view.mtime = msg.mtime;
			raja.store.invalidate(h.view.url, raja.error);
			return;
		}
		var inst = parent && Dom.pages[parent];
		if (!inst) return;

		msg.parents.forEach(function(key) {
			var url = raja.keyObj(key).url;
			// url is a key but all comparisons should be fine in these cases
			if (url == inst.author.url && msg.mtime > inst.author.mtime && (!inst.author.itime || msg.mtime > inst.author.itime)) {
				inst.author.itime = msg.mtime;
				raja.store.invalidate(inst.author.url, raja.error);
			} else if (url == inst.user.url && msg.mtime > inst.user.mtime && (!inst.user.itime || msg.mtime > inst.user.itime)) {
				inst.user.itime = msg.mtime;
				raja.store.invalidate(inst.user.key, raja.error);
			} else {
				var hasChild = inst.user.resources[key];
				if (!hasChild) return;
				raja.retrieve(key, function(err, child) {
					if (err) return raja.error(err);
					if (!child) return;
					if (child && child.headers && type.is(child.headers['Content-Type'], '*/javascript')) {
						// force reload of file on next page reload
						console.info("Detected change in", url);
						if (!inst.reloads) inst.reloads = {};
						inst.reloads[url] = msg.mtime;
					}
				});
			}
		});
	};

	Dom.Handler.prototype.init = function() {
		this.listener = listener.bind(this);
		raja.on('message', this.listener);
	};

	Dom.Handler.prototype.loadLocal = raja.proxies.local.get.bind(raja.proxies.local);

	Dom.Handler.prototype.loadRemote = function(url, opts, cb) {
		raja.proxies.remote.get(url, opts, function(err, res) {
			cb(err, res && res.data);
		});
	};

	var build = Dom.Handler.prototype.build;
	Dom.Handler.prototype.build = CacheDebounce(function(inst, req, res, cb) {
		res.set('X-Raja', opts.namespace);
		build.call(this, inst, req, res, cb);
	}, function(inst) {
		return inst.user.url;
	});

	Dom.author(domProxyPlugin, 'before');
	Dom.use(domProxyPlugin, 'before');
	Dom.use(domProxyBusyPlugin, 'before');

	function domProxyPlugin(inst) {
		inst.page.on('unload', function() {}); // keep it running !
		inst.page.on('error', raja.error.bind(raja));
		inst.page.on('request', function(req) {
			if (!inst.user.resources) {
				return;
			}
			if (inst.existings && inst.existings[req.uri]) {
				// copy back because some of them won't have response because of the cache
				inst.user.depend(req.uri);
			}
			if (inst.reloads && inst.reloads[req.uri]) {
				req.headers['Cache-Control'] = 'no-cache';
			}
			if (ioreg && ioreg.test(URL.parse(req.uri).pathname)) {
				req.ignore = true;
			}
		});
		inst.page.on('response', function(res) {
			if (res.uri == inst.user.url) return; // ignore view and author
			if (!inst.user.resources) return;
			if (res.status >= 200 && res.status < 400) {
				if (!ioreg) {
					var iopath = raja.io && raja.io.socket && raja.io.socket.io.opts.path;
					if (iopath) ioreg = new RegExp("^" + iopath);
				}
				if (ioreg && ioreg.test(URL.parse(res.uri).pathname)) {
					if (type.is(res.mime, 'javascript') == false) inst.live = true;
				} else {
					var resource = raja.create(res.uri, res);
					inst.user.depend(resource);
					resource.save();

					// this covers the case where a resource finishes loading after idle
					if (inst.user.valid) {
						inst.user.itime = new Date();
						inst.user.valid = false;
						inst.user.save();
					}
				}
			}
		});
	}

	function domProxyBusyPlugin(inst) {
		inst.page.on('busy', function() {
			if (inst.user.itime && inst.user.itime > inst.user.mtime) {
				delete inst.user.itime;
				// update resources to make sure instances loaded on server receive messages
				inst.user.valid = false;
				inst.user.save();
			}
		});
	}

	var _getView = Dom.Handler.prototype.getView;
	Dom.Handler.prototype.getView = function(req, cb) {
		var h = this;
		raja.upsert(h.view, function(err, resource) {
			if (err) return cb(err);
			h.view = resource;
			if (resource.valid) return cb();
			_getView.call(h, req, function(err) {
				if (err) return cb(err);
				h.view.save(cb);
			});
		});
	};

	var _getAuthored = Dom.Handler.prototype.getAuthored;
	Dom.Handler.prototype.getAuthored = function(inst, req, res, cb) {
		var h = this;
		raja.upsert(inst.author, function(err, resource) {
			if (err) return cb(err);
			inst.author = resource;
			if (!resource.headers) resource.headers = {};
			if (!resource.resources) resource.resources = {};
			if (resource.valid) return cb();
			_getAuthored.call(h, inst, req, res, function(err) {
				if (err) return cb(err);
				resource.headers['Content-Type'] = 'text/html';
				resource.depend(h.view);
				resource.save(cb);
			});
		});
	};

	var _getUsed = Dom.Handler.prototype.getUsed;
	Dom.Handler.prototype.getUsed = function(inst, req, res, cb) {
		var h = this;
		inst.user.headers = raja.headers(req);
		raja.upsert(inst.user, function(err, resource) {
			if (err) return cb(err);
			inst.user = resource;
			resource.headers['Content-Type'] = 'text/html';
			if (!resource.resources) resource.resources = {};
			if (inst.page && inst.live && !inst.reloads) {
				inst.page.locked = true;
				inst.output(inst.page, function(err, str) {
					inst.page.locked = false;
					if (str) {
						resource.data = str;
					}
					next(err);
				});
			} else {
				inst.existings = resource.resources;
				resource.resources = {};
				resource.depend(inst.author);
				resource.valid = false;
				if (!inst.opts) inst.opts = {};
				var sessionCookie = stealSession(req);
				if (sessionCookie) {
					inst.opts.cookies = sessionCookie;
				}
				_getUsed.call(h, inst, req, res, next);
			}
			function next(err) {
				if (err) return cb(err);
				if (inst.opts.cookies) delete inst.opts.cookies;
				if (inst.existings) delete inst.existings;
				if (inst.reloads) delete inst.reloads;
				if (!resource.itime || resource.itime <= resource.mtime) {
					resource.valid = true;
					resource.mtime = new Date();
				} else if (!resource.mtime) {
					resource.mtime = new Date();
				}
				updateGrants(resource, function(err) {
					// must be done at the very end, since the key varies on the resources grants
					if (err) return cb(err);
					if (req.headers['X-Right'] != resource.headers['X-Grant']) {
						console.warn(
							'Permissions mismatch while accessing',
							resource.url,
							'X-Right', req.headers['X-Right'],
							'X-Grant', resource.headers['X-Grant']
						);
					}
					resource.save(cb);
				});
			}
		});
	};
}

function updateGrants(resource, cb) {
	var q = queue();
	var store = resource.raja.store;
	for (var key in resource.resources) {
		q.defer(store.get.bind(store), key);
	}
	q.awaitAll(function(err, resources) {
		if (err) return cb(err);
		var grants = mergeGrants({}, resource.headers);
		resources.forEach(function(item) {
			mergeGrants(grants, item.headers);
		});
		grants = Object.keys(grants);
		if (grants.length) {
			mergeHeader(resource.headers, 'Vary', 'X-Grant');
			resource.headers['X-Grant'] = grants.sort().join(',');
		} else {
			// update ?
		}
		cb();
	});
}

function mergeGrants(obj, headers) {
	var vary = headers.Vary;
	if (!vary || vary.split(',').indexOf('X-Grant') < 0) return obj;
	var header = headers['X-Grant'];
	if (!header) return obj;
	header.split(',').forEach(function(val) {
		obj[val.trim()] = true;
	});
	return obj;
}

function mergeHeader(headers, name, val) {
	var header = headers[name];
	header = header ? header.split(',') : [];
	if (header.indexOf(val) < 0) header.push(val);
	headers[name] = header.join(',');
}

function stealSession(req) {
	var session = req.session;
	var store = req.sessionStore;
	var opts = store.opts;
	// 1) generate a new session
	var decoy = {sessionStore: store};
	store.generate(decoy);
	// 2) give it same data
	for (var key in session) {
		if (decoy.session[key] != null || key == 'passport') continue;
		decoy.session[key] = session[key];
	}
	decoy.session.save();
	// 3) set that session cookie into inst.opts.cookies
	var cookieVal = decoy.sessionID;
	if (opts.secret) {
		cookieVal = 's:' + signature.sign(cookieVal, opts.secret);
	}
	var name = opts.name || opts.key || 'connect.sid';
	var cookieOpts = session.cookie.data;
	// webkitgtk must be able to set cookie using document.cookie
	if (cookieOpts.httpOnly) delete cookieOpts.httpOnly;
	return cookie.serialize(name, cookieVal, cookieOpts);
}

<|endoftext|>