<commit_before>/*
 * jitsu.js: Top-level include for the jitsu module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var jitsu = exports;

var path = require('path');

// Failure HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var failCodes = jitsu.failCodes = {
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Not Authorized',
  404: 'Item not found',
  409: 'Conflict',
  500: 'Internal Server Error'
};

// Success HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var successCodes = jitsu.successCodes = {
  200: 'OK',
  201: 'Created'
};

jitsu.skipAuth          = false;
jitsu.started           = false;
jitsu.utils             = require('./jitsu/utils');
jitsu.package           = require('./jitsu/package');
jitsu.log               = require('cliff');
jitsu.logFile           = new (require('./jitsu/utils/logfile').LogFile)(path.join(process.env.HOME, '.jitsulog'));
jitsu.api               = {};
jitsu.api.Client        = require('nodejitsu-api').Client;
jitsu.api.Apps          = require('nodejitsu-api').Apps;
jitsu.api.Databases     = require('nodejitsu-api').Databases;
jitsu.api.Logs          = require('nodejitsu-api').Logs;
jitsu.api.Snapshots     = require('nodejitsu-api').Snapshots;
jitsu.api.Users         = require('nodejitsu-api').Users;
jitsu.prompt            = require('prompt');
jitsu.prompt.properties = require('./jitsu/properties').properties;
jitsu.prompt.override   = require('optimist').argv;
jitsu.config            = require('./jitsu/config');
jitsu.commands          = require('./jitsu/commands');

//
// Alias the appropriate commands for simplier CLI usage
//
jitsu.commands.alias('create', { resource: 'apps',   command: 'create' });
jitsu.commands.alias('deploy', { resource: 'apps',   command: 'deploy' });
jitsu.commands.alias('list',   { resource: 'apps',   command: 'list' });
jitsu.commands.alias('conf',   { resource: 'config', command: 'list' });
jitsu.commands.alias('login',  { resource: 'users',  command: 'login' });
jitsu.commands.alias('logout', { resource: 'users',  command: 'logout' });
jitsu.commands.alias('signup', { resource: 'users',  command: 'create' });
jitsu.commands.alias('whoami', { resource: 'users',  command: 'whoami' });

var eyes = require('eyes'),
    winston = require('winston'),
    colors = require('colors');

//
// Setup `jitsu` to use `pkginfo` to expose version
//
require('pkginfo')(module, 'version');

//
// ### function start (command, callback)
// #### @command {string} Command to execute once started
// #### @callback {function} Continuation to pass control to when complete.
// Starts the jitsu CLI and runs the specified command.
//
jitsu.start = function (argv, callback) {
  var command = argv._;

  //
  // Special -v command for showing current version without winston formatting
  //
  if (argv.version || argv.v) {
    console.log('v' + jitsu.version);
    process.exit(0);
  }

  //
  // Check for --no-colors/--colors option, without hitting the config file
  // yet
  //
  (typeof argv.colors == "undefined" || argv.colors) || (colors.mode = "none");

  // Setup the initial prompt but don't leave it open
  jitsu.prompt.start().pause();

  //
  // Default to the `help` command.
  //
  command[0] || (command[0] = 'help');

  jitsu.utils.checkVersion(function (err) {
    if (err) return callback();
    jitsu.config.load(function (err) {
      if (err) {
        jitsu.welcome();
        callback(err);
        return jitsu.showError.apply(null, [command[0]].concat(arguments));
      }

      //
      // --no-colors option turns off output coloring, and so does setting
      // colors: false in ~/.jitsuconf (see
      // https://github.com/nodejitsu/jitsu/issues/101 )
      //
      jitsu.config.get('colors') || (colors.mode = "none");

      jitsu.welcome();

      var username = jitsu.config.get('username');
      if (!username && jitsu.commands.requiresAuth(command[0])) {
        return jitsu.setupUser(function (err) {
          if (err) {
            callback(err);
            return jitsu.showError.apply(jitsu, [command[0]].concat(arguments));
          }

          var username = jitsu.config.get('username');
          winston.info('Successfully configured user ' + username.magenta);
          return command.length > 0 ? jitsu.exec(command, callback) : callback();
        });
      }

      return jitsu.exec(command, callback)
    });
  });
};

//
// ### function welcome ()
// Print welcome message.
//
jitsu.welcome = function () {
  winston.info('Welcome to ' + 'Nodejitsu'.grey);
  winston.info('It worked if it ends with ' + 'Nodejitsu'.grey + ' ok'.green.bold);
};

//
// ### function exec (command, callback)
// #### @command {string} Command to execute
// #### @callback {function} Continuation to pass control to when complete.
// Runs the specified command in the jitsu CLI.
//
jitsu.exec = function (command, callback) {
  function execCommand (err) {
    if (err) {
      return callback(err);
    }

    //
    // Remark: This is a temporary fix for aliasing init=>install,
    // was having a few issues with the alias command on the install resource
    //
    if (command[0] === 'init'){
      command[0] = 'install';
    }

    // Alias db to databases
    if (command[0] === 'db'){
      command[0] = 'databases';
    }

    // Allow `jitsu logs` as a shortcut for `jitsu logs app`
    if (command[0] === 'logs' && command.length === 1) {
      command[1] = 'app';
    }

    winston.info('Executing command ' + command.join(' ').magenta);
    jitsu.commands.run(command, function (err, shallow) {
      if (err) {
        callback(err);
        return jitsu.showError(command.join(' '), err, shallow);
      }

      //
      // TODO (indexzero): Something here
      //
      callback();
    });
  }

  return !jitsu.started ? jitsu.setup(execCommand) : execCommand();
};

//
// ### function setup (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Sets up the instances of the Resource clients for jitsu.
// there is no io here, yet this function is ASYNC.
//
jitsu.setup = function (callback) {
  if (jitsu.started === true) {
    return callback();
  }

  jitsu.users = new jitsu.api.Users(jitsu.config);
  jitsu.apps = new jitsu.api.Apps(jitsu.config);
  jitsu.snapshots = new jitsu.api.Snapshots(jitsu.config);
  jitsu.databases = new jitsu.api.Databases(jitsu.config);
  jitsu.logs = new jitsu.api.Logs(jitsu.config);
  jitsu.started = true;

  callback();
};

//
// ### function auth (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Attempts to authenicate the user identified in `jitsu.config.get('username'|'password')`
//
jitsu.auth = function (callback) {
  if (jitsu.skipAuth) {
    return callback();
  }

  var username = jitsu.config.get('username');
  winston.silly('Attempting to authenticate as ' + username.magenta);
  jitsu.users.auth(function (err, success) {
    if (err || !success) {
      winston.error('Unable to Authenticate as ' + username.magenta);
      winston.error(err.message);
      return callback(err);
    }

    jitsu.skipAuth = true;
    winston.info('Authenticated as ' + username.magenta);
    return callback();
  })
}

//
// ### function setupUser (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk.
//
jitsu.setupUser = function (callback) {
  winston.warn('You will need to login to continue');
  winston.info('To login, you will need an activated nodejitsu account');
  winston.help('You can create an account using the ' + 'jitsu signup'.magenta + ' command');
  jitsu.setupUserNoWarn(callback);
};

//
// ### function setupUserNoWarn (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk without a warning
//
jitsu.setupUserNoWarn = function(callback) {
  //
  // Attempt to get the password three times.
  //
  var tries = 0;

  function offerReset (username) {
    jitsu.prompt.get(['reset'], function (err, res) {
      if (err) {
        return callback(err);
      }
      if (/^y[es]+/i.test(res['request password reset'])) {
        return jitsu.commands.run(['users', 'forgot', username], callback);
      }

      callback(new Error('Invalid username / password.'));
    });
  }

  (function setupAuth () {
    jitsu.prompt.get(['username', 'password'], function (err, result) {
      if (err) {
        return callback(err);
      }

      jitsu.config.set('username', result.username);
      jitsu.config.set('password', result.password);
      jitsu.setup(function () {
        jitsu.auth(function (err) {
          //
          // Increment the auth attempts
          //
          tries += 1;

          if (err) {
            if (tries >= 3) {
              winston.error('Three failed login attempts');
              winston.info('Would you like to reset your password?');
              return offerReset(result.username);
            }
            return setupAuth();
          }

          jitsu.config.save(function (err) {
            return err ? callback(err) : callback();
          });
        });
      });
    });
  })();
};

//
// ### function showError (command, err, shallow, skip)
// #### @command {string} Command which has errored.
// #### @err {Error} Error received for the command.
// #### @shallow {boolean} Value indicating if a deep stack should be displayed
// #### @skip {boolean} Value indicating if this error should be forcibly suppressed.
// Displays the `err` to the user for the `command` supplied.
//
jitsu.showError = function (command, err, shallow, skip) {
  if (err.statusCode !== '403' && !skip) {
    winston.error('Error running command ' + command.magenta);

    if (!jitsu.config.get('nolog')) {
      jitsu.logFile.log(err);
    }

    if (err.message) {
      winston.error(err.message);
    }

    if (err.result) {
      if (err.result.error) {
        winston.error(err.result.error);
      }

      if (err.result.result && err.result.result.error) {
        if (err.result.result.error.stderr) {
          winston.warn('Error returned from drone');
          winston.error('');
          err.result.result.error.stderr.split('\n').forEach(function (line) {
            winston.error(line);
          })
        }

        if (err.result.result.error.stack && jitsu.config.get('debug')) {
          winston.warn('Error returned from Haibu');
          winston.error('Message: ' + err.result.result.error.message);
          winston.error('');
          err.result.result.error.stack.split('\n').forEach(function (line) {
            winston.error(line);
          });
        }
      }
      else if (err.result.stack) {
        winston.warn('Error returned from Nodejitsu');
        err.result.stack.split('\n').forEach(function (line) {
          winston.error(line);
        })
      }
    }
    else {
      if (err.stack && !shallow) {
        err.stack.split('\n').forEach(function (trace) {
          winston.error(trace);
        });
      }
    }
  }

  winston.info('Nodejitsu '.grey + 'not ok'.red.bold);
};
<commit_msg>[fix] Improved error reporting for deploy errors.  A refactor of jitsu.showError will follow.<commit_after>/*
 * jitsu.js: Top-level include for the jitsu module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var jitsu = exports;

var path = require('path');

// Failure HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var failCodes = jitsu.failCodes = {
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Not Authorized',
  404: 'Item not found',
  409: 'Conflict',
  500: 'Internal Server Error'
};

// Success HTTP Response codes based
// off of `/lib/broodmother/slave/service.js`
var successCodes = jitsu.successCodes = {
  200: 'OK',
  201: 'Created'
};

jitsu.skipAuth          = false;
jitsu.started           = false;
jitsu.utils             = require('./jitsu/utils');
jitsu.package           = require('./jitsu/package');
jitsu.log               = require('cliff');
jitsu.logFile           = new (require('./jitsu/utils/logfile').LogFile)(path.join(process.env.HOME, '.jitsulog'));
jitsu.api               = {};
jitsu.api.Client        = require('nodejitsu-api').Client;
jitsu.api.Apps          = require('nodejitsu-api').Apps;
jitsu.api.Databases     = require('nodejitsu-api').Databases;
jitsu.api.Logs          = require('nodejitsu-api').Logs;
jitsu.api.Snapshots     = require('nodejitsu-api').Snapshots;
jitsu.api.Users         = require('nodejitsu-api').Users;
jitsu.prompt            = require('prompt');
jitsu.prompt.properties = require('./jitsu/properties').properties;
jitsu.prompt.override   = require('optimist').argv;
jitsu.config            = require('./jitsu/config');
jitsu.commands          = require('./jitsu/commands');

//
// Alias the appropriate commands for simplier CLI usage
//
jitsu.commands.alias('create', { resource: 'apps',   command: 'create' });
jitsu.commands.alias('deploy', { resource: 'apps',   command: 'deploy' });
jitsu.commands.alias('list',   { resource: 'apps',   command: 'list' });
jitsu.commands.alias('conf',   { resource: 'config', command: 'list' });
jitsu.commands.alias('login',  { resource: 'users',  command: 'login' });
jitsu.commands.alias('logout', { resource: 'users',  command: 'logout' });
jitsu.commands.alias('signup', { resource: 'users',  command: 'create' });
jitsu.commands.alias('whoami', { resource: 'users',  command: 'whoami' });

var eyes = require('eyes'),
    winston = require('winston'),
    colors = require('colors');

//
// Setup `jitsu` to use `pkginfo` to expose version
//
require('pkginfo')(module, 'version');

//
// ### function start (command, callback)
// #### @command {string} Command to execute once started
// #### @callback {function} Continuation to pass control to when complete.
// Starts the jitsu CLI and runs the specified command.
//
jitsu.start = function (argv, callback) {
  var command = argv._;

  //
  // Special -v command for showing current version without winston formatting
  //
  if (argv.version || argv.v) {
    console.log('v' + jitsu.version);
    process.exit(0);
  }

  //
  // Check for --no-colors/--colors option, without hitting the config file
  // yet
  //
  (typeof argv.colors == "undefined" || argv.colors) || (colors.mode = "none");

  // Setup the initial prompt but don't leave it open
  jitsu.prompt.start().pause();

  //
  // Default to the `help` command.
  //
  command[0] || (command[0] = 'help');

  jitsu.utils.checkVersion(function (err) {
    if (err) return callback();
    jitsu.config.load(function (err) {
      if (err) {
        jitsu.welcome();
        callback(err);
        return jitsu.showError.apply(null, [command[0]].concat(arguments));
      }

      //
      // --no-colors option turns off output coloring, and so does setting
      // colors: false in ~/.jitsuconf (see
      // https://github.com/nodejitsu/jitsu/issues/101 )
      //
      jitsu.config.get('colors') || (colors.mode = "none");

      jitsu.welcome();

      var username = jitsu.config.get('username');
      if (!username && jitsu.commands.requiresAuth(command[0])) {
        return jitsu.setupUser(function (err) {
          if (err) {
            callback(err);
            return jitsu.showError.apply(jitsu, [command[0]].concat(arguments));
          }

          var username = jitsu.config.get('username');
          winston.info('Successfully configured user ' + username.magenta);
          return command.length > 0 ? jitsu.exec(command, callback) : callback();
        });
      }

      return jitsu.exec(command, callback)
    });
  });
};

//
// ### function welcome ()
// Print welcome message.
//
jitsu.welcome = function () {
  winston.info('Welcome to ' + 'Nodejitsu'.grey);
  winston.info('It worked if it ends with ' + 'Nodejitsu'.grey + ' ok'.green.bold);
};

//
// ### function exec (command, callback)
// #### @command {string} Command to execute
// #### @callback {function} Continuation to pass control to when complete.
// Runs the specified command in the jitsu CLI.
//
jitsu.exec = function (command, callback) {
  function execCommand (err) {
    if (err) {
      return callback(err);
    }

    //
    // Remark: This is a temporary fix for aliasing init=>install,
    // was having a few issues with the alias command on the install resource
    //
    if (command[0] === 'init'){
      command[0] = 'install';
    }

    // Alias db to databases
    if (command[0] === 'db'){
      command[0] = 'databases';
    }

    // Allow `jitsu logs` as a shortcut for `jitsu logs app`
    if (command[0] === 'logs' && command.length === 1) {
      command[1] = 'app';
    }

    winston.info('Executing command ' + command.join(' ').magenta);
    jitsu.commands.run(command, function (err, shallow) {
      if (err) {
        callback(err);
        return jitsu.showError(command.join(' '), err, shallow);
      }

      //
      // TODO (indexzero): Something here
      //
      callback();
    });
  }

  return !jitsu.started ? jitsu.setup(execCommand) : execCommand();
};

//
// ### function setup (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Sets up the instances of the Resource clients for jitsu.
// there is no io here, yet this function is ASYNC.
//
jitsu.setup = function (callback) {
  if (jitsu.started === true) {
    return callback();
  }

  jitsu.users = new jitsu.api.Users(jitsu.config);
  jitsu.apps = new jitsu.api.Apps(jitsu.config);
  jitsu.snapshots = new jitsu.api.Snapshots(jitsu.config);
  jitsu.databases = new jitsu.api.Databases(jitsu.config);
  jitsu.logs = new jitsu.api.Logs(jitsu.config);
  jitsu.started = true;

  callback();
};

//
// ### function auth (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Attempts to authenicate the user identified in `jitsu.config.get('username'|'password')`
//
jitsu.auth = function (callback) {
  if (jitsu.skipAuth) {
    return callback();
  }

  var username = jitsu.config.get('username');
  winston.silly('Attempting to authenticate as ' + username.magenta);
  jitsu.users.auth(function (err, success) {
    if (err || !success) {
      winston.error('Unable to Authenticate as ' + username.magenta);
      winston.error(err.message);
      return callback(err);
    }

    jitsu.skipAuth = true;
    winston.info('Authenticated as ' + username.magenta);
    return callback();
  })
}

//
// ### function setupUser (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk.
//
jitsu.setupUser = function (callback) {
  winston.warn('You will need to login to continue');
  winston.info('To login, you will need an activated nodejitsu account');
  winston.help('You can create an account using the ' + 'jitsu signup'.magenta + ' command');
  jitsu.setupUserNoWarn(callback);
};

//
// ### function setupUserNoWarn (callback)
// #### @callback {function} Continuation to pass control to when complete.
// Prompts the user for their username / password combo, then sets up the jitsu CLI,
// and saves the resulting configuration to disk without a warning
//
jitsu.setupUserNoWarn = function(callback) {
  //
  // Attempt to get the password three times.
  //
  var tries = 0;

  function offerReset (username) {
    jitsu.prompt.get(['reset'], function (err, res) {
      if (err) {
        return callback(err);
      }
      if (/^y[es]+/i.test(res['request password reset'])) {
        return jitsu.commands.run(['users', 'forgot', username], callback);
      }

      callback(new Error('Invalid username / password.'));
    });
  }

  (function setupAuth () {
    jitsu.prompt.get(['username', 'password'], function (err, result) {
      if (err) {
        return callback(err);
      }

      jitsu.config.set('username', result.username);
      jitsu.config.set('password', result.password);
      jitsu.setup(function () {
        jitsu.auth(function (err) {
          //
          // Increment the auth attempts
          //
          tries += 1;

          if (err) {
            if (tries >= 3) {
              winston.error('Three failed login attempts');
              winston.info('Would you like to reset your password?');
              return offerReset(result.username);
            }
            return setupAuth();
          }

          jitsu.config.save(function (err) {
            return err ? callback(err) : callback();
          });
        });
      });
    });
  })();
};

//
// ### function showError (command, err, shallow, skip)
// #### @command {string} Command which has errored.
// #### @err {Error} Error received for the command.
// #### @shallow {boolean} Value indicating if a deep stack should be displayed
// #### @skip {boolean} Value indicating if this error should be forcibly suppressed.
// Displays the `err` to the user for the `command` supplied.
//
jitsu.showError = function (command, err, shallow, skip) {
  if (err.statusCode !== '403' && !skip) {
    winston.error('Error running command ' + command.magenta);

    if (!jitsu.config.get('nolog')) {
      jitsu.logFile.log(err);
    }

    if (err.message) {
      winston.error(err.message);
    }

    if (err.result) {
      if (err.result.error) {
        winston.error(err.result.error);
      }

      if (err.result.result && err.result.result.error) {
        if (err.result.result.error.stderr) {
          winston.error('');
          winston.error('There was an error while attempting to start your application.');
          winston.error(err.result.result.error.message);
          if (err.result.result.error.blame) {
            winston.error(err.result.result.error.blame.message);
            winston.error('');
            winston.error('This type of error is usually a ' + err.result.result.error.blame.type + ' error.');
          }
          winston.error('Error output from your application:');
          winston.error('');
          err.result.result.error.stderr.split('\n').forEach(function (line) {
            winston.error(line);
          })
        }
        else if (err.result.result.error.stack && jitsu.config.get('debug')) {
          winston.error('There was an error while attempting to deploy your application.');
          winston.error('');
          winston.error(err.result.result.error.message);
          if (err.result.result.error.blame) {
            winston.error(err.result.result.error.blame.message);
            winston.error('');
            winston.error('This type of error is usually a ' + err.result.result.error.blame.type + ' error.');
          }
          winston.error('Error output from Haibu:');
          winston.error('');
          err.result.result.error.stack.split('\n').forEach(function (line) {
            winston.error(line);
          });
        }
      }
      else if (err.result.stack) {
        winston.warn('Error returned from Nodejitsu');
        err.result.stack.split('\n').forEach(function (line) {
          winston.error(line);
        })
      }
    }
    else {
      if (err.stack && !shallow) {
        err.stack.split('\n').forEach(function (trace) {
          winston.error(trace);
        });
      }
    }
  }

  winston.info('Nodejitsu '.grey + 'not ok'.red.bold);
};
<|endoftext|>


<commit_before>// Latte supports Mocha syntax for now, but we'd like to evolve.
// It needs to support two different modes: headless and debug
// * headless runs in the background, and is optimized for speed.
// * debug is optimized for an engineer with devtools open.
(function() {
  window.Latte = {}
  var abort = false // when a callback times out

  // create the root describe block
  var current = Latte.root = {
    _name: 'root', _children: [], _tests: [],
    _before: [], _beforeEach: [], _after: [], _afterEach: [],
    timeout: (val) => {}
  }

  window.describe = function(name, fn) {
    var previous = current
    current = Object.assign(Object.create(previous), {
      _name: name, _children: [], _tests: [],
      _before: [], _beforeEach: [], _after: [], _afterEach: []
    })

    // TODO: if the name changes, this breaks. Maybe use filename instead?
    let oldIndex = previous._children.findIndex(d => d._name == name)
    if (~oldIndex)
      previous._children[oldIndex] = current
    else
      previous._children.push(current)

    fn.call(current)
    current = previous
  }

  window.context = window.describe
  describe.skip = function(name) {}
  window.beforeEach = function(fn) { current._beforeEach.push({fn, timeout: timeoutError()}) }
  window.afterEach = function(fn) { current._afterEach.push({fn, timeout: timeoutError()}) }
  window.before = function(fn) { current._before.push({fn, timeout: timeoutError()}) }
  window.after = function(fn) { current._after.push({fn, timeout: timeoutError()}) }

  // Helpers add methods to the current scope that aid writing clean tests.
  // For example, you might have a `login` or `goOffline` helper that you can call at the top of a
  // test to get things in the right state. Helpers are usually preferred over `beforeEach`, because
  // having setup code at the top makes tests easier to understand.
  //
  // Helpers can be defined as a single function: `helper('login', () => {...})`
  // Or as a group: `helper('Factory', {user: () => {...}, hats: () => {...}})`
  // Groups are a great way to organize related helpers.
  window.helper = function(name, fnOrGroup) {
    // Simple case of a single named function
    if (typeof fnOrGroup == 'function')
      return current[name] = fnOrGroup

    // A group without a name. This gets applied directly to the current context
    if (!name)
      return Object.assign(current, fnOrGroup)

    // A named group. This one is tricky, because when you call `this.myGroup.myHelper`,
    // we want the `this` in myHelper to be currentContext, rather than than `myGroup`.
    let dest = current[name] = {}
    Object.keys(fnOrGroup).forEach(prop => {
      let val = fnOrGroup[prop]
      let isFunction = typeof(val) == 'function'
      Object.defineProperty(dest, prop, { get: () => isFunction ? val.bind(currentContext) : val})
    })
  }

  window.it = function(name, fn) {
    current._tests.push({fn, name, suite: current, stack: timeoutError()})
  }
  it.skip = function() {}
  it.only = function() {}

  // generate the timeout error ahead of time. That way, the stack trace points
  // at the callback, rather than Latte code.
  function timeoutError() { return new Error() }

  Latte.run = async function latteRun(suiteName, testNames, {mode}) {
    abort = false

    // walk through all suites looking for the given name
    var suite, queue = [current]
    while(step = queue.pop()) {
      queue.push.apply(queue, step._children)
      if (step._name == suiteName)
        suite = step
    }

    // figure out which tests to run in the suite
    let tests = suite._tests.filter(t => {
      if (testNames.length == 0) return true // no names? run everything
      return testNames.some(tn => t.name.indexOf(tn) >= 0)
    })

    // Not exactly correct. Top-level befores should only run once for all suites
    var suiteRun = Object.create(suite)
    await applyTopDown(suiteRun, '_before')
    if (abort) return

    for (test of tests) {
      var testRun = Object.create(suiteRun)
      if (!test.fn) continue

      await applyTopDown(testRun, '_beforeEach')
      if (abort) return
      await runTest(test, testRun, mode)
      if (abort) return
      await applyTopDown(testRun, '_afterEach')
      if (abort) return
    }

    await applyTopDown(suiteRun, '_after')
    console.log('Done!')
  }

  // In headless mode, we should catch all errors and continue on.
  // In debug mode, let the error bubble to the devtool's "break on exception"
  async function runTest(test, testRun, mode) {
    if (mode == 'debug') {
      await test.fn.call(testRun)
      console.log(`\u2713 ${test.name}`)
      return
    }

    try { await test.fn.apply(testRun) }
    catch(e) {
      console.error(e, e.stack)
    }
  }

  // Walk upwards through the prototype chain looking for callbacks,
  // then apply them from the top of the chain downwards.
  async function applyTopDown(context, name) {
    var step = context, toApply = [], currentTimeout
    while(step != Object.prototype) {
      if (step.hasOwnProperty(name))
        toApply.unshift.apply(toApply, step[name] || [])
      step = Object.getPrototypeOf(step)
    }

    for (callback of toApply) {
      if (abort) return
      currentTimeout = setTimeout(() => {
        console.log(callback.timeout)
        abort = true
      }, 2000)
      await callback.fn.call(context)
      clearTimeout(currentTimeout)
    }
  }
})()
<commit_msg>Don't run `after` blocks immediately<commit_after>// Latte supports Mocha syntax for now, but we'd like to evolve.
// It needs to support two different modes: headless and debug
// * headless runs in the background, and is optimized for speed.
// * debug is optimized for an engineer with devtools open.
(function() {
  window.Latte = {}
  var abort = false // when a callback times out

  // create the root describe block
  var current = Latte.root = {
    _name: 'root', _children: [], _tests: [],
    _before: [], _beforeEach: [], _after: [], _afterEach: [],
    timeout: (val) => {}
  }
  var currentContext = null

  window.describe = function(name, fn) {
    var previous = current
    current = Object.assign(Object.create(previous), {
      _name: name, _children: [], _tests: [],
      _before: [], _beforeEach: [], _after: [], _afterEach: []
    })

    // TODO: if the name changes, this breaks. Maybe use filename instead?
    let oldIndex = previous._children.findIndex(d => d._name == name)
    if (~oldIndex)
      previous._children[oldIndex] = current
    else
      previous._children.push(current)

    fn.call(current)
    current = previous
  }

  window.context = window.describe
  describe.skip = function(name) {}
  describe.only = function(name) {} // TODO
  window.beforeEach = function(fn) { current._beforeEach.push({fn, timeout: timeoutError()}) }
  window.afterEach = function(fn) { current._afterEach.push({fn, timeout: timeoutError()}) }
  window.before = function(fn) { current._before.push({fn, timeout: timeoutError()}) }
  window.after = function(fn) { current._after.push({fn, timeout: timeoutError()}) }

  // Helpers add methods to the current scope that aid writing clean tests.
  // For example, you might have a `login` or `goOffline` helper that you can call at the top of a
  // test to get things in the right state. Helpers are usually preferred over `beforeEach`, because
  // having setup code at the top makes tests easier to understand.
  //
  // Helpers can be defined as a single function: `helper('login', () => {...})`
  // Or as a group: `helper('Factory', {user: () => {...}, hats: () => {...}})`
  // Groups are a great way to organize related helpers.
  window.helper = function(name, fnOrGroup) {
    // Simple case of a single named function
    if (typeof fnOrGroup == 'function')
      return current[name] = fnOrGroup

    // A group without a name. This gets applied directly to the current context
    if (!name)
      return Object.assign(current, fnOrGroup)

    // A named group. This one is tricky, because when you call `this.myGroup.myHelper`,
    // we want the `this` in myHelper to be currentContext, rather than than `myGroup`.
    let dest = current[name] = {}
    Object.keys(fnOrGroup).forEach(prop => {
      let val = fnOrGroup[prop]
      let isFunction = typeof(val) == 'function'
      Object.defineProperty(dest, prop, { get: () => isFunction ? val.bind(currentContext) : val})
    })
  }

  window.it = function(name, fn) {
    current._tests.push({fn, name, suite: current, stack: timeoutError()})
  }
  it.skip = function() {}
  it.only = function() {}

  // generate the timeout error ahead of time. That way, the stack trace points
  // at the callback, rather than Latte code.
  function timeoutError() { return new Error() }

  Latte.run = async function latteRun(suiteName, testNames, {mode}) {
    abort = false

    // clean up from a previous test run. Delaying `after` blocks like this means that
    // when a test fails, we leave all the DOM/js in place for you to inspect and debug further.
    if (currentContext) {
      await applyTopDown(currentContext, '_afterEach')
      await applyTopDown(currentContext, '_after')
      currentContext = null
      // TODO: if after handlers throw (or timeout), we need to refresh the page.
    }

    // walk through all suites looking for the given name
    var suite, queue = [current]
    while(step = queue.pop()) {
      queue.push.apply(queue, step._children)
      if (step._name == suiteName)
        suite = step
    }

    // figure out which tests to run in the suite
    let tests = suite._tests.filter(t => {
      if (testNames.length == 0) return true // no names? run everything
      return testNames.some(tn => t.name.indexOf(tn) >= 0)
    })

    // Not exactly correct. Top-level befores should only run once for all suites
    var suiteRun = Object.create(suite)
    await applyTopDown(suiteRun, '_before')
    if (abort) return

    for (test of tests) {
      if (!test.fn) continue

      // clean up from the previous test. This only applies if tests.length > 1
      if (currentContext)
        await applyTopDown(currentContext, '_afterEach')
        if (abort) return

      currentContext = Object.create(suiteRun)
      await applyTopDown(currentContext, '_beforeEach')
      if (abort) return
      await runTest(test, currentContext, mode)
    }

    console.log('Done!')
  }

  // In headless mode, we should catch all errors and continue on.
  // In debug mode, let the error bubble to the devtool's "break on exception"
  async function runTest(test, testRun, mode) {
    if (mode == 'debug') {
      await test.fn.call(testRun)
      console.log(`\u2713 ${test.name}`)
      return
    }

    try { await test.fn.apply(testRun) }
    catch(e) {
      console.error(e, e.stack)
    }
  }

  // Walk upwards through the prototype chain looking for callbacks,
  // then apply them from the top of the chain downwards.
  async function applyTopDown(context, name) {
    var step = context, toApply = [], currentTimeout
    while(step != Object.prototype) {
      if (step.hasOwnProperty(name))
        toApply.unshift.apply(toApply, step[name] || [])
      step = Object.getPrototypeOf(step)
    }

    for (callback of toApply) {
      if (abort) return
      currentTimeout = setTimeout(() => {
        console.log(callback.timeout)
        abort = true
      }, 2000)
      await callback.fn.call(context)
      clearTimeout(currentTimeout)
    }
  }
})()
<|endoftext|>


<commit_before>const BbPromise = require('bluebird');
const fse = require('fs-extra');
const path = require('path');
const JSZip = require('jszip');
const { writeZip, addTree } = require('./zipTree');

BbPromise.promisifyAll(fse);

/**
 * Zip up requirements to be used as layer package.
 * @return {Promise} the JSZip object constructed.
 */
