<commit_before>var path = require('path'),
  Autowire = require('wantsit').Autowire,
  async = require('async')

var CLI = function() {
  this._connect = Autowire
  this._running = Autowire
  this._logger = Autowire
  this._config = Autowire
  this._posix = Autowire
  this._commander = Autowire
  this._package = Autowire
  this._execSync = Autowire
  this._prompt = Autowire
  this._running = Autowire
  this._fs = Autowire
  this._os = Autowire

  this._apps = Autowire
  this._cluster = Autowire
  this._daemon = Autowire
  this._processes = Autowire
  this._remote = Autowire

  this._user = Autowire
  this._group = Autowire
}

CLI.prototype.afterPropertiesSet = function() {
  this._logger.debug('Loaded config from', this._config._rcfiles)

  async.series([
    this._checkBossUser.bind(this),
    this._checkBossGroup.bind(this)
  ], function(error) {
    if(error) throw error

    this._setUpCommander()
  }.bind(this))
}

CLI.prototype._setUpCommander = function() {

  this._commander
    .version(this._package.version)

  this._commander
    .command('list')
      .description('List all running processes')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.list.bind(this._processes))

  this._commander
    .command('start <scriptOrAppName>')
      .description('Start a process')
      .option('-u, --user <user>', 'The user to start a process as')
      .option('-g, --group <group>', 'The group to start a process as')
      .option('-i, --instances <instances>', 'How many instances of the process to start', parseInt)
      .option('-n, --name <name>', 'What name to give the process. If omitted and there is a package.json in the same directory as your script, it will be loaded and the name property used automatically.')
      .option('-a, --argv <argv>', 'A space separated list of arguments to pass to a process', this._parseList, [])
      .option('-e, --execArgv <execArgv>', 'A space separated list of arguments to pass to the node executable', this._parseList, [])
      .option('-d, --debug', 'Pause the process at the start of execution and wait for a debugger to be attached')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.start.bind(this._processes))

  this._commander
    .command('workers <pidOrName> <workers>')
      .description('Set the number of workers managed by the cluster manager with the passed pid/name')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._cluster.setClusterWorkers.bind(this._cluster))

  this._commander
    .command('stop <pidOrName...>')
      .description('Stop one or more processes')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.stop.bind(this._processes))

  this._commander
    .command('restart <pidOrName...>')
    .description('Restart one or more processes')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._processes.restart.bind(this._processes))

  this._commander
    .command('send <pidOrName> <event> [args...]')
      .description('Causes process.emit(event, args[0], args[1]...) to occur in the process')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.send.bind(this._processes))

  this._commander
    .command('heapdump <pidOrName>')
      .description('Write out a snapshot of the processes memory for inspection')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.heapdump.bind(this._processes))

  this._commander
    .command('gc <pidOrName>')
    .description('Force garbage collection to occur in the process with the passed pid/name')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._processes.gc.bind(this._processes))

  this._commander
    .command('signal <pidOrName> <signal>')
    .description('Sends a signal to a process (SIGUSR1, SIGINT, SIGHUP, SIGTERM, etc)')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._processes.signal.bind(this._processes))

  this._commander
    .command('logs [pidOrName]')
      .description('Show realtime process logs, optionally filtering by pid/name')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._daemon.logs.bind(this._daemon))

  this._commander
    .command('kill')
    .description('Stop all processes and kill the daemon')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._daemon.kill.bind(this._daemon))

  this._commander
    .command('dump')
      .description('Dumps process data to ' + this._config.boss.confdir + '/processes.json')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._daemon.dump.bind(this._daemon))

  this._commander
    .command('restore')
      .description('Restores processes from ' + this._config.boss.confdir + '/processes.json')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._daemon.restore.bind(this._daemon))

  this._commander
    .command('config <path>')
    .description('Print a config option')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._daemon.config.bind(this._daemon))

  this._commander
    .command('status')
    .description('Returns whether the daemon is running or not')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._daemon.status.bind(this._daemon))

  this._commander
    .command('remoteconfig [hostname]')
    .description('Prints the remote host config for boss-web')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.remoteHostConfig.bind(this._remote))

  this._commander
    .command('useradd <username>')
    .description('Adds a user for use with boss-web')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.addRemoteUser.bind(this._remote))

  this._commander
    .command('rmuser <username>')
    .description('Removes a user from the boss-web user list')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.deleteRemoteUser.bind(this._remote))

  this._commander
    .command('lsusers')
    .description('Prints out all remote users')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.listRemoteUsers.bind(this._remote))

  this._commander
    .command('reset <username>')
    .description('Generate a new secret for the passed user')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.rotateRemoteUserKeys.bind(this._remote))

  this._commander
    .command('genssl [days]')
    .description('Generates self-signed SSL certificates for use between boss and boss-web that will expire in the passed number of days (defaults to 365)')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.generateSSLCertificate.bind(this._remote))

  this._commander
    .command('install <url> [appName]')
    .description('Installs an application from a git repository')
    .option('-u, --user <user>', 'The user to install as - n.b. the current user must be able to su to that user')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.installApplication.bind(this._apps))

  this._commander
    .command('lsapps')
    .description('List applications that have been deployed from git repositories')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.listApplications.bind(this._apps))

  this._commander
    .command('rmapp <appName>')
    .description('Remote deployed application')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.removeApplication.bind(this._apps))

  this._commander
    .command('lsrefs <appName>')
    .description('Lists app refs available to be started')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.listRefs.bind(this._apps))

  this._commander
    .command('updaterefs <appName>')
    .description('Updates app refs available to be started')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.updateRefs.bind(this._apps))

  this._commander
    .command('setref <appName> <ref>')
    .description('Checks out the app at the passed ref')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.updateRefs.bind(this._apps))

  this._commander
    .command('*')
    .description('')
    .action(this.unknown.bind(this))

  var program = this._commander.parse(process.argv)

  // No command
  if(program.rawArgs.length == 2) {
    this._processes.list()
  }
}

CLI.prototype._checkBossUser = function(callback) {
  if(this._user.name == this._config.boss.user) {
    return callback()
  }

  var bossGroup = this._posix.getgrnam(this._config.boss.group)

  if(bossGroup.members.indexOf(this._user.name) == -1) {
    this._logger.error('Boss has been configured to run as %s:%s but user %s is not in the group %s', this._config.boss.user, this._config.boss.group , this._user.name, this._config.boss.group)
    this._logger.error('')

    if(this._os.platform() == 'linux') {
      this._logger.error('To fix this run:')
      this._logger.error('')
      this._logger.error('$ sudo usermod -G %s -a %s', this._config.boss.group, this._user.name)
      this._logger.error('')
      this._logger.error('Note you may need to log out and in again for the new group membership to take effect.')
      this._logger.error('')
    } else if(this._os.platform() == 'darwin') {
      this._logger.error('To fix this run:')
      this._logger.error('')
      this._logger.error('$ sudo dscl . append /Groups/%s GroupMembership %s', this._config.boss.group, this._user.name)
      this._logger.error('')
      this._logger.error('Note you may need to log out and in again for the new group membership to take effect.')
      this._logger.error('')
    } else {
      this._logger.error('To fix this add % to the % group.', this._user.name, this._config.boss.group)
      this._logger.error('')
    }

    return callback(new Error('User is in the wrong group'))
  }

  this._running(function(running) {
    callback(running ? undefined : new Error('The daemon is not running. Please run this command as ' + this._config.boss.user + ' to start the daemon.'))
  }.bind(this))
}