function zipRequirements() {
  const rootZip = new JSZip();
  const src = path.join('.serverless', 'requirements');
  const runtimepath = 'python';

  return addTree(rootZip.folder(runtimepath), src).then(() =>
    writeZip(rootZip, path.join('.serverless', 'pythonRequirements.zip'))
  );
}

/**
 * Creates a layer on the serverless service for the requirements zip.
 * @return {Promise} empty promise
 */
function createLayers() {
  if(!this.serverless.service.layers['pythonRequirements']){
     this.serverless.service.layers['pythonRequirements'] = {}
  }
  this.serverless.service.layers['pythonRequirements'] = Object.assign(
    {
      artifact: path.join('.serverless', 'pythonRequirements.zip'),
      name: `${
        this.serverless.service.service
      }-${this.serverless.providers.aws.getStage()}-python-requirements`,
      description:
        'Python requirements generated by serverless-python-requirements.',
      compatibleRuntimes: [this.serverless.service.provider.runtime]
    },
    this.options.layer
  );

  return BbPromise.resolve();
}

/**
 * Creates a layer from the installed requirements.
 * @return {Promise} the combined promise for requirements layer.
 */
function layerRequirements() {
  if (!this.options.layer) {
    return BbPromise.resolve();
  }

  this.serverless.cli.log('Packaging Python Requirements Lambda Layer...');

  return BbPromise.bind(this)
    .then(zipRequirements)
    .then(createLayers);
}