CLI.prototype._checkBossGroup = function(callback) {
  try {
    this._posix.getgrnam(this._config.boss.group)

    return callback()
  } catch(error) {
    if(error.message == 'group id does not exist') {

      this._logger.warn('Boss has been configured to start with the group \'%s\' but that group does not exist', this._config.boss.group)
      this._prompt.start()
      this._prompt.get([{
        name: 'create group \'' + this._config.boss.group + '\' [Y/n]',
        default: 'Y'
      }], function (err, result) {
        this._logger.debug('Creating group', this._config.boss.group)

        // ugh http://www.greenend.org.uk/rjk/tech/useradd.html

        // linux
        var command = this._execSync.exec('which groupadd').stdout.trim()

        if(command) {
          command = command + ' ' + this._config.boss.group
          this._logger.debug(command)
          this._execSync.run(command + ' ' + this._config.boss.group)

          return callback()
        }

        // mac os x
        command = this._execSync.exec('which dscl').stdout.trim()

        if(command) {
          // gids over 500 will appear in the system preferences window
          var gid = 500

          // find a gid
          this._execSync.exec(command + ' . -list /Groups PrimaryGroupID').stdout.trim().split('\n').forEach(function(line) {
            line = line.replace(/\s+/g, ' ')
            var parts = line.split(' ')

            var existingGid = parseInt(parts[1], 10)

            if(gid == existingGid) {
              gid = existingGid + 1
            }
          })

          this._logger.debug(this._config.boss.group + ' will have gid ' + gid)

          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group)
          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group + ' name ' + this._config.boss.group)
          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group + ' passwd "*"')
          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group + ' gid ' + gid)

          this._fs.appendFileSync('/etc/group', this._config.boss.group + ':*:' + gid + ':\n')

          return callback()
        }

        callback(new Error('I don\'t know how to create groups, sorry!'))
      }.bind(this))
    } else {
      callback(error)
    }
  }
}

// takes ['foo', '"bar', 'baz"'] and turns it into ['foo', 'bar baz']
CLI.prototype._parseList = function(value) {
  if(!value) {
    return []
  }

  var output = []
  var token = ''
  var ignoreNext
  var inBlock
  var blockDelimiter

  value.split('').forEach(function(char) {
    if(char == '\\') {
      ignoreNext = true
      return
    }

    if(ignoreNext) {
      ignoreNext = false
      token += char
      return
    }

    if((char == '"' || char == "'") && !inBlock) {
      inBlock = true
      blockDelimiter = char
      //token += char
      return
    }

    if(inBlock) {
      if(char != blockDelimiter) {
        token += char
      }

      if(char == blockDelimiter) {
        inBlock = false
        blockDelimiter = null
        output.push(token)
        token = ''
      }
    } else if(char == ' ') {
      output.push(token)
      token = ''
    } else {
      token += char
    }
  })

  if(token.trim()) {
    output.push(token)
  }

  return output
}

CLI.prototype.unknown = function() {
  this._logger.error('Please specify a known subcommand. See \'' + this._commander.name() + ' --help\' for commands.')
}

module.exports = CLI
<commit_msg>better error messages<commit_after>var path = require('path'),
  Autowire = require('wantsit').Autowire,
  async = require('async')

var CLI = function() {
  this._connect = Autowire
  this._running = Autowire
  this._logger = Autowire
  this._config = Autowire
  this._posix = Autowire
  this._commander = Autowire
  this._package = Autowire
  this._execSync = Autowire
  this._prompt = Autowire
  this._running = Autowire
  this._fs = Autowire
  this._os = Autowire

  this._apps = Autowire
  this._cluster = Autowire
  this._daemon = Autowire
  this._processes = Autowire
  this._remote = Autowire

  this._user = Autowire
  this._group = Autowire
}

CLI.prototype.afterPropertiesSet = function() {
  this._logger.debug('Loaded config from', this._config._rcfiles)

  async.series([
    this._checkBossUser.bind(this),
    this._checkBossGroup.bind(this)
  ], function(error) {
    if(error) throw error

    this._setUpCommander()
  }.bind(this))
}

CLI.prototype._setUpCommander = function() {

  this._commander
    .version(this._package.version)

  this._commander
    .command('list')
      .description('List all running processes')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.list.bind(this._processes))

  this._commander
    .command('start <scriptOrAppName>')
      .description('Start a process')
      .option('-u, --user <user>', 'The user to start a process as')
      .option('-g, --group <group>', 'The group to start a process as')
      .option('-i, --instances <instances>', 'How many instances of the process to start', parseInt)
      .option('-n, --name <name>', 'What name to give the process. If omitted and there is a package.json in the same directory as your script, it will be loaded and the name property used automatically.')
      .option('-a, --argv <argv>', 'A space separated list of arguments to pass to a process', this._parseList, [])
      .option('-e, --execArgv <execArgv>', 'A space separated list of arguments to pass to the node executable', this._parseList, [])
      .option('-d, --debug', 'Pause the process at the start of execution and wait for a debugger to be attached')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.start.bind(this._processes))

  this._commander
    .command('workers <pidOrName> <workers>')
      .description('Set the number of workers managed by the cluster manager with the passed pid/name')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._cluster.setClusterWorkers.bind(this._cluster))

  this._commander
    .command('stop <pidOrName...>')
      .description('Stop one or more processes')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.stop.bind(this._processes))

  this._commander
    .command('restart <pidOrName...>')
    .description('Restart one or more processes')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._processes.restart.bind(this._processes))

  this._commander
    .command('send <pidOrName> <event> [args...]')
      .description('Causes process.emit(event, args[0], args[1]...) to occur in the process')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.send.bind(this._processes))

  this._commander
    .command('heapdump <pidOrName>')
      .description('Write out a snapshot of the processes memory for inspection')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._processes.heapdump.bind(this._processes))

  this._commander
    .command('gc <pidOrName>')
    .description('Force garbage collection to occur in the process with the passed pid/name')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._processes.gc.bind(this._processes))

  this._commander
    .command('signal <pidOrName> <signal>')
    .description('Sends a signal to a process (SIGUSR1, SIGINT, SIGHUP, SIGTERM, etc)')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._processes.signal.bind(this._processes))

  this._commander
    .command('logs [pidOrName]')
      .description('Show realtime process logs, optionally filtering by pid/name')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._daemon.logs.bind(this._daemon))

  this._commander
    .command('kill')
    .description('Stop all processes and kill the daemon')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._daemon.kill.bind(this._daemon))

  this._commander
    .command('dump')
      .description('Dumps process data to ' + this._config.boss.confdir + '/processes.json')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._daemon.dump.bind(this._daemon))

  this._commander
    .command('restore')
      .description('Restores processes from ' + this._config.boss.confdir + '/processes.json')
      .option('-v, --verbose', 'Prints detailed internal logging output')
      .action(this._daemon.restore.bind(this._daemon))

  this._commander
    .command('config <path>')
    .description('Print a config option')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._daemon.config.bind(this._daemon))

  this._commander
    .command('status')
    .description('Returns whether the daemon is running or not')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._daemon.status.bind(this._daemon))

  this._commander
    .command('remoteconfig [hostname]')
    .description('Prints the remote host config for boss-web')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.remoteHostConfig.bind(this._remote))

  this._commander
    .command('useradd <username>')
    .description('Adds a user for use with boss-web')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.addRemoteUser.bind(this._remote))

  this._commander
    .command('rmuser <username>')
    .description('Removes a user from the boss-web user list')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.deleteRemoteUser.bind(this._remote))

  this._commander
    .command('lsusers')
    .description('Prints out all remote users')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.listRemoteUsers.bind(this._remote))

  this._commander
    .command('reset <username>')
    .description('Generate a new secret for the passed user')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.rotateRemoteUserKeys.bind(this._remote))

  this._commander
    .command('genssl [days]')
    .description('Generates self-signed SSL certificates for use between boss and boss-web that will expire in the passed number of days (defaults to 365)')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._remote.generateSSLCertificate.bind(this._remote))

  this._commander
    .command('install <url> [appName]')
    .description('Installs an application from a git repository')
    .option('-u, --user <user>', 'The user to install as - n.b. the current user must be able to su to that user')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.installApplication.bind(this._apps))

  this._commander
    .command('lsapps')
    .description('List applications that have been deployed from git repositories')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.listApplications.bind(this._apps))

  this._commander
    .command('rmapp <appName>')
    .description('Remote deployed application')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.removeApplication.bind(this._apps))

  this._commander
    .command('lsrefs <appName>')
    .description('Lists app refs available to be started')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.listRefs.bind(this._apps))

  this._commander
    .command('updaterefs <appName>')
    .description('Updates app refs available to be started')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.updateRefs.bind(this._apps))

  this._commander
    .command('setref <appName> <ref>')
    .description('Checks out the app at the passed ref')
    .option('-v, --verbose', 'Prints detailed internal logging output')
    .action(this._apps.updateRefs.bind(this._apps))

  this._commander
    .command('*')
    .description('')
    .action(this.unknown.bind(this))

  var program = this._commander.parse(process.argv)

  // No command
  if(program.rawArgs.length == 2) {
    this._processes.list()
  }
}

CLI.prototype._checkBossUser = function(callback) {
  if(this._user.name == this._config.boss.user) {
    return callback()
  }

  var bossGroup

  try {
    bossGroup = this._posix.getgrnam(this._config.boss.group)
  } catch(e) {
    if(e.message != 'group id does not exist') {
      return callback(e)
    }

    this._logger.error('Boss has been configured to run as %s:%s but the %s group does not exist.', this._config.boss.user, this._config.boss.group, this._config.boss.group)
    this._logger.error('')
    this._logger.error('To fix this run boss as %s and it will attempt to create the %s group.', this._config.boss.user, this._config.boss.group)
    this._logger.error('')

    return callback(new Error(this._config.boss.group + ' group does not exist'))
  }

  if(bossGroup.members.indexOf(this._user.name) == -1) {
    this._logger.error('Boss has been configured to run as %s:%s but user %s is not in the group %s', this._config.boss.user, this._config.boss.group , this._user.name, this._config.boss.group)
    this._logger.error('')

    if(this._os.platform() == 'linux') {
      this._logger.error('To fix this run:')
      this._logger.error('')
      this._logger.error('$ sudo usermod -G %s -a %s', this._config.boss.group, this._user.name)
      this._logger.error('')
    } else if(this._os.platform() == 'darwin') {
      this._logger.error('To fix this run:')
      this._logger.error('')
      this._logger.error('$ sudo dscl . append /Groups/%s GroupMembership %s', this._config.boss.group, this._user.name)
      this._logger.error('')
    } else {
      this._logger.error('To fix this add % to the % group.', this._user.name, this._config.boss.group)
      this._logger.error('')
    }

    this._logger.error('Note you may need to log out and in again for the new group membership to take effect.')
    this._logger.error('')

    return callback(new Error('User is in the wrong group'))
  }

  this._running(function(running) {
    callback(running ? undefined : new Error('The daemon is not running. Please run this command as ' + this._config.boss.user + ' to start the daemon.'))
  }.bind(this))
}

CLI.prototype._checkBossGroup = function(callback) {
  try {
    this._posix.getgrnam(this._config.boss.group)

    return callback()
  } catch(error) {
    if(error.message == 'group id does not exist') {

      this._logger.warn('Boss has been configured to start with the group \'%s\' but that group does not exist', this._config.boss.group)
      this._prompt.start()
      this._prompt.get([{
        name: 'create group \'' + this._config.boss.group + '\' [Y/n]',
        default: 'Y'
      }], function (err, result) {
        this._logger.debug('Creating group', this._config.boss.group)

        // ugh http://www.greenend.org.uk/rjk/tech/useradd.html

        // linux
        var command = this._execSync.exec('which groupadd').stdout.trim()

        if(command) {
          command = command + ' ' + this._config.boss.group
          this._logger.debug(command)
          this._execSync.run(command + ' ' + this._config.boss.group)

          return callback()
        }

        // mac os x
        command = this._execSync.exec('which dscl').stdout.trim()

        if(command) {
          // gids over 500 will appear in the system preferences window
          var gid = 500

          // find a gid
          this._execSync.exec(command + ' . -list /Groups PrimaryGroupID').stdout.trim().split('\n').forEach(function(line) {
            line = line.replace(/\s+/g, ' ')
            var parts = line.split(' ')

            var existingGid = parseInt(parts[1], 10)

            if(gid == existingGid) {
              gid = existingGid + 1
            }
          })

          this._logger.debug(this._config.boss.group + ' will have gid ' + gid)

          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group)
          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group + ' name ' + this._config.boss.group)
          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group + ' passwd "*"')
          this._execSync.run(command + ' . create /Groups/' + this._config.boss.group + ' gid ' + gid)

          this._fs.appendFileSync('/etc/group', this._config.boss.group + ':*:' + gid + ':\n')

          return callback()
        }

        callback(new Error('I don\'t know how to create groups, sorry!'))
      }.bind(this))
    } else {
      callback(error)
    }
  }
}

// takes ['foo', '"bar', 'baz"'] and turns it into ['foo', 'bar baz']
CLI.prototype._parseList = function(value) {
  if(!value) {
    return []
  }

  var output = []
  var token = ''
  var ignoreNext
  var inBlock
  var blockDelimiter

  value.split('').forEach(function(char) {
    if(char == '\\') {
      ignoreNext = true
      return
    }

    if(ignoreNext) {
      ignoreNext = false
      token += char
      return
    }

    if((char == '"' || char == "'") && !inBlock) {
      inBlock = true
      blockDelimiter = char
      //token += char
      return
    }

    if(inBlock) {
      if(char != blockDelimiter) {
        token += char
      }

      if(char == blockDelimiter) {
        inBlock = false
        blockDelimiter = null
        output.push(token)
        token = ''
      }
    } else if(char == ' ') {
      output.push(token)
      token = ''
    } else {
      token += char
    }
  })

  if(token.trim()) {
    output.push(token)
  }

  return output
}

CLI.prototype.unknown = function() {
  this._logger.error('Please specify a known subcommand. See \'' + this._commander.name() + ' --help\' for commands.')
}

module.exports = CLI
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs'),
    Q = require('q'),
    path = require('path'),
    archiver = require('archiver'),
    Readable = require('lazystream').Readable;

// Download and unzip/untar the node wekit files from aws
// Mostly copied from grunt-contrib-compress
module.exports = function(grunt) {
    var utils = require('./utils')(grunt);

    // Generate a Zip file from a directory and a destination path
    // and returns back a read stream
    exports.generateZip = function(files, dest) {
        var package_path = null,
            zipDone = Q.defer(),
            destFiles = [],
            archive = archiver('zip'),
            destStream = fs.createWriteStream(dest);

        // Getting the paths we need
        files.forEach(function(file) {
            var src = file.src.filter(function(f) {
                return grunt.file.isFile(f);
            });

            src.forEach(function(srcFile) {
                var internalFileName = path.normalize(utils.unixifyPath(srcFile));

                if ( !package_path ) {

                    // We need to make sure that the package.json is in the root
                    if (internalFileName.match('package.json') && !internalFileName.match('node_modules')) {
                        package_path = path.normalize(internalFileName.split('package.json')[0]);
                    }
                }

                destFiles.push(internalFileName);
            });
        });

        if(!package_path) {
            grunt.fail.warn('Could not find a package.json in your src folder');
        }

        // Resolve on close
        destStream.on('close', zipDone.resolve);

        // Report Error
        archive.on('error', function(err) {
          grunt.log.error(err);
          grunt.fail.warn('Archiving failed.');
        });

        // Pipe archive to the destStream
        archive.pipe(destStream);

        // Push the files into the zip stream
        destFiles.forEach(function(srcFile) {
            var internalFileName = srcFile.replace(package_path, '');
            var srcStream = new Readable(function() {
              return fs.createReadStream(srcFile);
            });
            archive.append(srcStream, { name: internalFileName }, function(err) {
              grunt.verbose.writeln('Archiving ' + srcFile + ' -> ' + '/' + internalFileName.cyan);
            });
        });

        archive.finalize();

        return zipDone.promise;
    };

    exports.cleanUpRelease = function(nwPath) {

        if(grunt.file.exists(nwPath)) {
            grunt.file.delete(nwPath, {
              force: true
            });
        }
    };

    exports.generateRelease = function(relaseFile, zipPath, type, nwpath) {
        var releaseDone = Q.defer(),
            ws = fs.createWriteStream(relaseFile),
            zipStream = fs.createReadStream(zipPath),
            nwpath_rs;

        ws.on('error', function(err) {
            grunt.fail.fatal(err);
        });

        ws.on('close', function() {
            releaseDone.resolve(type);
        });

        if(type === 'mac') {
            // On osx just copy to the target location
            zipStream.pipe(ws);
        } else {
            // on windows and linux cat the node-webkit with the nw archive
            nwpath_rs = fs.createReadStream(nwpath);

            nwpath_rs.on('error', function(err) {
                grunt.fail.fatal(err);
            });

            nwpath_rs.on('end', function(){
                zipStream.pipe(ws);
            });

            nwpath_rs.pipe(ws, { end: false });
        }

        return releaseDone.promise;
    };

    return exports;
};
<commit_msg>sanitize relative path reference<commit_after>'use strict';