module.exports = {
  layerRequirements
};
<commit_msg>Fix layers being undefined<commit_after>const BbPromise = require('bluebird');
const fse = require('fs-extra');
const path = require('path');
const JSZip = require('jszip');
const { writeZip, addTree } = require('./zipTree');

BbPromise.promisifyAll(fse);

/**
 * Zip up requirements to be used as layer package.
 * @return {Promise} the JSZip object constructed.
 */
function zipRequirements() {
  const rootZip = new JSZip();
  const src = path.join('.serverless', 'requirements');
  const runtimepath = 'python';

  return addTree(rootZip.folder(runtimepath), src).then(() =>
    writeZip(rootZip, path.join('.serverless', 'pythonRequirements.zip'))
  );
}

/**
 * Creates a layer on the serverless service for the requirements zip.
 * @return {Promise} empty promise
 */
function createLayers() {
  if(!this.serverless.service.layers){
     this.serverless.service.layers = {}
  }
  this.serverless.service.layers['pythonRequirements'] = Object.assign(
    {
      artifact: path.join('.serverless', 'pythonRequirements.zip'),
      name: `${
        this.serverless.service.service
      }-${this.serverless.providers.aws.getStage()}-python-requirements`,
      description:
        'Python requirements generated by serverless-python-requirements.',
      compatibleRuntimes: [this.serverless.service.provider.runtime]
    },
    this.options.layer
  );

  return BbPromise.resolve();
}