var fs = require('fs'),
    Q = require('q'),
    path = require('path'),
    archiver = require('archiver'),
    Readable = require('lazystream').Readable;

// Download and unzip/untar the node wekit files from aws
// Mostly copied from grunt-contrib-compress
module.exports = function(grunt) {
    var utils = require('./utils')(grunt);

    // Generate a Zip file from a directory and a destination path
    // and returns back a read stream
    exports.generateZip = function(files, dest) {
        var package_path = null,
            zipDone = Q.defer(),
            destFiles = [],
            archive = archiver('zip'),
            destStream = fs.createWriteStream(dest);

        // Getting the paths we need
        files.forEach(function(file) {
            var src = file.src.filter(function(f) {
                return grunt.file.isFile(f);
            });

            src.forEach(function(srcFile) {
                var internalFileName = path.normalize(utils.unixifyPath(srcFile));

                if ( !package_path ) {

                    // We need to make sure that the package.json is in the root
                    if (internalFileName.match('package.json') && !internalFileName.match('node_modules')) {
                        package_path = path.normalize(internalFileName.split('package.json')[0] || './' );
                    }
                }

                destFiles.push(internalFileName);
            });
        });

        if(!package_path) {
            grunt.fail.warn('Could not find a package.json in your src folder');
        }

        // Resolve on close
        destStream.on('close', zipDone.resolve);

        // Report Error
        archive.on('error', function(err) {
          grunt.log.error(err);
          grunt.fail.warn('Archiving failed.');
        });

        // Pipe archive to the destStream
        archive.pipe(destStream);

        // Push the files into the zip stream
        destFiles.forEach(function(srcFile) {
            var internalFileName = srcFile.replace(package_path, '');
            var srcStream = new Readable(function() {
              return fs.createReadStream(srcFile);
            });
            archive.append(srcStream, { name: internalFileName }, function(err) {
              grunt.verbose.writeln('Archiving ' + srcFile + ' -> ' + '/' + internalFileName.cyan);
            });
        });

        archive.finalize();

        return zipDone.promise;
    };

    exports.cleanUpRelease = function(nwPath) {

        if(grunt.file.exists(nwPath)) {
            grunt.file.delete(nwPath, {
              force: true
            });
        }
    };

    exports.generateRelease = function(relaseFile, zipPath, type, nwpath) {
        var releaseDone = Q.defer(),
            ws = fs.createWriteStream(relaseFile),
            zipStream = fs.createReadStream(zipPath),
            nwpath_rs;

        ws.on('error', function(err) {
            grunt.fail.fatal(err);
        });

        ws.on('close', function() {
            releaseDone.resolve(type);
        });

        if(type === 'mac') {
            // On osx just copy to the target location
            zipStream.pipe(ws);
        } else {
            // on windows and linux cat the node-webkit with the nw archive
            nwpath_rs = fs.createReadStream(nwpath);

            nwpath_rs.on('error', function(err) {
                grunt.fail.fatal(err);
            });

            nwpath_rs.on('end', function(){
                zipStream.pipe(ws);
            });

            nwpath_rs.pipe(ws, { end: false });
        }

        return releaseDone.promise;
    };

    return exports;
};
<|endoftext|>


<commit_before>function FVPresentSum(PV, n, i)
//Returns the Future Value of a Present Sum
//PV = Present Value
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_a_present_sum
{
	return PV * Math.pow(1+i, n);
}

function PVFutureSum(FV, n, i)
//Returns the Present Value of a Future Sum
//FV = Future Value
//n = Number of Periods
//i = Interest Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_future_sum
{
	return FV / Math.pow(1+i, n);
}

function PVAnnuity(A, n, i)
//Returns the Present Value of an Annuity
//A = Annuity
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_an_annuity_for_n_payment_periods
{
	return A / i * (1-1/Math.pow(1+i,n));
}

function PVAnnuityDue(A, n, i)
//Returns the Present Value of an Annuity Due
//A = Annuity
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_an_annuity_for_n_payment_periods
{
	return A / i * (1-1/Math.pow(1+i,n)) * (1+i);
}

function PVGrowingAnnuity(A, n, i, g)
//Returns the Present Value of a Growing Annuity
//A = Annuity
//n = Number of Periods
//i - Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A / (i-g) * (1-Math.pow((1+g)/(1+i),n));
	}
	else
	{
		return A * n / (1+i);
	}
}

function PVGrowingAnnuityDue(A, n, i, g)
//Returns the Present Value of a Growing Annuity Due
//A = Annuity
//n = Number of Periods
//i - Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A / (i-g) * (1-Math.pow((1+g)/(1+i),n)) * (1+i);
	}
	else
	{
		return A * n;
	}
}

function PVGrowingAnnuityDue1stPmt(PV, n, i, g)
//returns the first annuity payment when the present value of a growing annuity due is known
//A = Annuity
//n = Number of Periods
//i - Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_growing_annuity
{
	if (i != g)
	{
		return PV * (i-g) / (1-Math.pow((1+g)/(1+i),n)) / (1+i);
	}
	else
	{
		return PV / n;
	}
}

function PVPerpetuity(A, i)
//returns the Present Value of a Perpetuity
//A = Annuity Payment
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_perpetuity
{
	return A / i;
}

function PVGrowingPerpetuity(A, i, g)
//returns the Present Value of a Growing Perpetuity
//A = Annuity Payment
//i = Interest Rate
//g = Growth Rate
//http://financeformulas.net/Present_Value_of_Growing_Perpetuity.html
{
	return A / (i - g);
}

function FVGrowingAnnuity(A, n, i, g)
//Returns the Future Value of a Growing Annuity
//A = Annuity Payment
//n = Number of Periods
//i = Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A * (Math.pow(1+i, n) - Math.pow(1+g, n))/(i-g);
	}
	else
	{
		return A * n * Math.pow(1+i , n-1);
	}
}

function FVGrowingAnnuityDue(A, n, i, g)
//Returns the Future Value of a Growing Annuity Due
//A = Annuity Payment
//n = Number of Periods
//i = Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A * (Math.pow(1+i, n) - Math.pow(1+g, n))/(i-g) * (1 + i);
	}
	else
	{
		return A * n * Math.pow(1+i , n);
	}
}
<commit_msg>add FVAnnuity<commit_after>function FVPresentSum(PV, n, i)
//Returns the Future Value of a Present Sum
//PV = Present Value
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_a_present_sum
{
	return PV * Math.pow(1+i, n);
}

function PVFutureSum(FV, n, i)
//Returns the Present Value of a Future Sum
//FV = Future Value
//n = Number of Periods
//i = Interest Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_future_sum
{
	return FV / Math.pow(1+i, n);
}

function PVAnnuity(A, n, i)
//Returns the Present Value of an Annuity
//A = Annuity
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_an_annuity_for_n_payment_periods
{
	return A / i * (1-1/Math.pow(1+i,n));
}

function PVAnnuityDue(A, n, i)
//Returns the Present Value of an Annuity Due
//A = Annuity
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_an_annuity_for_n_payment_periods
{
	return A / i * (1-1/Math.pow(1+i,n)) * (1+i);
}

function PVGrowingAnnuity(A, n, i, g)
//Returns the Present Value of a Growing Annuity
//A = Annuity
//n = Number of Periods
//i - Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A / (i-g) * (1-Math.pow((1+g)/(1+i),n));
	}
	else
	{
		return A * n / (1+i);
	}
}

function PVGrowingAnnuityDue(A, n, i, g)
//Returns the Present Value of a Growing Annuity Due
//A = Annuity
//n = Number of Periods
//i - Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A / (i-g) * (1-Math.pow((1+g)/(1+i),n)) * (1+i);
	}
	else
	{
		return A * n;
	}
}

function PVGrowingAnnuityDue1stPmt(PV, n, i, g)
//returns the first annuity payment when the present value of a growing annuity due is known
//A = Annuity
//n = Number of Periods
//i - Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_growing_annuity
{
	if (i != g)
	{
		return PV * (i-g) / (1-Math.pow((1+g)/(1+i),n)) / (1+i);
	}
	else
	{
		return PV / n;
	}
}

function PVPerpetuity(A, i)
//returns the Present Value of a Perpetuity
//A = Annuity Payment
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Present_value_of_a_perpetuity
{
	return A / i;
}

function PVGrowingPerpetuity(A, i, g)
//returns the Present Value of a Growing Perpetuity
//A = Annuity Payment
//i = Interest Rate
//g = Growth Rate
//http://financeformulas.net/Present_Value_of_Growing_Perpetuity.html
{
	return A / (i - g);
}

function FVAnnuity(A, n, i)
//returns the Future Value of an Annuity
//A = Annuity Payment
//n = Number of Periods
//i = Interest Rate
//https://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_an_annuity
{
	return A * (Math.pow(1+i, n) - 1) / i;
}

function FVGrowingAnnuity(A, n, i, g)
//Returns the Future Value of a Growing Annuity
//A = Annuity Payment
//n = Number of Periods
//i = Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A * (Math.pow(1+i, n) - Math.pow(1+g, n))/(i-g);
	}
	else
	{
		return A * n * Math.pow(1+i , n-1);
	}
}