/**
 * Creates a layer from the installed requirements.
 * @return {Promise} the combined promise for requirements layer.
 */
function layerRequirements() {
  if (!this.options.layer) {
    return BbPromise.resolve();
  }

  this.serverless.cli.log('Packaging Python Requirements Lambda Layer...');

  return BbPromise.bind(this)
    .then(zipRequirements)
    .then(createLayers);
}

module.exports = {
  layerRequirements
};
<|endoftext|>


<commit_before>var utils = require('./utils');

var TYPES = exports.types = {
    WHITESPACE: 0,
    STRING: 1,
    FILTER: 2,
    FILTEREMPTY: 3,
    FUNCTION: 4,
    PARENOPEN: 5,
    PARENCLOSE: 6,
    COMMA: 7,
    VAR: 8,
    NUMBER: 9,
    OPERATOR: 10,
    BRACKETOPEN: 11,
    BRACKETCLOSE: 12,
    DOTKEY: 13,
    ARRAYOPEN: 14,
    ARRAYCLOSE: 15,
    CURLYOPEN: 16,
    CURLYCLOSE: 17,
    COLON: 18,
    COMPARATOR: 19,
    LOGIC: 20,
    NOT: 21,
    BOOL: 22,
    ASSIGNMENT: 23,
    UNKNOWN: 100
  },
  rules = [
    {
      type: TYPES.WHITESPACE,
      regex: [
        /^\s+/
      ]
    },
    {
      type: TYPES.STRING,
      regex: [
        /^""/,
        /^".*?[^\\]"/,
        /^''/,
        /^'.*?[^\\]'/
      ]
    },
    {
      type: TYPES.FILTER,
      regex: [
        /^\|\s*(\w+)\(/
      ],
      idx: 1
    },
    {
      type: TYPES.FILTEREMPTY,
      regex: [
        /^\|\s*(\w+)/
      ],
      idx: 1
    },
    {
      type: TYPES.FUNCTION,
      regex: [
        /^\s*(\w+)\(/
      ],
      idx: 1
    },
    {
      type: TYPES.PARENOPEN,
      regex: [
        /^\(/
      ]
    },
    {
      type: TYPES.PARENCLOSE,
      regex: [
        /^\)/
      ]
    },
    {
      type: TYPES.COMMA,
      regex: [
        /^,/
      ]
    },
    {
      type: TYPES.LOGIC,
      regex: [
        /^(&&|\|\||and|or)\s*/
      ],
      idx: 1,
      replace: {
        'and': '&&',
        'or': '||'
      }
    },
    {
      type: TYPES.COMPARATOR,
      regex: [
        /^(===|==|\!==|\!=|<=|<|>=|>|in\s|gte\s|gt\s|lte\s|lt\s)\s*/
      ],
      idx: 1,
      replace: {
        'gte': '>=',
        'gt': '>',
        'lte': '<=',
        'lt': '<'
      }
    },
    {
      type: TYPES.ASSIGNMENT,
      regex: [
        /^(=|\+=|-=|\*=|\/=)/
      ]
    },
    {
      type: TYPES.NOT,
      regex: [
        /^(not|\!)\s*/
      ],
      idx: 1,
      replace: {
        'not': '!'
      }
    },
    {
      type: TYPES.BOOL,
      regex: [
        /^(true|false)/
      ]
    },
    {
      type: TYPES.VAR,
      regex: [
        /^[a-zA-Z_$]\w*((\.\w*)+)?/,
        /^[a-zA-Z_$]\w*/
      ]
    },
    {
      type: TYPES.BRACKETOPEN,
      regex: [
        /^\[/
      ]
    },
    {
      type: TYPES.BRACKETCLOSE,
      regex: [
        /^\]/
      ]
    },
    {
      type: TYPES.CURLYOPEN,
      regex: [
        /^\{/
      ]
    },
    {
      type: TYPES.COLON,
      regex: [
        /^\:/
      ]
    },
    {
      type: TYPES.CURLYCLOSE,
      regex: [
        /^\}/
      ]
    },
    {
      type: TYPES.DOTKEY,
      regex: [
        /^\.(\w+)/,
      ],
      idx: 1
    },
    {
      type: TYPES.NUMBER,
      regex: [
        /^[+\-]?\d+(\.\d+)?/
      ]
    },
    {
      type: TYPES.OPERATOR,
      regex: [
        /^(\+|\-|\/|\*|%)/
      ]
    }
  ];

function reader(str) {
  var matched;

  utils.some(rules, function (rule) {
    return utils.some(rule.regex, function (regex) {
      var match = str.match(regex),
        normalized;

      if (!match) {
        return;
      }

      normalized = match[rule.idx || 0].replace(/\s*$/, '');
      normalized = (rule.hasOwnProperty('replace') && rule.replace.hasOwnProperty(normalized)) ? rule.replace[normalized] : normalized;

      matched = {
        match: normalized,
        type: rule.type,
        length: match[0].length
      };
      return true;
    });
  });

  if (!matched) {
    matched = {
      match: str,
      type: TYPES.UNKNOWN,
      length: str.length
    };
  }

  return matched;
}

exports.read = function (str) {
  var offset = 0,
    tokens = [],
    substr,
    match;
  while (offset < str.length) {
    substr = str.substring(offset);
    match = reader(substr);
    offset += match.length;
    tokens.push(match);
  }
  return tokens;
};
<commit_msg>jsdoc lib/lexer.js<commit_after>var utils = require('./utils');

var TYPES = exports.types = {
    WHITESPACE: 0,
    STRING: 1,
    FILTER: 2,
    FILTEREMPTY: 3,
    FUNCTION: 4,
    PARENOPEN: 5,
    PARENCLOSE: 6,
    COMMA: 7,
    VAR: 8,
    NUMBER: 9,
    OPERATOR: 10,
    BRACKETOPEN: 11,
    BRACKETCLOSE: 12,
    DOTKEY: 13,
    ARRAYOPEN: 14,
    ARRAYCLOSE: 15,
    CURLYOPEN: 16,
    CURLYCLOSE: 17,
    COLON: 18,
    COMPARATOR: 19,
    LOGIC: 20,
    NOT: 21,
    BOOL: 22,
    ASSIGNMENT: 23,
    UNKNOWN: 100
  },
  rules = [
    {
      type: TYPES.WHITESPACE,
      regex: [
        /^\s+/
      ]
    },
    {
      type: TYPES.STRING,
      regex: [
        /^""/,
        /^".*?[^\\]"/,
        /^''/,
        /^'.*?[^\\]'/
      ]
    },
    {
      type: TYPES.FILTER,
      regex: [
        /^\|\s*(\w+)\(/
      ],
      idx: 1
    },
    {
      type: TYPES.FILTEREMPTY,
      regex: [
        /^\|\s*(\w+)/
      ],
      idx: 1
    },
    {
      type: TYPES.FUNCTION,
      regex: [
        /^\s*(\w+)\(/
      ],
      idx: 1
    },
    {
      type: TYPES.PARENOPEN,
      regex: [
        /^\(/
      ]
    },
    {
      type: TYPES.PARENCLOSE,
      regex: [
        /^\)/
      ]
    },
    {
      type: TYPES.COMMA,
      regex: [
        /^,/
      ]
    },
    {
      type: TYPES.LOGIC,
      regex: [
        /^(&&|\|\||and|or)\s*/
      ],
      idx: 1,
      replace: {
        'and': '&&',
        'or': '||'
      }
    },
    {
      type: TYPES.COMPARATOR,
      regex: [
        /^(===|==|\!==|\!=|<=|<|>=|>|in\s|gte\s|gt\s|lte\s|lt\s)\s*/
      ],
      idx: 1,
      replace: {
        'gte': '>=',
        'gt': '>',
        'lte': '<=',
        'lt': '<'
      }
    },
    {
      type: TYPES.ASSIGNMENT,
      regex: [
        /^(=|\+=|-=|\*=|\/=)/
      ]
    },
    {
      type: TYPES.NOT,
      regex: [
        /^(not|\!)\s*/
      ],
      idx: 1,
      replace: {
        'not': '!'
      }
    },
    {
      type: TYPES.BOOL,
      regex: [
        /^(true|false)/
      ]
    },
    {
      type: TYPES.VAR,
      regex: [
        /^[a-zA-Z_$]\w*((\.\w*)+)?/,
        /^[a-zA-Z_$]\w*/
      ]
    },
    {
      type: TYPES.BRACKETOPEN,
      regex: [
        /^\[/
      ]
    },
    {
      type: TYPES.BRACKETCLOSE,
      regex: [
        /^\]/
      ]
    },
    {
      type: TYPES.CURLYOPEN,
      regex: [
        /^\{/
      ]
    },
    {
      type: TYPES.COLON,
      regex: [
        /^\:/
      ]
    },
    {
      type: TYPES.CURLYCLOSE,
      regex: [
        /^\}/
      ]
    },
    {
      type: TYPES.DOTKEY,
      regex: [
        /^\.(\w+)/,
      ],
      idx: 1
    },
    {
      type: TYPES.NUMBER,
      regex: [
        /^[+\-]?\d+(\.\d+)?/
      ]
    },
    {
      type: TYPES.OPERATOR,
      regex: [
        /^(\+|\-|\/|\*|%)/
      ]
    }
  ];

/**
 * Return the token type object for a single chunk of a string.
 * @param  {string} str String chunk.
 * @return {{match: string, type: number, length: number}}     Defined type, potentially stripped or replaced with more suitable content.
 * @api private
 */
function reader(str) {
  var matched;

  utils.some(rules, function (rule) {
    return utils.some(rule.regex, function (regex) {
      var match = str.match(regex),
        normalized;

      if (!match) {
        return;
      }

      normalized = match[rule.idx || 0].replace(/\s*$/, '');
      normalized = (rule.hasOwnProperty('replace') && rule.replace.hasOwnProperty(normalized)) ? rule.replace[normalized] : normalized;

      matched = {
        match: normalized,
        type: rule.type,
        length: match[0].length
      };
      return true;
    });
  });

  if (!matched) {
    matched = {
      match: str,
      type: TYPES.UNKNOWN,
      length: str.length
    };
  }

  return matched;
}

/**
 * Read a string and break it into separate token types.
 * @param  {string} str
 * @return {[{match: string, type: number, length: number}...]}     Array of defined types, potentially stripped or replaced with more suitable content.
 */
exports.read = function (str) {
  var offset = 0,
    tokens = [],
    substr,
    match;
  while (offset < str.length) {
    substr = str.substring(offset);
    match = reader(substr);
    offset += match.length;
    tokens.push(match);
  }
  return tokens;
};
<|endoftext|>


<commit_before>var utils = require('./utils');
var jsonLogic = require('json-logic');

module.exports = function (options, data, next) {

    var rules = {"and": [
        {"<" : [{"var": "temp"}, 110]},
        {"==": [{"var": "pie.filling"}, "apple"]}
    ]};

    var data = { "temp" : 100, "pie" : { "filling" : "apple" } };

    console.log(jsonLogic(rules, data));
    /*
        {
            =: ['data.key', 'value', 'if', 'else']
            !: ['data.key', 'value', 'if', 'else']
        }
    */

    // merge options into data
    Object.assign(data, options, data);

    if (!data['?']) {
        return next(new Error('Flow-Tools: Mask: No mask found.'));
    }

    // .. do masking

    next(null, data);
}
<commit_msg>under construction<commit_after>var utils = require('./utils');
var jsonLogic = require('json-logic');

module.exports = function (options, data, next) {

    var rules = {"and": [
        {"<" : [{"var": "temp"}, 110]},
        {"==": [{"var": "pie.filling"}, "apple"]}
    ]};

    var data = { "temp" : 100, "pie" : { "filling" : "apple" } };

    console.log(jsonLogic(rules, data));

    // merge options into data
    Object.assign(data, options, data);

    if (!data['?']) {
        return next(new Error('Flow-Tools: Mask: No mask found.'));
    }

    // .. do masking

    next(null, data);
}
<|endoftext|>


<commit_before>'use strict';

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

/**
 * Test a JSON object
 *
 * @param {String | Number | Boolean | Object | Array} value The value that will be tested.
 * @param {*} expected A pattern to test against the value provided.
 * @return {Boolean} The boolean result of the test.
 * @api public
 */
function match(value, expected) {
  if (value === null) return expected === null;
  if (expected === null) return value === null;
  switch (type(expected)) {
    case 'string':
    case 'number':
    case 'boolean':
    case 'undefined':
      return value === expected;
    case 'regexp':
      return expected.test(value);
    case 'function':
      return evaluateFunction(value, expected); // eslint-disable-line no-use-before-define
    case 'object':
      return compareObjects(value, expected); // eslint-disable-line no-use-before-define
    case 'array':
      return compareArrays(value, expected); // eslint-disable-line no-use-before-define
    default:
      return false;
  }
}

/**
 * Make a function which test a JSON object
 *
 * @param {*} expected A pattern to test against the value provided.
 * @returns {Function} A function with the expected patter predefined.
 * @api public
 */
match.bake = expected => value => match(value, expected);

/**
 * Compares two arrays
 *
 * @param {Array} array Array to be tested.
 * @param {Object} expected Pattern to test the array.
 * @return {Boolean} Result of the test.
 * @api private
 */
function compareArrays(array, expected) {
  if (array.length !== expected.length) return false;
  return array.reduce((previous, current, index) => {
    return match(array[index], expected[index]) && previous;
  }, true);
}

/**
 * Compares two objects
 *
 * @param {Objet} object Object to be tested.
 * @param {Object} expected Pattern to test the object.
 * @return {Boolean} Result of the test.
 * @api private
 */
function compareObjects(object, expected) {
  if (!compareArrays(Object.keys(object), Object.keys(expected))) return false;
  return Object.keys(expected).reduce((previous, current) => {
    return match(object[current], expected[current]) && previous;
  }, true);
}

/**
 * Evaluate a function and throw an error if the result is not boolean
 *
 * @param {*} value A value that will be sended as a parameter to the function.
 * @param {Function} func A function to call.
 * @return {Boolean} The result of the function.
 * @api private
 */
function evaluateFunction(value, func) {
  let result = func.call(null, value);
  if (typeof result !== 'boolean') {
    throw new Error('Function provided should return a boolean');
  }
  return result;
}

module.exports = match;
<commit_msg>Use custom type function for consistency<commit_after>'use strict';

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

/**
 * Test a JSON object
 *
 * @param {String | Number | Boolean | Object | Array} value The value that will be tested.
 * @param {*} expected A pattern to test against the value provided.
 * @return {Boolean} The boolean result of the test.
 * @api public
 */
function match(value, expected) {
  if (value === null) return expected === null;
  if (expected === null) return value === null;
  switch (type(expected)) {
    case 'string':
    case 'number':
    case 'boolean':
    case 'undefined':
      return value === expected;
    case 'regexp':
      return expected.test(value);
    case 'function':
      return evaluateFunction(value, expected); // eslint-disable-line no-use-before-define
    case 'object':
      return compareObjects(value, expected); // eslint-disable-line no-use-before-define
    case 'array':
      return compareArrays(value, expected); // eslint-disable-line no-use-before-define
    default:
      return false;
  }
}

/**
 * Make a function which test a JSON object
 *
 * @param {*} expected A pattern to test against the value provided.
 * @returns {Function} A function with the expected patter predefined.
 * @api public
 */
match.bake = expected => value => match(value, expected);

/**
 * Compares two arrays
 *
 * @param {Array} array Array to be tested.
 * @param {Object} expected Pattern to test the array.
 * @return {Boolean} Result of the test.
 * @api private
 */
function compareArrays(array, expected) {
  if (array.length !== expected.length) return false;
  return array.reduce((previous, current, index) => {
    return match(array[index], expected[index]) && previous;
  }, true);
}

/**
 * Compares two objects
 *
 * @param {Objet} object Object to be tested.
 * @param {Object} expected Pattern to test the object.
 * @return {Boolean} Result of the test.
 * @api private
 */
function compareObjects(object, expected) {
  if (!compareArrays(Object.keys(object), Object.keys(expected))) return false;
  return Object.keys(expected).reduce((previous, current) => {
    return match(object[current], expected[current]) && previous;
  }, true);
}

/**
 * Evaluate a function and throw an error if the result is not boolean
 *
 * @param {*} value A value that will be sended as a parameter to the function.
 * @param {Function} func A function to call.
 * @return {Boolean} The result of the function.
 * @api private
 */
function evaluateFunction(value, func) {
  let result = func.call(null, value);
  if (type(result) !== 'boolean') {
    throw new Error('Function provided should return a boolean');
  }
  return result;
}

module.exports = match;
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * @fileoverview Moodle mobile file system lib.
 * @author Juan Leyva
 * @version 1.2
 */

/**
 * @namespace Holds all the MoodleMobile file system functionality.
 */
MM.fs = {

    basePath: '',
    defaultSize: 0,

    init: function(callBack) {
        MM.log('Loading File System', 'FS');

        // This means that or Cordova or the emulator are not yet loaded, so we must delay this.
        if (typeof(LocalFileSystem) == "undefined") {
            MM.log("LocalFileSystem not defined yet", "FS");
            setTimeout(function() {
                MM.fs.init(callBack);
            }, 5000);
            return;
        }

        if( MM.getOS() == 'android' && typeof(MM.config.app_id) == "undefined" ){
            MM.log("MM.config not defined yet", "FS");
            setTimeout(function() {
                MM.fs.init(callBack);
            }, 2000);
            return;
        }

        if (!callBack) {
            callBack = function() {};
        }

        if (MM.fs.loaded()) {
            // The file system was yet loaded.
            MM.log('The file system was previously loaded', 'FS');
            callBack();
            return;
        }

        if (MM.getOS() == 'android') {
            MM.fs.basePath = 'Android/data/'+MM.config.app_id;
        }
        MM.fs.loadFS(callBack);
    },

    loaded: function() {
        return typeof(MM.fs.fileSystemRoot) != 'undefined';
    },

    getRoot: function() {
        if (!MM.fs.fileSystemRoot) {
            MM.fs.loadFS(function() {
                MM.fs.entryURL(MM.fs.fileSystemRoot);
            });
        } else {
            var path = MM.fs.entryURL(MM.fs.fileSystemRoot);
            // Android 4.2 and onwards
            //path = path.replace("storage/emulated/0", "sdcard");
            return path;
        }
    },

    loadFS: function(callBack) {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, MM.fs.defaultSize,
            function(fileSystem) {
                MM.log("FileSystem quota: " + MM.fs.defaultSize, "FS");
                MM.fs.fileSystemRoot = fileSystem.root;
                if (MM.fs.basePath) {
                    MM.fs.fileSystemRoot.getDirectory(
                        MM.fs.basePath, {create: true, exclusive: false},
                        function(entry) {
                            MM.fs.fileSystemRoot = entry;
                            callBack();
                        },
                        function(err) {
                            var msg = 'Critical error accessing file system, directory ' + MM.fs.basePath + ' can\'t be created';
                            MM.log(msg);
                            if (err) {
                                console.log("Error dump", "FS");
                            }
                            MM.popErrorMessage(msg);
                        }
                    );
                } else {
                    callBack();
                }
            }, function() {
                MM.popErrorMessage('Critical error accessing file system');
            }
       );
    },

    fileExists: function(path, successCallback, errorCallback) {
        var directory = path.substring(0, path.lastIndexOf('/') );
        var filename = path.substr(path.lastIndexOf('/') + 1);
        MM.fs.fileSystemRoot.getDirectory(
            directory,
            { create: false },
            function(entry) {
                entry.getFile(
                    filename, { create: false },
                    function(entryFile) {
                        successCallback(MM.fs.entryURL(entryFile));
                    },
                    errorCallback
                );
            },
            errorCallback
        );
    },

    createDir: function(path, successCallback, dirEntry) {
        path = path.replace('file:///', '');
        MM.log('FS: Creating full directory ' + path);

        var paths = path.split('/');

        var baseRoot = MM.fs.fileSystemRoot;
        if (dirEntry) {
            baseRoot = dirEntry;
        }

        MM.log('FS: Creating directory ' + paths[0] + 'in' + MM.fs.entryURL(baseRoot));
        baseRoot.getDirectory(
            paths[0],
            {create: true, exclusive: false},
            function(newDirEntry) {
                if (paths.length == 1) {
                    successCallback(newDirEntry);
                    return;
                }
                // Recursively, create next directories
                paths.shift();
                MM.fs.createDir(paths.join('/'), successCallback, newDirEntry);
            },
            function(err) {
                MM.popErrorMessage('Critical error creating directory: ' + paths[0]);
                if (err) {
                    MM.log("Error dump", "FS");
                    console.log(err);
                }
            }
        );
    },

    /**
     * Remove recursively a directory
     * @param  {string} path            The relative path of the directory
     * @param  {object} successCallback Success callback function
     * @param  {object} errorCallback   Error callback function
     */
    removeDirectory: function(path, successCallback, errorCallback) {
        MM.log('FS: Removing full directory ' + path);

        var baseRoot = MM.fs.fileSystemRoot;
        if (!baseRoot) {
            errorCallback();
            return;
        }
        baseRoot.getDirectory(
            path,
            {create: false, exclusive: false},
            function(dirEntry) {
                dirEntry.removeRecursively(successCallback, errorCallback);
            },
            errorCallback
        );
    },

    /**
     * Recursive and asynchronous function for calculating the size of a directory.
     * We use several controls global vars in order to know when the algorithm has finished.
     *
     * @param  {string} path                Relative path to the directory
     * @param  {[type]} successCallback     Success Callback
     * @param  {[type]} errorCallback       Error Callback
     */
    directorySize: function(path, successCallback, errorCallback) {
        var baseRoot = MM.fs.fileSystemRoot;
        var fileCounter = 1;    // Are files sizes pending to be retrieved?
        var totalSize = 0;      // Total size of files.
        var running = 0;        // There are async calls pending?
        var directoryReader;

        MM.log('Calculating directory size: ' + path , 'FS');
        var sizeHelper = function(entry) {
            if (entry.isDirectory) {
                fileCounter--;
                running++;
                directoryReader = entry.createReader();
                directoryReader.readEntries(function(entries) {
                    running--;
                    fileCounter += entries.length;
                    var i;
                    for (i=0; i<entries.length; i++) {
                        sizeHelper(entries[i]);
                    }
                }, errorCallback);
            } else if (entry.isFile) {
                entry.file(
                    function(file) {
                        totalSize += file.size;
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    },
                    function() {
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    });
            } else {
                fileCounter--;
            }
        };

        if (baseRoot) {
            baseRoot.getDirectory(
                path,
                {create: false},
                function(entry) {
                    sizeHelper(entry);
                },
                function() {
                    errorCallback();
                }
            );
        } else {
            errorCallback();
        }
    },

    /**
     * Asynchronous function for retrieving the contents of a directory (not subdirectories)
     *
     * @param  {string} path                Relative path to the directory
     * @param  {[type]} successCallback     Success Callback
     * @param  {[type]} errorCallback       Error Callback
     */
    getDirectoryContents: function(path, successCallback, errorCallback) {
        var baseRoot = MM.fs.fileSystemRoot;
        var directoryReader;

        MM.log('Reading directory contents: ' + path , 'FS');
        var contentsHelper = function(entry) {
            directoryReader = entry.createReader();
            directoryReader.readEntries(function(entries) {
                successCallback(entries);
            }, errorCallback);
        };

        if (baseRoot) {
            baseRoot.getDirectory(
                path,
                {create: false},
                function(entry) {
                    contentsHelper(entry);
                },
                function() {
                    MM.log('Directory doesn\'t exist: ' + path , 'FS');
                    errorCallback();
                }
            );
        } else {
            errorCallback();
        }
    },

    /**
     * This function attemps to calculate the free space in the disck (or sandbox or whatever)
     * Since there is not Phonegap API, what we do is request a file system instance with a minimum size until we get an error
     * We call this function two times for accurate results.
     *
     * @param  {object} callBack        Success callback
     * @param  {object} errorCallback   Error Callback
     * @return {float}                  The estimated free space in bytes
     */
    calculateFreeSpace: function(callBack, errorCallback) {

        var tooMuch = false;
        var tooLessCounter = 0;
        var iterations = 50;

        calculateByRequest = function(size, ratio, iterations, callBack) {
            window.requestFileSystem(LocalFileSystem.PERSISTENT, size,
                function() {

                    tooLessCounter++;

                    if (tooMuch) {
                        callBack(size);
                        return;
                    }
                    // This is to prevent infinite loops.
                    if (tooLessCounter > iterations) {
                        return;
                    }
                    calculateByRequest(size * ratio, ratio, iterations, callBack);
                },
                function() {
                    tooMuch = true;
                    calculateByRequest(size / ratio, ratio, iterations, callBack);
                }
            );
        };

        if (window.requestFileSystem) {
            // General calculation, base 1MB and increasing factor 1.3.
            calculateByRequest(1048576, 1.3, iterations, function(size) {
                tooMuch = false;
                tooLessCounter = 0;
                iterations = 10;
                // More accurate. Factor is 1.1.
                calculateByRequest(size, 1.1, iterations, callBack);
            });
        } else {
            errorCallback();
        }
    },

    /**
     * Helper function for using the correct available method since toNativeURL is Cordova specific
     * @param  {object} entry File/Directory entry
     * @return {string}       URL for the file
     */
    entryURL: function(entry) {
        if (typeof(entry.toNativeURL) == "function") {
            return entry.toNativeURL();
        } else {
            return entry.toURL();
        }
    }

};<commit_msg>MOBILE-686 cordova: Add additional logging<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * @fileoverview Moodle mobile file system lib.
 * @author Juan Leyva
 * @version 1.2
 */

/**
 * @namespace Holds all the MoodleMobile file system functionality.
 */
MM.fs = {

    basePath: '',
    defaultSize: 0,

    init: function(callBack) {
        MM.log('Loading File System', 'FS');

        // This means that or Cordova or the emulator are not yet loaded, so we must delay this.
        if (typeof(LocalFileSystem) == "undefined") {
            MM.log("LocalFileSystem not defined yet", "FS");
            setTimeout(function() {
                MM.fs.init(callBack);
            }, 5000);
            return;
        }

        if( MM.getOS() == 'android' && typeof(MM.config.app_id) == "undefined" ){
            MM.log("MM.config not defined yet", "FS");
            setTimeout(function() {
                MM.fs.init(callBack);
            }, 2000);
            return;
        }

        if (!callBack) {
            callBack = function() {};
        }

        if (MM.fs.loaded()) {
            // The file system was yet loaded.
            MM.log('The file system was previously loaded', 'FS');
            callBack();
            return;
        }

        if (MM.getOS() == 'android') {
            MM.fs.basePath = 'Android/data/'+MM.config.app_id;
            MM.log("Android device file path " + MM.fs.basePath, "FS");
        }
        MM.fs.loadFS(callBack);
    },

    loaded: function() {
        return typeof(MM.fs.fileSystemRoot) != 'undefined';
    },

    getRoot: function() {
        if (!MM.fs.fileSystemRoot) {
            MM.fs.loadFS(function() {
                MM.fs.entryURL(MM.fs.fileSystemRoot);
            });
        } else {
            var path = MM.fs.entryURL(MM.fs.fileSystemRoot);
            // Android 4.2 and onwards
            //path = path.replace("storage/emulated/0", "sdcard");
            return path;
        }
    },

    loadFS: function(callBack) {
        MM.log("Requesting file system", "FS");
        window.requestFileSystem(LocalFileSystem.PERSISTENT, MM.fs.defaultSize,
            function(fileSystem) {
                MM.log("FileSystem quota: " + MM.fs.defaultSize, "FS");
                MM.fs.fileSystemRoot = fileSystem.root;
                if (MM.fs.basePath) {
                    MM.fs.fileSystemRoot.getDirectory(
                        MM.fs.basePath, {create: true, exclusive: false},
                        function(entry) {
                            MM.fs.fileSystemRoot = entry;
                            callBack();
                        },
                        function(err) {
                            var msg = 'Critical error accessing file system, directory ' + MM.fs.basePath + ' can\'t be created';
                            MM.log(msg, "FS");
                            if (err) {
                                console.log("Error dump", "FS");
                            }
                            MM.popErrorMessage(msg);
                        }
                    );
                } else {
                    callBack();
                }
            }, function() {
                MM.log("Critical error accessing file system", "FS");
                MM.popErrorMessage('Critical error accessing file system');
            }
       );
    },

    fileExists: function(path, successCallback, errorCallback) {
        var directory = path.substring(0, path.lastIndexOf('/') );
        var filename = path.substr(path.lastIndexOf('/') + 1);
        MM.fs.fileSystemRoot.getDirectory(
            directory,
            { create: false },
            function(entry) {
                entry.getFile(
                    filename, { create: false },
                    function(entryFile) {
                        successCallback(MM.fs.entryURL(entryFile));
                    },
                    errorCallback
                );
            },
            errorCallback
        );
    },

    createDir: function(path, successCallback, dirEntry) {
        path = path.replace('file:///', '');
        MM.log('FS: Creating full directory ' + path, 'FS');

        var paths = path.split('/');

        var baseRoot = MM.fs.fileSystemRoot;
        if (dirEntry) {
            baseRoot = dirEntry;
        }

        MM.log('FS: Creating directory ' + paths[0] + 'in' + MM.fs.entryURL(baseRoot), 'FS');
        baseRoot.getDirectory(
            paths[0],
            {create: true, exclusive: false},
            function(newDirEntry) {
                if (paths.length == 1) {
                    successCallback(newDirEntry);
                    return;
                }
                // Recursively, create next directories
                paths.shift();
                MM.fs.createDir(paths.join('/'), successCallback, newDirEntry);
            },
            function(err) {
                MM.popErrorMessage('Critical error creating directory: ' + paths[0]);
                if (err) {
                    MM.log("Error dump", "FS");
                }
            }
        );
    },

    /**
     * Remove recursively a directory
     * @param  {string} path            The relative path of the directory
     * @param  {object} successCallback Success callback function
     * @param  {object} errorCallback   Error callback function
     */
    removeDirectory: function(path, successCallback, errorCallback) {
        MM.log('FS: Removing full directory ' + path, 'FS');

        var baseRoot = MM.fs.fileSystemRoot;
        if (!baseRoot) {
            errorCallback();
            return;
        }
        baseRoot.getDirectory(
            path,
            {create: false, exclusive: false},
            function(dirEntry) {
                dirEntry.removeRecursively(successCallback, errorCallback);
            },
            errorCallback
        );
    },

    /**
     * Recursive and asynchronous function for calculating the size of a directory.
     * We use several controls global vars in order to know when the algorithm has finished.
     *
     * @param  {string} path                Relative path to the directory
     * @param  {[type]} successCallback     Success Callback
     * @param  {[type]} errorCallback       Error Callback
     */
    directorySize: function(path, successCallback, errorCallback) {
        var baseRoot = MM.fs.fileSystemRoot;
        var fileCounter = 1;    // Are files sizes pending to be retrieved?
        var totalSize = 0;      // Total size of files.
        var running = 0;        // There are async calls pending?
        var directoryReader;

        MM.log('Calculating directory size: ' + path , 'FS');
        var sizeHelper = function(entry) {
            if (entry.isDirectory) {
                fileCounter--;
                running++;
                directoryReader = entry.createReader();
                directoryReader.readEntries(function(entries) {
                    running--;
                    fileCounter += entries.length;
                    var i;
                    for (i=0; i<entries.length; i++) {
                        sizeHelper(entries[i]);
                    }
                }, errorCallback);
            } else if (entry.isFile) {
                entry.file(
                    function(file) {
                        totalSize += file.size;
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    },
                    function() {
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    });
            } else {
                fileCounter--;
            }
        };

        if (baseRoot) {
            baseRoot.getDirectory(
                path,
                {create: false},
                function(entry) {
                    sizeHelper(entry);
                },
                function() {
                    errorCallback();
                }
            );
        } else {
            errorCallback();
        }
    },

    /**
     * Asynchronous function for retrieving the contents of a directory (not subdirectories)
     *
     * @param  {string} path                Relative path to the directory
     * @param  {[type]} successCallback     Success Callback
     * @param  {[type]} errorCallback       Error Callback
     */
    getDirectoryContents: function(path, successCallback, errorCallback) {
        var baseRoot = MM.fs.fileSystemRoot;
        var directoryReader;

        MM.log('Reading directory contents: ' + path , 'FS');
        var contentsHelper = function(entry) {
            directoryReader = entry.createReader();
            directoryReader.readEntries(function(entries) {
                successCallback(entries);
            }, errorCallback);
        };

        if (baseRoot) {
            baseRoot.getDirectory(
                path,
                {create: false},
                function(entry) {
                    contentsHelper(entry);
                },
                function() {
                    MM.log('Directory doesn\'t exist: ' + path , 'FS');
                    errorCallback();
                }
            );
        } else {
            errorCallback();
        }
    },

    /**
     * This function attemps to calculate the free space in the disck (or sandbox or whatever)
     * Since there is not Phonegap API, what we do is request a file system instance with a minimum size until we get an error
     * We call this function two times for accurate results.
     *
     * @param  {object} callBack        Success callback
     * @param  {object} errorCallback   Error Callback
     * @return {float}                  The estimated free space in bytes
     */
    calculateFreeSpace: function(callBack, errorCallback) {

        var tooMuch = false;
        var tooLessCounter = 0;
        var iterations = 50;

        calculateByRequest = function(size, ratio, iterations, callBack) {
            window.requestFileSystem(LocalFileSystem.PERSISTENT, size,
                function() {

                    tooLessCounter++;

                    if (tooMuch) {
                        callBack(size);
                        return;
                    }
                    // This is to prevent infinite loops.
                    if (tooLessCounter > iterations) {
                        return;
                    }
                    calculateByRequest(size * ratio, ratio, iterations, callBack);
                },
                function() {
                    tooMuch = true;
                    calculateByRequest(size / ratio, ratio, iterations, callBack);
                }
            );
        };

        if (window.requestFileSystem) {
            // General calculation, base 1MB and increasing factor 1.3.
            calculateByRequest(1048576, 1.3, iterations, function(size) {
                tooMuch = false;
                tooLessCounter = 0;
                iterations = 10;
                // More accurate. Factor is 1.1.
                calculateByRequest(size, 1.1, iterations, callBack);
            });
        } else {
            errorCallback();
        }
    },

    /**
     * Helper function for using the correct available method since toNativeURL is Cordova specific
     * @param  {object} entry File/Directory entry
     * @return {string}       URL for the file
     */
    entryURL: function(entry) {
        if (typeof(entry.toNativeURL) == "function") {
            return entry.toNativeURL();
        } else {
            return entry.toURL();
        }
    }

};<|endoftext|>


<commit_before>// Setup
const util = require('util')
const Event = require('events').EventEmitter
var app, logger, PG

// Exports
module.exports = function (thisApp, model) {
  if (typeof model === 'string') {
    var modelName = model.toString()
    model = function () {}
    Object.defineProperty(model, 'name', {
      configurable: true,
      enumerable: false,
      value: modelName,
      writable: false
    })
  }
  model._modelName = model.name
    .replace(/\.?([A-Z]+)/g, (x, y) => (' ' + y)).trim()
    .replace(/\s+/g, '_').toLowerCase()
  app = app || thisApp
  logger = logger || app.config.logger
  PG = require('./pg')(app)
  model._ns = app._ns
  util.inherits(model, Record)
  Object.keys(Record).map(function (e) { model[e] = Record[e] })
  return model
}

// Record class
function Record () {}

// Inherit EventEmitter
util.inherits(Record, Event)

/* CLASS METHODS */
// Return new record (not persisted to db)
Record.new = function (params) {
  var Class = this
  var record = new Class()
  record.model = Class._modelName
  record._ns = Class._ns
  record.attrs = params
  return record
}

// Return new record (persisted to db)
Record.create = function (params) {
  var Class = this
  var record = Class.new(params)
  record.on('saved', function () { record.emit('created') })
  record.save()
  return record
}

// Get record from DB
Record.find = function (recordid) {
  var Class = this
  var model = Class.name.toLowerCase()
  var record = Class.new()
  var spName = Class._ns + '_' + model + '_read'
  var args = {id_array: [parseInt(recordid, 10)]}
  record._callSP(spName, args, function () {
    return record.emit('data')
  })
  return record
}

// Get records from DB
Record.findAll = function (query) {
  var Class = this
  var model = Class.name.toLowerCase()
  var evt = new Event()
  var pg = new PG()
  var spName = Class._ns + '_' + model + '_search'
  pg.on('error', function (err) {
    evt.emit('error', err)
    return logger.fatal(err)
  })
  pg.on('data', function (data) {
    var records = data.rows[0][spName].map(function (u) {
      return Class.new(u)
    })
    evt.emit('data', records)
  })
  pg.on('ready', function () { pg.sp(spName, [query]) })
  return evt
}

/* INSTANCE HELPERS */
// SP Helper
Record.prototype._callSP = function (spName, args, callback) {
  var self = this
  var pg = new PG()
  pg.on('error', function (err) {
    self.emit('error', err)
    return logger.fatal(err)
  })
  pg.on('data', function (data) {
    self.attrs = data.rows[0][spName][0]
    callback(data)
  })
  pg.on('ready', function () { pg.sp(spName, [args]) })
}

/* INSTANCE METHODS */
// Save our record to db
Record.prototype.save = function () {
  var self = this
  var spName = self.new_record
    ? self._ns + '_' + self.model + '_create'
    : self._ns + '_' + self.model + '_update'
  self._callSP(spName, self.attrs, function () { return self.emit('saved') })
}

// Mark record deleted in db
Record.prototype.destroy = function (recordid) {
  var self = this
  var spName = self._ns + '_' + self.model + '_delete'
  var args = {id_array: [parseInt(recordid, 10)]}
  self._callSP(spName, args, function () { return self.emit('deleted') })
}
<commit_msg>Tie PG to Model class<commit_after>// Setup
const util = require('util')
const Event = require('events').EventEmitter
var app, logger

// Exports
module.exports = function (thisApp, model) {
  if (typeof model === 'string') {
    var modelName = model.toString()
    model = function () {}
    Object.defineProperty(model, 'name', {
      configurable: true,
      enumerable: false,
      value: modelName,
      writable: false
    })
  }
  model._modelName = model.name
    .replace(/\.?([A-Z]+)/g, (x, y) => (' ' + y)).trim()
    .replace(/\s+/g, '_').toLowerCase()
  app = app || thisApp
  logger = logger || app.config.logger
  model.PG = require('./pg')(app)
  model._ns = app._ns
  util.inherits(model, Record)
  Object.keys(Record).map(function (e) { model[e] = Record[e] })
  return model
}

// Record class
function Record () {}

// Inherit EventEmitter
util.inherits(Record, Event)

/* CLASS METHODS */
// Return new record (not persisted to db)
Record.new = function (params) {
  var Class = this
  var record = new Class()
  record.model = Class._modelName
  record._ns = Class._ns
  record.attrs = params
  return record
}

// Return new record (persisted to db)
Record.create = function (params) {
  var Class = this
  var record = Class.new(params)
  record.on('saved', function () { record.emit('created') })
  record.save()
  return record
}

// Get record from DB
Record.find = function (recordid) {
  var Class = this
  var model = Class.name.toLowerCase()
  var record = Class.new()
  var spName = Class._ns + '_' + model + '_read'
  var args = {id_array: [parseInt(recordid, 10)]}
  record._callSP(spName, args, function () {
    return record.emit('data')
  })
  return record
}

// Get records from DB
Record.findAll = function (query) {
  var Class = this
  var model = Class.name.toLowerCase()
  var evt = new Event()
  var pg = new Class.PG()
  var spName = Class._ns + '_' + model + '_search'
  pg.on('error', function (err) {
    evt.emit('error', err)
    return logger.fatal(err)
  })
  pg.on('data', function (data) {
    var records = data.rows[0][spName].map(function (u) {
      return Class.new(u)
    })
    evt.emit('data', records)
  })
  pg.on('ready', function () { pg.sp(spName, [query]) })
  return evt
}

/* INSTANCE HELPERS */
// SP Helper
Record.prototype._callSP = function (spName, args, callback) {
  var self = this
  var pg = new self.constructor.PG()
  pg.on('error', function (err) {
    self.emit('error', err)
    return logger.fatal(err)
  })
  pg.on('data', function (data) {
    self.attrs = data.rows[0][spName] ? data.rows[0][spName][0] : null
    callback(data)
  })
  pg.on('ready', function () { pg.sp(spName, [args]) })
}

/* INSTANCE METHODS */
// Save our record to db
Record.prototype.save = function () {
  var self = this
  var spName = self.new_record
    ? self._ns + '_' + self.model + '_create'
    : self._ns + '_' + self.model + '_update'
  self._callSP(spName, self.attrs, function () { return self.emit('saved') })
}

// Mark record deleted in db
Record.prototype.destroy = function (recordid) {
  var self = this
  var spName = self._ns + '_' + self.model + '_delete'
  var args = {id_array: [parseInt(recordid, 10)]}
  self._callSP(spName, args, function () { return self.emit('deleted') })
}
<|endoftext|>


<commit_before>'use strict';

exports = module.exports = Model;

var Table = require('./table');
var SQLiteTable = require('./datastores/sqlite3/table');

var inflection = require('inflection');
var util = require('util');
var utils = require('./utils');
var ModelInstance = require('./model-instance');

function Model(models) {
	this.models = models;
	this._table = null;
}

Model.prototype.setTable = function(table) {
	this._table = table;
};

Model.prototype.getAllProperties = function() {
	var properties = {};

	//now create the default properties
	this.id = [this.Id];
	properties.id = [this.Id];

	for(var propertyName in this) {
		var propertyTypes = this[propertyName];
		if(util.isArray(propertyTypes) && !Model.prototype[propertyName]) {
			properties[propertyName] = propertyTypes;
		}
	}

	return properties;
};

//todo: hijack some of these methods so we can cleanly implement associations
Model.prototype.propertyTypes = {
	Reference: function(modelOrName) {
		return function(columnName) {
			//todo: return a method :-)
		};
	}
};

for(var propertyName in Model.prototype.propertyTypes) {
	if(Model.prototype[propertyName]) {
		//todo: replace this with our Model#propertyTypes but still call it
		//this seperates SQL stuff from Model stuff
	}
}

Model.prototype.setup = function() {
	return this._table.create();
};

function toValues(map) {
	return Object.keys(map).map(function(propertyName) {
		var value = map[propertyName];

		if(value instanceof ModelInstance) {
			return value.id;
		}

		return value;
	});
}

Model.prototype._create = function(set) {
	return this._table.insert(Object.keys(set), toValues(set))
	.then(function(result) {
		return result.rows[0];
	});
};

Model.prototype.updateOne = function(where, set) {
	var self = this;
	return this._updateOne(where, set)
	.then(function(row) {
		return new ModelInstance(self, row, null);
	});
}

Model.prototype._updateOne = function(where, set) {
	return this._table.update(Object.keys(set), Object.keys(where), 1, toValues(set).concat(toValues(where)))
	.then(function(result) {
		return result.rows[0];
	});
};

Model.prototype.getOne = function() {
	return this.findOne.apply(this, Array.prototype.slice.call(arguments))
		.then(function(model) {
			if(model) {
				return model;
			}
			else {
				throw new Error();
			}
		})
		.fail(function(error) {
			throw error;
		});
};

Model.prototype.exists = function() {
	return this._table.exists();
}

/*
Model.prototype.count = function(where) {
	return this._table.count(Object.keys(where), Object.keys(where).map(function(propertyName) {
		return where[propertyName];
	}))
	.then(function(result) {
		console.dir(result);
	});
}
*/

Model.prototype.removeAll = function() {
	return this._table.remove([], []);
};

Model.prototype.remove = function(where) {
	var keys = Object.keys(where || {});
	if(!keys.length) {
		throw new Error('You are calling Model#remove without a `where` clause. This will result in removing all instances. This is disabled in Model#remove. Please invoke Model#removeAll instead.');
	}
	else {
		return this._table.remove(Object.keys(where), toValues(where))
		.then(function(result) {
			return true;
		});
	}
};

Model.prototype.findOrCreateOne = function(where, set) {
	var self = this;
	return this.findOne(where)
	.then(function(model) {
		if(!model) {
			return self.createOne(utils.merge(where, set));
		}
		else {
			return model;
		}
	});
}

Model.prototype.findOne = function(where, sort) {
	var self = this;
	return this._table.select(Object.keys(where), 1, toValues(where), sort)
	.then(function(result) {
		if(result.rowCount == 1) {
			return new ModelInstance(self, result.rows[0], null);
		}
		else {
			return null;
		}
	});
};

Model.prototype.createOne = function(fields) {
	return (new ModelInstance(this, null, fields)).save();
};

Model.prototype.destroy = function() {
	return this._table.drop();
};

Model.prototype.execute = function(query, params) {
	return this._table.query(query, params);
}

Model.prototype.addProperties = function(properties, persist) {
	var self = this;
	Object.keys(properties).forEach(function(propertyName) {
		self[propertyName] = properties[propertyName];
	});

	return this._table.addProperties(properties, persist);
};

Model.prototype.removeProperties = function(propertyNames, persist) {
	// We want to collect the property types as well, as we likely need it to calculate some column names in the table
	var properties = {};

	var self = this;
	propertyNames.forEach(function(propertyName) {
		properties[propertyName] = self[propertyName];
		delete self[propertyName];
	});

	return this._table.removeProperties(properties, persist);
};
<commit_msg>updateOne now throws an error if where clause does not match any instance<commit_after>'use strict';

exports = module.exports = Model;

var Table = require('./table');
var SQLiteTable = require('./datastores/sqlite3/table');

var inflection = require('inflection');
var util = require('util');
var utils = require('./utils');
var ModelInstance = require('./model-instance');

function Model(models) {
	this.models = models;
	this._table = null;
}

Model.prototype.setTable = function(table) {
	this._table = table;
};

Model.prototype.getAllProperties = function() {
	var properties = {};

	//now create the default properties
	this.id = [this.Id];
	properties.id = [this.Id];

	for(var propertyName in this) {
		var propertyTypes = this[propertyName];
		if(util.isArray(propertyTypes) && !Model.prototype[propertyName]) {
			properties[propertyName] = propertyTypes;
		}
	}

	return properties;
};

//todo: hijack some of these methods so we can cleanly implement associations
Model.prototype.propertyTypes = {
	Reference: function(modelOrName) {
		return function(columnName) {
			//todo: return a method :-)
		};
	}
};

for(var propertyName in Model.prototype.propertyTypes) {
	if(Model.prototype[propertyName]) {
		//todo: replace this with our Model#propertyTypes but still call it
		//this seperates SQL stuff from Model stuff
	}
}

Model.prototype.setup = function() {
	return this._table.create();
};

function toValues(map) {
	return Object.keys(map).map(function(propertyName) {
		var value = map[propertyName];

		if(value instanceof ModelInstance) {
			return value.id;
		}

		return value;
	});
}

Model.prototype._create = function(set) {
	return this._table.insert(Object.keys(set), toValues(set))
	.then(function(result) {
		return result.rows[0];
	});
};

Model.prototype.updateOne = function(where, set) {
	var self = this;
	return this._updateOne(where, set)
	.then(function(row) {
		if(row) {
			return new ModelInstance(self, row, null);
		}
		else {
			var error = new Error('Did not match any instance to update.');
			error.status = 400;
			throw error;
		}
	});
}

Model.prototype._updateOne = function(where, set) {
	return this._table.update(Object.keys(set), Object.keys(where), 1, toValues(set).concat(toValues(where)))
	.then(function(result) {
		return result.rows[0];
	});
};

Model.prototype.getOne = function() {
	return this.findOne.apply(this, Array.prototype.slice.call(arguments))
		.then(function(model) {
			if(model) {
				return model;
			}
			else {
				throw new Error();
			}
		})
		.fail(function(error) {
			throw error;
		});
};

Model.prototype.exists = function() {
	return this._table.exists();
}

/*
Model.prototype.count = function(where) {
	return this._table.count(Object.keys(where), Object.keys(where).map(function(propertyName) {
		return where[propertyName];
	}))
	.then(function(result) {
		console.dir(result);
	});
}
*/

Model.prototype.removeAll = function() {
	return this._table.remove([], []);
};

Model.prototype.remove = function(where) {
	var keys = Object.keys(where || {});
	if(!keys.length) {
		throw new Error('You are calling Model#remove without a `where` clause. This will result in removing all instances. This is disabled in Model#remove. Please invoke Model#removeAll instead.');
	}
	else {
		return this._table.remove(Object.keys(where), toValues(where))
		.then(function(result) {
			return true;
		});
	}
};

Model.prototype.findOrCreateOne = function(where, set) {
	var self = this;
	return this.findOne(where)
	.then(function(model) {
		if(!model) {
			return self.createOne(utils.merge(where, set));
		}
		else {
			return model;
		}
	});
}

Model.prototype.findOne = function(where, sort) {
	var self = this;
	return this._table.select(Object.keys(where), 1, toValues(where), sort)
	.then(function(result) {
		if(result.rowCount == 1) {
			return new ModelInstance(self, result.rows[0], null);
		}
		else {
			return null;
		}
	});
};

Model.prototype.createOne = function(fields) {
	return (new ModelInstance(this, null, fields)).save();
};

Model.prototype.destroy = function() {
	return this._table.drop();
};

Model.prototype.execute = function(query, params) {
	return this._table.query(query, params);
}

Model.prototype.addProperties = function(properties, persist) {
	var self = this;
	Object.keys(properties).forEach(function(propertyName) {
		self[propertyName] = properties[propertyName];
	});

	return this._table.addProperties(properties, persist);
};

Model.prototype.removeProperties = function(propertyNames, persist) {
	// We want to collect the property types as well, as we likely need it to calculate some column names in the table
	var properties = {};

	var self = this;
	propertyNames.forEach(function(propertyName) {
		properties[propertyName] = self[propertyName];
		delete self[propertyName];
	});

	return this._table.removeProperties(properties, persist);
};
<|endoftext|>


<commit_before>module.exports = function(network, chan, cmd, args) {
    if (cmd != "kick") {
        return;
    }
    if (args.length !== 0) {
        var irc = network.irc;
        irc.kick(chan.name, args[0]);
    }
};
<commit_msg>Lint fix: kick.js<commit_after>/*eslint quotes: [2, "single"]*/
'use strict';

module.exports = function(network, chan, cmd, args) {
    if (cmd !== 'kick') {
        return;
    }
    if (args.length !== 0) {
        var irc = network.irc;
        irc.kick(chan.name, args[0]);
    }
};
<|endoftext|>