function FVGrowingAnnuityDue(A, n, i, g)
//Returns the Future Value of a Growing Annuity Due
//A = Annuity Payment
//n = Number of Periods
//i = Interest Rate
//g = Growth Rate
//http://en.wikipedia.org/wiki/Time_value_of_money#Future_value_of_a_growing_annuity
{
	if (i != g)
	{
		return A * (Math.pow(1+i, n) - Math.pow(1+g, n))/(i-g) * (1 + i);
	}
	else
	{
		return A * n * Math.pow(1+i , n);
	}
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed 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.
 */
FOAModel({
  name: 'AbstractChoiceView',

  extendsModel: 'View',

  properties: [
    // This is the real, final choice. The internals use index only.
    // When useSelection is enabled, data is not set until a final choice is made.
    {
      model_: 'BooleanProperty',
      name: 'autoSetData',
      help: 'If true, this.data is set when choices update and the current data is not one of the choices.',
      defaultValue: true
    },
    {
      name: 'data',
      help: 'The value of the current choice (ie. [value, label] -> value).',
      postSet: function(_, d) {
        for ( var i = 0 ; i < this.choices.length ; i++ ) {
          if ( this.choices[i][0] === d ) {
            if ( this.index !== i ) this.index = i;
            return;
          }
        }
      }
    },
    {
      name: 'label',
      help: 'The label of the current choice (ie. [value, label] -> label).',
      postSet: function(_, d) {
        for ( var i = 0 ; i < this.choices.length ; i++ ) {
          if ( this.choices[i][1] === d ) {
            if ( this.index !== i ) this.index = i;
            return;
          }
        }
      }
    },
    // See above; choice works the same as data.
    {
      name: 'choice',
      help: 'The current choice (ie. [value, label]).',
      getter: function() {
        var value = this.data;
        for ( var i = 0 ; i < this.choices.length ; i++ ) {
          var choice = this.choices[i];
          if ( value === choice[0] ) return choice;
        }
        return undefined;
      },
      setter: function(choice) {
        var oldValue = this.choice;
        this.data = choice[0];
        this.label = choice[1];
        this.propertyChange('choice', oldValue, this.choice);
      }
    },
    {
      name:  'choices',
      type:  'Array[StringField]',
      help: 'Array of [value, label] choices.  Simple String values will be upgraded to [value, value].',
      defaultValue: [],
      preSet: function(_, a) {
        a = a.clone();
        // Upgrade single values to [value, value]
        for ( var i = 0 ; i < a.length ; i++ )
          if ( ! Array.isArray(a[i]) )
            a[i] = [a[i], a[i]];
        return a;
      },
      postSet: function(_, newValue) {
        var value = this.data;

        // Update current choice when choices update.
        for ( var i = 0 ; i < newValue.length ; i++ ) {
          var choice = newValue[i];

          if ( value === choice[0] ) {
            if ( this.useSelection ) this.index = i;
            else this.choice = choice;
            break;
          }
        }

        if ( this.autoSetData && i === newValue.length ) {
          if ( this.useSelection ) this.index = 0;
          else this.data = newValue.length ? newValue[0][0] : undefined;
        }

        if ( this.$ ) this.updateHTML();
      }
    },
    // The authoritative selection internally. data and choice are outputs when
    // useSelection is enabled.
    {
      name: 'index',
      help: 'The index of the current choice.',
      preSet: function(_, i) {
        if ( i < 0 || this.choices.length == 0 ) return 0;
        if ( i >= this.choices.length ) return this.choices.length - 1;
        return i;
      },
      postSet: function(_, i) {
        // If useSelection is enabled, don't update data or choice.
        if ( this.useSelection ) return;
        if ( this.data !== this.choices[i][0] ) this.data = this.choices[i][0];
      }
    },
    {
      model_: 'FunctionProperty',
      name: 'objToChoice',
      help: 'A Function which adapts an object from the DAO to a [key, value, ...] choice.'
    },
    {
      name: 'useSelection',
      help: 'When set, data and choice do not update until an entry is firmly selected',
      model_: 'BooleanProperty'
    },
    {
      name: 'dao',
      postSet: function(oldDAO, dao) {
        if ( oldDAO ) {
          oldDAO.unlisten(this.onDAOUpdate);
        }
        if ( dao && this.$ ) {
          dao.listen(this.onDAOUpdate);
          this.onDAOUpdate();
        }
      }
    }
  ],

  listeners: [
    {
      name: 'onDAOUpdate',
      isMerged: 100,
      code: function() {
        this.dao.select(MAP(this.objToChoice))(function(map) {
          // console.log('***** Update Choices ', map.arg2, this.choices);
          this.choices = map.arg2;
        }.bind(this));
      }
    }
  ],

  methods: {
    initHTML: function() {
      this.SUPER();

      this.dao = this.dao;
    },

    findChoiceIC: function(name) {
      name = name.toLowerCase();
      for ( var i = 0 ; i < this.choices.length ; i++ ) {
        if ( this.choices[i][1].toLowerCase() == name )
          return this.choices[i];
      }
    },

    commit: function() {
      if ( ! this.useSelection ) return;
      this.choice = this.choices[this.index];
    }
  }
});

FOAModel({
  name:  'ChoiceListView',

  extendsModel: 'AbstractChoiceView',

  properties: [
    {
      name: 'orientation',
      defaultValue: 'horizontal',
      view: {
        model_: 'ChoiceView',
        choices: [
          [ 'horizontal', 'Horizontal' ],
          [ 'vertical',   'Vertical'   ]
        ]
      },
      postSet: function(old, nu) {
        if ( this.$ ) {
          DOM.setClass(this.$, old, false);
          DOM.setClass(this.$, nu);
        }
      }
    },
    {
      name: 'className',
      defaultValueFn: function() { return 'foamChoiceListView ' + this.orientation; }
    },
    {
      name: 'tagName',
      defaultValue: 'ul'
    },
    {
      name: 'innerTagName',
      defaultValue: 'li'
    }
  ],

  methods: {
    choiceToHTML: function(id, choice) {
      return '<' + this.innerTagName + ' id="' + id + '" class="choice">' +
          choice[1] + '</' + this.innerTagName + '>';
    },
    toInnerHTML: function() {
      var out = [];
      for ( var i = 0 ; i < this.choices.length ; i++ ) {
        var choice = this.choices[i];
        var id     = this.nextID();

        this.on(
          'click',
          function(index) {
            this.choice = this.choices[index];
          }.bind(this, i),
          id);

        this.setClass(
          'selected',
          function(index) { return this.index === index; }.bind(this, i),
          id);

        out.push(this.choiceToHTML(id, choice));
      }
      return out.join('');
    },

    scrollToSelection: function() {
      // Three cases: in view, need to scroll up, need to scroll down.
      // First we determine the parent's scrolling bounds.
      var e = this.$.children[this.index];
      if ( ! e ) return;
      var parent = e.parentElement;
      while ( parent ) {
        var overflow = this.X.window.getComputedStyle(parent).overflow;
        if ( overflow === 'scroll' || overflow === 'auto' ) {
          break;
        }
        parent = parent.parentElement;
      }
      parent = parent || this.X.window;

      if ( e.offsetTop < parent.scrollTop ) { // Scroll up
        e.scrollIntoView(true);
      } else if ( e.offsetTop + e.offsetHeight >=
          parent.scrollTop + parent.offsetHeight ) { // Down
        e.scrollIntoView();
      }
    }
  }
});

FOAModel({
  name:  'ChoiceView',

  extendsModel: 'AbstractChoiceView',

  /*
   * <select size="">
   *    <choice value="" selected></choice>
   * </select>
   */
  properties: [
    {
      name:  'name',
      type:  'String',
      defaultValue: 'field'
    },
    {
      name:  'helpText',
      type:  'String',
      defaultValue: undefined
    },
    {
      name:  'size',
      type:  'int',
      defaultValue: 1
    }
  ],

  methods: {
    toHTML: function() {
      return '<select id="' + this.id + '" name="' + this.name + '" size=' + this.size + '/></select>';
    },

    updateHTML: function() {
      var out = [];

      if ( this.helpText ) {
        out.push('<option disabled="disabled">');
        out.push(this.helpText);
        out.push('</option>');
      }

      for ( var i = 0 ; i < this.choices.length ; i++ ) {
        var choice = this.choices[i];
        var id     = this.nextID();

        try {
          this.on('click', this.onClick, id);
          this.on('mouseover', this.onMouseOver, id);
          this.on('mouseout', this.onMouseOut, id);
        } catch (x) {
          // Fails on iPad, which is okay, because this feature doesn't make
          // sense on the iPad anyway.
        }

        out.push('\t<option id="' + id + '"');

        if ( choice[0] === this.data ) out.push(' selected');
        out.push(' value="');
        out.push(i + '">');
        out.push(choice[1].toString());
        out.push('</option>');
      }

      this.$.innerHTML = out.join('');
      View.getPrototype().initHTML.call(this);
    },

    initHTML: function() {
      this.SUPER();

      var e = this.$;

      this.updateHTML();
      this.domValue = DomValue.create(e);
      Events.link(this.index$, this.domValue);
    }
  },

  listeners: [
    {
      name: 'onMouseOver',
      code: function(e) {
        if ( this.timer_ ) window.clearTimeout(this.timer_);
        this.prev = ( this.prev === undefined ) ? this.data : this.prev;
        this.index = e.target.value;
      }
    },
    {
      name: 'onMouseOut',
      code: function(e) {
        if ( this.timer_ ) window.clearTimeout(this.timer_);
        this.timer_ = window.setTimeout(function() {
          this.data = this.prev || '';
          this.prev = undefined;
        }.bind(this), 1);
      }
    },
    {
      name: 'onClick',
      code: function(e) {
        this.data = this.prev = this.choices[e.target.value][0];
      }
    }
  ]
});

FOAModel({
  name:  'RadioBoxView',

  extendsModel: 'ChoiceView',

  methods: {
    toHTML: function() {
      return '';
    },

    updateHTML: function() {
      var out = '';
      var self = this;
      this.choices.forEach(function(choice) {
        var value  = choice[0];
        var label  = choice[1];
        var id     = self.nextID();

        out += label + ':<input type="radio" name="';
        out += self.id;
        out += '" value="';
        out += value;
        out += '" ';
        out += 'id="' + id + '"';
        if ( self.data === value ) out += ' checked';
        out += '> ';

        self.on('click', function() { self.data = value; }, id)
        self.data$.addListener(function() { $(id).checked = ( self.data == value ); });
      });

      this.$.innerHTML = out;
      View.getPrototype().initHTML.call(this);
    },

    initHTML: function() {
      this.SUPER();

      Events.dynamic(function() { this.choices; }.bind(this), this.updateHTML.bind(this));
    }
  }
});

FOAModel({
  name:  'PopupChoiceView',

  extendsModel: 'AbstractChoiceView',

  properties: [
    {
      name: 'label'
    },
    {
      name: 'iconUrl'
    },
    {
      name: 'tagName',
      defaultValue: 'button'
    },
    {
      name: 'className',
      defaultValue: 'popupChoiceView'
    },
    {
      model_: 'BooleanProperty',
      name: 'showValue'
    }
  ],

  listeners: [
    {
      name: 'popup',
      code: function(e) {
        var view = this.X.ChoiceListView.create({
          className: 'popupChoiceList',
          data: this.data,
          choices: this.choices,
          autoSetData: this.autoSetData
        });

        // I don't know why the 'animate' is required, but it sometimes
        // doesn't remove the view without it.
        view.data$.addListener(EventService.animate(function() {
          this.data = view.data;
          if ( view.$ ) view.$.remove();
        }.bind(this)));

        var pos = findPageXY(this.$.querySelector('.action'));
        var e = this.X.document.body.insertAdjacentHTML('beforeend', view.toHTML());
        var s = this.X.window.getComputedStyle(view.$);
        var parentNode = view.$.parentNode;

        view.$.style.top = pos[1]-2;
        view.$.style.left = pos[0]-toNum(s.width)+30;
        view.$.style.maxHeight = Math.max(200, this.X.window.innerHeight-pos[1]-10);
        view.initHTML();
        view.$.addEventListener('click', function() { if ( view.$ ) view.$.remove(); });
        parentNode.addEventListener('mousemove', function(evt) {
          if ( ! view.$ ) {
            parentNode.removeEventListener('mousemove', arguments.callee);
          } else if ( ! view.$.contains(evt.target) ) {
            parentNode.removeEventListener('mousemove', arguments.callee);
            view.$.remove();
          }
        });
      }
    }
  ],

  methods: {
    toInnerHTML: function() {
      var out = '';

      if ( this.showValue ) {
        var id = this.nextID();
        out += '' + (this.choice[1] || '') + '';
        this.data$.addListener(function() { this.X.$(id).innerHTML = this.choice[1]; }.bind(this));
      }

      out += '';
      if ( this.iconUrl ) {
        out += '';
      }

      if ( this.label ) {
        out += this.label;
      }
      out += '';

      return out;
    },

    initHTML: function() {
      this.SUPER();
      this.$.addEventListener('click', this.popup);
    }
  }
});
<commit_msg>QuickBug: No more listener jank on autocomplete filtering<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed 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.
 */
FOAModel({
  name: 'AbstractChoiceView',

  extendsModel: 'View',

  properties: [
    // This is the real, final choice. The internals use index only.
    // When useSelection is enabled, data is not set until a final choice is made.
    {
      model_: 'BooleanProperty',
      name: 'autoSetData',
      help: 'If true, this.data is set when choices update and the current data is not one of the choices.',
      defaultValue: true
    },
    {
      name: 'data',
      help: 'The value of the current choice (ie. [value, label] -> value).',
      postSet: function(_, d) {
        for ( var i = 0 ; i < this.choices.length ; i++ ) {
          if ( this.choices[i][0] === d ) {
            if ( this.index !== i ) this.index = i;
            return;
          }
        }
      }
    },
    {
      name: 'label',
      help: 'The label of the current choice (ie. [value, label] -> label).',
      postSet: function(_, d) {
        for ( var i = 0 ; i < this.choices.length ; i++ ) {
          if ( this.choices[i][1] === d ) {
            if ( this.index !== i ) this.index = i;
            return;
          }
        }
      }
    },
    // See above; choice works the same as data.
    {
      name: 'choice',
      help: 'The current choice (ie. [value, label]).',
      getter: function() {
        var value = this.data;
        for ( var i = 0 ; i < this.choices.length ; i++ ) {
          var choice = this.choices[i];
          if ( value === choice[0] ) return choice;
        }
        return undefined;
      },
      setter: function(choice) {
        var oldValue = this.choice;
        this.data = choice[0];
        this.label = choice[1];
        this.propertyChange('choice', oldValue, this.choice);
      }
    },
    {
      name:  'choices',
      type:  'Array[StringField]',
      help: 'Array of [value, label] choices.  Simple String values will be upgraded to [value, value].',
      defaultValue: [],
      preSet: function(_, a) {
        a = a.clone();
        // Upgrade single values to [value, value]
        for ( var i = 0 ; i < a.length ; i++ )
          if ( ! Array.isArray(a[i]) )
            a[i] = [a[i], a[i]];
        return a;
      },
      postSet: function(_, newValue) {
        var value = this.data;

        // Update current choice when choices update.
        for ( var i = 0 ; i < newValue.length ; i++ ) {
          var choice = newValue[i];

          if ( value === choice[0] ) {
            if ( this.useSelection ) this.index = i;
            else this.choice = choice;
            break;
          }
        }

        if ( this.autoSetData && i === newValue.length ) {
          if ( this.useSelection ) this.index = 0;
          else this.data = newValue.length ? newValue[0][0] : undefined;
        }

        if ( this.$ ) this.updateHTML();
      }
    },
    // The authoritative selection internally. data and choice are outputs when
    // useSelection is enabled.
    {
      name: 'index',
      help: 'The index of the current choice.',
      preSet: function(_, i) {
        if ( i < 0 || this.choices.length == 0 ) return 0;
        if ( i >= this.choices.length ) return this.choices.length - 1;
        return i;
      },
      postSet: function(_, i) {
        // If useSelection is enabled, don't update data or choice.
        if ( this.useSelection ) return;
        if ( this.data !== this.choices[i][0] ) this.data = this.choices[i][0];
      }
    },
    {
      model_: 'FunctionProperty',
      name: 'objToChoice',
      help: 'A Function which adapts an object from the DAO to a [key, value, ...] choice.'
    },
    {
      name: 'useSelection',
      help: 'When set, data and choice do not update until an entry is firmly selected',
      model_: 'BooleanProperty'
    },
    {
      name: 'dao',
      postSet: function(oldDAO, dao) {
        if ( oldDAO ) {
          oldDAO.unlisten(this.onDAOUpdate);
        }
        if ( dao && this.$ ) {
          dao.listen(this.onDAOUpdate);
          this.onDAOUpdate();
        }
      }
    }
  ],

  listeners: [
    {
      name: 'onDAOUpdate',
      isMerged: 100,
      code: function() {
        this.dao.select(MAP(this.objToChoice))(function(map) {
          // console.log('***** Update Choices ', map.arg2, this.choices);
          this.choices = map.arg2;
        }.bind(this));
      }
    }
  ],

  methods: {
    initHTML: function() {
      this.SUPER();

      this.dao = this.dao;
    },

    findChoiceIC: function(name) {
      name = name.toLowerCase();
      for ( var i = 0 ; i < this.choices.length ; i++ ) {
        if ( this.choices[i][1].toLowerCase() == name )
          return this.choices[i];
      }
    },

    commit: function() {
      if ( ! this.useSelection ) return;
      this.choice = this.choices[this.index];
    }
  }
});

FOAModel({
  name:  'ChoiceListView',

  extendsModel: 'AbstractChoiceView',

  properties: [
    {
      name: 'orientation',
      defaultValue: 'horizontal',
      view: {
        model_: 'ChoiceView',
        choices: [
          [ 'horizontal', 'Horizontal' ],
          [ 'vertical',   'Vertical'   ]
        ]
      },
      postSet: function(old, nu) {
        if ( this.$ ) {
          DOM.setClass(this.$, old, false);
          DOM.setClass(this.$, nu);
        }
      }
    },
    {
      name: 'className',
      defaultValueFn: function() { return 'foamChoiceListView ' + this.orientation; }
    },
    {
      name: 'tagName',
      defaultValue: 'ul'
    },
    {
      name: 'innerTagName',
      defaultValue: 'li'
    }
  ],

  listeners: [
    {
      name: 'updateSelected',
      code: function() {
        for ( var i = 0 ; i < this.$.children.length ; i++ ) {
          var c = this.$.children[i];
          DOM.setClass(c, 'selected', i === this.index);
        }
      }
    }
  ],

  methods: {
    init: function() {
      this.SUPER();
      // Doing this at the low level rather than with this.setClass listeners
      // to avoid creating loads of listeners when autocompleting or otherwise
      // rapidly changing this.choices.
      this.index$.addListener(this.updateSelected);
      this.choices$.addListener(this.updateSelected);
    },
    choiceToHTML: function(id, choice) {
      return '<' + this.innerTagName + ' id="' + id + '" class="choice">' +
          choice[1] + '</' + this.innerTagName + '>';
    },
    toInnerHTML: function() {
      var out = [];
      for ( var i = 0 ; i < this.choices.length ; i++ ) {
        var choice = this.choices[i];
        var id     = this.nextID();

        this.on(
          'click',
          function(index) {
            this.choice = this.choices[index];
          }.bind(this, i),
          id);

        out.push(this.choiceToHTML(id, choice));
      }
      return out.join('');
    },

    initHTML: function() {
      this.SUPER();
      this.updateSelected();
    },

    scrollToSelection: function() {
      // Three cases: in view, need to scroll up, need to scroll down.
      // First we determine the parent's scrolling bounds.
      var e = this.$.children[this.index];
      if ( ! e ) return;
      var parent = e.parentElement;
      while ( parent ) {
        var overflow = this.X.window.getComputedStyle(parent).overflow;
        if ( overflow === 'scroll' || overflow === 'auto' ) {
          break;
        }
        parent = parent.parentElement;
      }
      parent = parent || this.X.window;

      if ( e.offsetTop < parent.scrollTop ) { // Scroll up
        e.scrollIntoView(true);
      } else if ( e.offsetTop + e.offsetHeight >=
          parent.scrollTop + parent.offsetHeight ) { // Down
        e.scrollIntoView();
      }
    }
  }
});

FOAModel({
  name:  'ChoiceView',

  extendsModel: 'AbstractChoiceView',

  /*
   * <select size="">
   *    <choice value="" selected></choice>
   * </select>
   */
  properties: [
    {
      name:  'name',
      type:  'String',
      defaultValue: 'field'
    },
    {
      name:  'helpText',
      type:  'String',
      defaultValue: undefined
    },
    {
      name:  'size',
      type:  'int',
      defaultValue: 1
    }
  ],

  methods: {
    toHTML: function() {
      return '<select id="' + this.id + '" name="' + this.name + '" size=' + this.size + '/></select>';
    },

    updateHTML: function() {
      var out = [];

      if ( this.helpText ) {
        out.push('<option disabled="disabled">');
        out.push(this.helpText);
        out.push('</option>');
      }

      for ( var i = 0 ; i < this.choices.length ; i++ ) {
        var choice = this.choices[i];
        var id     = this.nextID();

        try {
          this.on('click', this.onClick, id);
          this.on('mouseover', this.onMouseOver, id);
          this.on('mouseout', this.onMouseOut, id);
        } catch (x) {
          // Fails on iPad, which is okay, because this feature doesn't make
          // sense on the iPad anyway.
        }

        out.push('\t<option id="' + id + '"');

        if ( choice[0] === this.data ) out.push(' selected');
        out.push(' value="');
        out.push(i + '">');
        out.push(choice[1].toString());
        out.push('</option>');
      }

      this.$.innerHTML = out.join('');
      View.getPrototype().initHTML.call(this);
    },

    initHTML: function() {
      this.SUPER();

      var e = this.$;

      this.updateHTML();
      this.domValue = DomValue.create(e);
      Events.link(this.index$, this.domValue);
    }
  },

  listeners: [
    {
      name: 'onMouseOver',
      code: function(e) {
        if ( this.timer_ ) window.clearTimeout(this.timer_);
        this.prev = ( this.prev === undefined ) ? this.data : this.prev;
        this.index = e.target.value;
      }
    },
    {
      name: 'onMouseOut',
      code: function(e) {
        if ( this.timer_ ) window.clearTimeout(this.timer_);
        this.timer_ = window.setTimeout(function() {
          this.data = this.prev || '';
          this.prev = undefined;
        }.bind(this), 1);
      }
    },
    {
      name: 'onClick',
      code: function(e) {
        this.data = this.prev = this.choices[e.target.value][0];
      }
    }
  ]
});

FOAModel({
  name:  'RadioBoxView',

  extendsModel: 'ChoiceView',

  methods: {
    toHTML: function() {
      return '';
    },

    updateHTML: function() {
      var out = '';
      var self = this;
      this.choices.forEach(function(choice) {
        var value  = choice[0];
        var label  = choice[1];
        var id     = self.nextID();

        out += label + ':<input type="radio" name="';
        out += self.id;
        out += '" value="';
        out += value;
        out += '" ';
        out += 'id="' + id + '"';
        if ( self.data === value ) out += ' checked';
        out += '> ';

        self.on('click', function() { self.data = value; }, id)
        self.data$.addListener(function() { $(id).checked = ( self.data == value ); });
      });

      this.$.innerHTML = out;
      View.getPrototype().initHTML.call(this);
    },

    initHTML: function() {
      this.SUPER();

      Events.dynamic(function() { this.choices; }.bind(this), this.updateHTML.bind(this));
    }
  }
});

FOAModel({
  name:  'PopupChoiceView',

  extendsModel: 'AbstractChoiceView',

  properties: [
    {
      name: 'label'
    },
    {
      name: 'iconUrl'
    },
    {
      name: 'tagName',
      defaultValue: 'button'
    },
    {
      name: 'className',
      defaultValue: 'popupChoiceView'
    },
    {
      model_: 'BooleanProperty',
      name: 'showValue'
    }
  ],

  listeners: [
    {
      name: 'popup',
      code: function(e) {
        var view = this.X.ChoiceListView.create({
          className: 'popupChoiceList',
          data: this.data,
          choices: this.choices,
          autoSetData: this.autoSetData
        });

        // I don't know why the 'animate' is required, but it sometimes
        // doesn't remove the view without it.
        view.data$.addListener(EventService.animate(function() {
          this.data = view.data;
          if ( view.$ ) view.$.remove();
        }.bind(this)));

        var pos = findPageXY(this.$.querySelector('.action'));
        var e = this.X.document.body.insertAdjacentHTML('beforeend', view.toHTML());
        var s = this.X.window.getComputedStyle(view.$);
        var parentNode = view.$.parentNode;

        view.$.style.top = pos[1]-2;
        view.$.style.left = pos[0]-toNum(s.width)+30;
        view.$.style.maxHeight = Math.max(200, this.X.window.innerHeight-pos[1]-10);
        view.initHTML();
        view.$.addEventListener('click', function() { if ( view.$ ) view.$.remove(); });
        parentNode.addEventListener('mousemove', function(evt) {
          if ( ! view.$ ) {
            parentNode.removeEventListener('mousemove', arguments.callee);
          } else if ( ! view.$.contains(evt.target) ) {
            parentNode.removeEventListener('mousemove', arguments.callee);
            view.$.remove();
          }
        });
      }
    }
  ],

  methods: {
    toInnerHTML: function() {
      var out = '';

      if ( this.showValue ) {
        var id = this.nextID();
        out += '' + (this.choice[1] || '') + '';
        this.data$.addListener(function() { this.X.$(id).innerHTML = this.choice[1]; }.bind(this));
      }

      out += '';
      if ( this.iconUrl ) {
        out += '';
      }

      if ( this.label ) {
        out += this.label;
      }
      out += '';

      return out;
    },

    initHTML: function() {
      this.SUPER();
      this.$.addEventListener('click', this.popup);
    }
  }
});
<|endoftext|>


<commit_before>//for production
//var timer = window.setInterval(fb, 600000);
//for testing
var timer = window.setInterval(logout, 3000);

function logoutCheck(action){
	if(action.indexOf("logout") > 0){
		return true;
	}
	else if(action.indexOf("signout") > 0){
		return true;
	}
	return false;
}

function stopTimer(){
	clearTimeout(timer);
}

function logout(){
	var currentPage = window.location.href;
	var logoutForm = document.getElementsByTagName("form")[0];

	console.log("current page:"+currentPage);

	if(currentPage.indexOf("twitter") > 0){

		var signoutButton = document.getElementById("signout-button");

		if(logoutCheck(signoutButton.id)){
			alert("You will be logged out now.");
			signoutButton.click();
		}

	}
	else if(currentPage.indexOf("quora") > 0){

		var logoutForm = document.getElementsByTagName("form")[0];

		if(logoutCheck(logoutForm.action)){
			alert("You will be logged out now.");
			 document.getElementsByTagName("form")[0].submit();
		}

	}
	else{

		console.log("no logout");
		alert("close tab now.");

	}

	stopTimer();
}
<commit_msg>facebook logout functionality added<commit_after>//for production
//var timer = window.setInterval(logout, 600000);
//for testing
var timer = window.setInterval(logout, 3000);

function logoutCheck(action){
	if(action.indexOf("logout") > 0){
		return true;
	}
	else if(action.indexOf("signout") > 0){
		return true;
	}
	return false;
}

function stopTimer(){
	clearTimeout(timer);
}

function isUndefined(obj){
	if(typeof obj === 'undefined')
		return true;
	else if(obj == null)
		return true;
	return false;
}

function logout(){
	var currentPage = window.location.href;
	var logoutForm = document.getElementsByTagName("form")[0];

	console.log("current page:"+currentPage);

	if(currentPage.indexOf("twitter") > 0){

		var signoutButton = document.getElementById("signout-button");

		if(!isUndefined(signoutButton) && logoutCheck(signoutButton.id)){
			alert("You will be logged out now.");
			signoutButton.click();
		}

	}
	else if(currentPage.indexOf("quora") > 0){

		var logoutForm = document.getElementsByTagName("form")[0];

		if(!isUndefined(logoutForm) && logoutCheck(logoutForm.action)){
			alert("You will be logged out now.");
			document.getElementsByTagName("form")[0].submit();
		}

	}
	else if(currentPage.indexOf("facebook") > 0){
		var insertedNodes = [];
		var observer = new WebKitMutationObserver(function(mutations){
			console.log("dom change");
			mutations.forEach(function(mutation){
				for(var j=0; j<mutation.addedNodes.length; j++)
				insertedNodes.push(mutation.addedNodes[j]);
				console.log("j"+insertedNodes[j].id);
			})

			var logoutForm = document.getElementsByClassName("_w0d")[0];
			if(!isUndefined(logoutForm)){
				observer.disconnect();
				alert("You will be logged out now.");
				logoutForm.submit();
			}
		});

		observer.observe(document, {
			attributes: true,
			childList: true,
			characterData: true,
			subtree: true
		});

		var logoutMenu = document.getElementById("userNavigationLabel");
		if(!isUndefined(logoutMenu))
			logoutMenu.click();

	}

	stopTimer();
}
<|endoftext|>