<commit_before>const logger = require('./logger')(module)
const EventEmitter = require('events').EventEmitter
const Promise = require('bluebird')
const is = require('./is')
const enums = require('./enums')
const Job = require('./job')
const dbReview = require('./db-review')
const queueDb = require('./queue-db')
const queueProcess = require('./queue-process')
const queueAddJob = require('./queue-add-job')
const queueGetJob = require('./queue-get-job')
const queueCancelJob = require('./queue-cancel-job')
const queueRemoveJob = require('./queue-remove-job')
const queueReset = require('./queue-reset')
const queueSummary = require('./queue-summary')
const queueStop = require('./queue-stop')
const queueDrop = require('./queue-drop')
const jobOptions = require('./job-options')

class Queue extends EventEmitter {

  constructor (options) {
    super()
    logger('Queue Constructor', options)

    options = options || {}
    this._name = options.name || enums.options.name
    this._host = options.host || enums.options.host
    this._port = options.port || enums.options.port
    this._db = options.db || enums.options.db
    this._master = options.master == null ? true
      : options.master
    this._masterInterval = options.masterInterval ||
      enums.options.masterInterval
    this._jobOptions = jobOptions()
    this._changeFeedCursor = false
    this._paused = false
    this._running = 0
    this._changeFeed = options.changeFeed == null
      ? true : options.changeFeed
    this._concurrency = options.concurrency > 1
      ? options.concurrency : enums.options.concurrency
    this._removeFinishedJobs = options.removeFinishedJobs == null
      ? enums.options.removeFinishedJobs : options.removeFinishedJobs
    this._handler = false
    this._id = [
      require('os').hostname(),
      this.db,
      this.name,
      process.pid
    ].join(':')
    queueDb.attach(this)
  }

  get paused () { return this._paused }
  get connection () { return this.r }
  get name () { return this._name }
  get id () { return this._id }
  get host () { return this._host }
  get port () { return this._port }
  get db () { return this._db }
  get changeFeed () { return this._changeFeed }
  get jobOptions () { return this._jobOptions }
  get removeFinishedJobs () { return this._removeFinishedJobs }
  get running () { return this._running }
  get master () { return this._master }
  get masterInterval () { return this._masterInterval }
  get concurrency () { return this._concurrency }
  get idle () { return this._running < 1 }

  set jobOptions (options) {
    logger('set jobOptions', options)
    this._jobOptions = jobOptions(options)
  }

  set concurrency (value) {
    if (!is.integer(value) || value < 1) {
      this.emit(enums.status.error,
        new Error(enums.message.concurrencyInvalid),
        value)
      return
    }
    this._concurrency = value
  }

  createJob (data, options = this._jobOptions, quantity = 1) {
    logger('createJob', data, options, quantity)
    if (is.integer(options)) {
      quantity = options
      options = this._jobOptions
    }
    if (quantity > 1) {
      const jobs = []
      for (let i = 0; i < quantity; i++) {
        jobs.push(new Job(this, data, options))
      }
      return jobs
    }
    return new Job(this, data, options)
  }

  addJob (job) {
    logger('addJob', job)
    return this.ready.then(() => {
      return queueAddJob(this, job)
    })
  }

  getJob (jobId) {
    logger('getJob', jobId)
    return this.ready.then(() => {
      return queueGetJob(this, jobId)
    })
  }

  cancelJob (job, reason) {
    logger('cancelJob', job, reason)
    return this.ready.then(() => {
      return queueCancelJob(this, job, reason)
    })
  }

  removeJob (job) {
    logger('removeJob', job)
    return this.ready.then(() => {
      return queueRemoveJob(this, job)
    })
  }

  process (handler) {
    logger('process', handler)
    return this.ready.then(() => {
      return queueProcess.addHandler(this, handler)
    })
  }

  review () {
    logger('review')
    return this.ready.then(() => {
      return dbReview.runOnce(this)
    })
  }

  summary () {
    logger('summary')
    return this.ready.then(() => {
      return queueSummary(this)
    })
  }

  pause () {
    logger(`pause`)
    return new Promise((resolve, reject) => {
      this._paused = true
      if (this._running < 1) { return resolve(true) }
      const q = this
      let intId = setInterval(function pausing () {
        logger(`Pausing, waiting on running jobs: [${q._running}]`)
        if (q._running < 1) {
          clearInterval(intId)
          resolve(true)
        }
      }, 400)
    }).then(() => {
      logger(`Event: paused [${this.id}]`)
      this.emit(enums.status.paused, this.id)
    })
  }

  resume () {
    logger(`resume`)
    return this.ready.then(() => {
      this._paused = false
      queueProcess.restart(this)
      logger(`Event: resumed [${this.id}]`)
      this.emit(enums.status.resumed, this.id)
      return true
    })
  }

  reset () {
    logger('reset')
    return this.ready.then(() => {
      return queueReset(this)
    })
  }

  stop () {
    logger('stop')
    return queueStop(this)
  }

  drop () {
    logger('drop')
    return queueDrop(this)
  }
}

module.exports = Queue
<commit_msg>reorded properties<commit_after>const logger = require('./logger')(module)
const EventEmitter = require('events').EventEmitter
const Promise = require('bluebird')
const is = require('./is')
const enums = require('./enums')
const Job = require('./job')
const dbReview = require('./db-review')
const queueDb = require('./queue-db')
const queueProcess = require('./queue-process')
const queueAddJob = require('./queue-add-job')
const queueGetJob = require('./queue-get-job')
const queueCancelJob = require('./queue-cancel-job')
const queueRemoveJob = require('./queue-remove-job')
const queueReset = require('./queue-reset')
const queueSummary = require('./queue-summary')
const queueStop = require('./queue-stop')
const queueDrop = require('./queue-drop')
const jobOptions = require('./job-options')

class Queue extends EventEmitter {

  constructor (options) {
    super()
    logger('Queue Constructor', options)

    options = options || {}
    this._name = options.name || enums.options.name
    this._host = options.host || enums.options.host
    this._port = options.port || enums.options.port
    this._db = options.db || enums.options.db
    this._master = options.master == null ? true
      : options.master
    this._masterInterval = options.masterInterval ||
      enums.options.masterInterval
    this._jobOptions = jobOptions()
    this._changeFeedCursor = false
    this._paused = false
    this._running = 0
    this._changeFeed = options.changeFeed == null
      ? true : options.changeFeed
    this._concurrency = options.concurrency > 1
      ? options.concurrency : enums.options.concurrency
    this._removeFinishedJobs = options.removeFinishedJobs == null
      ? enums.options.removeFinishedJobs : options.removeFinishedJobs
    this._handler = false
    this._id = [
      require('os').hostname(),
      this.db,
      this.name,
      process.pid
    ].join(':')
    queueDb.attach(this)
  }

  get name () { return this._name }
  get id () { return this._id }
  get host () { return this._host }
  get port () { return this._port }
  get db () { return this._db }
  get connection () { return this.r }
  get changeFeed () { return this._changeFeed }
  get master () { return this._master }
  get masterInterval () { return this._masterInterval }
  get jobOptions () { return this._jobOptions }
  get removeFinishedJobs () { return this._removeFinishedJobs }
  get running () { return this._running }
  get concurrency () { return this._concurrency }
  get paused () { return this._paused }
  get idle () { return this._running < 1 }

  set jobOptions (options) {
    logger('set jobOptions', options)
    this._jobOptions = jobOptions(options)
  }

  set concurrency (value) {
    if (!is.integer(value) || value < 1) {
      this.emit(enums.status.error,
        new Error(enums.message.concurrencyInvalid),
        value)
      return
    }
    this._concurrency = value
  }

  createJob (data, options = this._jobOptions, quantity = 1) {
    logger('createJob', data, options, quantity)
    if (is.integer(options)) {
      quantity = options
      options = this._jobOptions
    }
    if (quantity > 1) {
      const jobs = []
      for (let i = 0; i < quantity; i++) {
        jobs.push(new Job(this, data, options))
      }
      return jobs
    }
    return new Job(this, data, options)
  }

  addJob (job) {
    logger('addJob', job)
    return this.ready.then(() => {
      return queueAddJob(this, job)
    })
  }

  getJob (jobId) {
    logger('getJob', jobId)
    return this.ready.then(() => {
      return queueGetJob(this, jobId)
    })
  }

  cancelJob (job, reason) {
    logger('cancelJob', job, reason)
    return this.ready.then(() => {
      return queueCancelJob(this, job, reason)
    })
  }

  removeJob (job) {
    logger('removeJob', job)
    return this.ready.then(() => {
      return queueRemoveJob(this, job)
    })
  }

  process (handler) {
    logger('process', handler)
    return this.ready.then(() => {
      return queueProcess.addHandler(this, handler)
    })
  }

  review () {
    logger('review')
    return this.ready.then(() => {
      return dbReview.runOnce(this)
    })
  }

  summary () {
    logger('summary')
    return this.ready.then(() => {
      return queueSummary(this)
    })
  }

  pause () {
    logger(`pause`)
    return new Promise((resolve, reject) => {
      this._paused = true
      if (this._running < 1) { return resolve(true) }
      const q = this
      let intId = setInterval(function pausing () {
        logger(`Pausing, waiting on running jobs: [${q._running}]`)
        if (q._running < 1) {
          clearInterval(intId)
          resolve(true)
        }
      }, 400)
    }).then(() => {
      logger(`Event: paused [${this.id}]`)
      this.emit(enums.status.paused, this.id)
    })
  }

  resume () {
    logger(`resume`)
    return this.ready.then(() => {
      this._paused = false
      queueProcess.restart(this)
      logger(`Event: resumed [${this.id}]`)
      this.emit(enums.status.resumed, this.id)
      return true
    })
  }

  reset () {
    logger('reset')
    return this.ready.then(() => {
      return queueReset(this)
    })
  }

  stop () {
    logger('stop')
    return queueStop(this)
  }

  drop () {
    logger('drop')
    return queueDrop(this)
  }
}

module.exports = Queue
<|endoftext|>


<commit_before>var octonode    = require('octonode');
var path        = require('path');
var child       = require('child_process');
var pkg_config  = require('config-tree');
var chalk			  = require('chalk');
var io 					= require('indian-ocean');

// Github authentication
var config;
var gh_client;
var gh_entity;

var sh_commands = require('./sh-commands.js');

var PROJECT_PATH = path.resolve('.');
var LOCAL_FOLDER = path.basename(PROJECT_PATH);

/*    I N I T  C O M M A N D S   */
function configClient(){
	var dir = path.dirname(__dirname);
	pkg_config.sprout(dir, 'kestrel');
}

/*    C R E A T I O N  C O M M A N D S   */
function setGitHubOrgType(gh_c){
	if (config.github.type == 'org'){
		return gh_c.org(config.github.account_name);
	} else if (config.github.type == 'individual'){
		return gh_c.me();
	}
}

function kestrelInit(cb){
	child.exec( sh_commands.kestrelInit(), cb );
}

function gitInit(current_dir, cb){
	child.exec( sh_commands.gitInit(config.github.login_method, config.github.account_name, current_dir), cb );
}

function createGitHubRepo(repo_name, cb){
	gh_entity.repo({
	  "name": repo_name,
	  "private": config.github.private_repos
	}, function(err, response){
		cb(err, response);
	});
}

function createGitHubHook(repo_name, cb){
	var gh_repo = gh_client.repo(config.github.account_name + '/' + repo_name);

	gh_repo.hook({
	  "name": "web",
	  "active": true,
	  "events": ["push", "status"],
	  "config": {
	    "url": config.server.url
	  }
	}, function(err, response){
		cb(err, response);
	});
}

function setConfig(set_gh){
  var home_dir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
  var conf_dir_path = path.join(home_dir, '.conf');
  var config_path = path.join(home_dir,  '.conf', 'kestrel-config.json');
  var conf_dir_exists = io.existsSync( conf_dir_path );
  var config_exists = io.existsSync( config_path );

  if (!conf_dir_exists) {
  	throw '~/.conf folder not found. Please run `swoop config`.';
  }
  if (!config_exists) {
  	throw '~/.conf/kestrel-config.json not found. Please run `swoop config`.';
  }

  config = config || require(config_path);
	if (set_gh){
		gh_client = gh_client || octonode.client(config.github.access_token);
		gh_entity = gh_entity || setGitHubOrgType(gh_client);
	}
	return config;
}

function getProjectName(cb){
	var current_dir = path.basename(path.resolve('./'))
	if (!io.existsSync('./.git')) {
		cb(current_dir)
	} else {
		child.exec(sh_commands.getGitRemoteProjectName(), function(err, stdout, stderr ) {
			if (err || stdout.trim() == 'Fetch URL: origin') {
				console.log(chalk.yellow('Warning: Could not get remote project name from .git folder.'))
				console.log('> This could simply mean you have initialized git but haven\'t connected it to a GitHub repository.')
				cb(current_dir)
			} else {
				// Grab the repo name from the url, if that doesn't work, default to current directory name
				var url_parts = stdout.split('/')
				var project_name = url_parts[url_parts.length - 1].replace(/\.git/, '')
				cb(project_name)
			}
		})
	}
}

function initAll(){
	setConfig(true);
	console.log('Using project name:', chalk.bold(LOCAL_FOLDER))
	kestrelInit(function(err0, stdout0, stderr1){
		if (err0){
			console.log( chalk.yellow('Step 1/4: Skipping. .kestrel folder already exists.') );
		} else {
			console.log(chalk.green('Step 1/4: `.kestrel` folder created!'));
		}

		gitInit(LOCAL_FOLDER, function(err1, stdout, stderr){
			if (err1) {
				console.log(chalk.yellow('Step 2/4: Warning:') + ' Git remote origin already set. You should manually run ' + chalk.bold('git remote set-url origin ' + sh_commands.gitInit(config.github.login_method, config.github.account_name, LOCAL_FOLDER).split('origin ')[1]) );
			} else {
				console.log(chalk.green('Step 2/4: Git init\'ed and origin set!'));
			}

			createGitHubRepo(LOCAL_FOLDER, function(err2, response){
				if (err2) {
					console.log(chalk.yellow('Step 3/4: GitHub repo creation failed!') + ' `Validation Failed` could mean it already exists.' + '\nCheck here: ' + chalk.cyan('https://github.com/' + config.github.account_name + '/' + LOCAL_FOLDER) + '\nStated reason:', err2.message);
				} else {
					console.log(chalk.green('Step 3/4: GitHub repo created!'));
				}

				createGitHubHook(LOCAL_FOLDER, function(err3){
					if (err3) {
						console.log(chalk.yellow('Step 4/4: GitHub hook creation failed!') + ' `Validation Failed` could mean it already exists.' + '\nCheck here: ' + chalk.cyan('https://github.com/' + config.github.account_name + '/' + LOCAL_FOLDER + '/settings/hooks') + '\nStated reason:', err3.message);
					} else {
						console.log(chalk.green('Step 4/4: GitHub hook created.') + '\nOnce you push you can preview it at: ' + chalk.bold(config.server.url.split(':').slice(0,2).join(':') + ':3000/' + LOCAL_FOLDER) );
					}
				});
			});
		});
	});
}

/*    D E P L O Y   C O M M A N D S   */
function checkGitStatus(gitStatus){
	gitStatus = gitStatus.trim();
	// These could also be `.indexOf` and avoid escaping
	var ahead_regex = new RegExp('ahead');
	var behind_regex = new RegExp('behind');
	var kestrel_init_regex = new RegExp('\.kestrel\/');
	var deploy_settings_regex = new RegExp('\.kestrel\/deploy-settings\.json');
	var git_status_lines = gitStatus.split('\n');

	// If it's just two lines and the second line describes a change to `.kestrel/deploy-settings.json` then we're okay.
	// It should also ignore the creation of the `.kestrel` folder
	if (git_status_lines.length == 2 && ( deploy_settings_regex.exec(git_status_lines[1]) || kestrel_init_regex.exec(git_status_lines[1])) ) return 'clean_with_deploy_change';
	// If the status has more than one line, we have uncommitted changes
	if (git_status_lines.length > 1) return 'uncommitted';
	// If the status has the word ahead and behind then we have to pull and push
	if (ahead_regex.exec(gitStatus) && behind_regex.exec(gitStatus)) return 'ahead_and_behind';
	// Allow for deployment if we are ahead
	if (ahead_regex.exec(gitStatus)) return 'clean';
	// if (ahead_regex.exec(gitStatus)) return 'ahead';
	// Don't allow for deployment if we are behind. This condition will rarely be triggered and will also be caught by git itself when your push fails
	if (behind_regex.exec(gitStatus)) return 'behind';
	return 'clean';
}

function deployLastCommit(bucket_environment, trigger_type, trigger, local_path, remote_path, when){
  setConfig(true);
	var current_dir = path.resolve('.');

	var trigger_commit_msg  = bucket_environment + '::' + trigger + '::' + local_path + '::' + remote_path + '::' + when;

	// Make sure the working branch has no outstanding commits and is neither ahead or behind
	// Normally outstanding commits wouldn't be a problem, but the push flag allows for an empty commit
	// So if we had untracked or uncommitted files, it would just push the last commit.
	child.exec( sh_commands.status(), function(err0, stdout0, stderr0){
		if (err0 !== null) throw stderr1;
		var branch_status = checkGitStatus(stdout0),
				push,
				erred_out = false,
				spawnPush = sh_commands.spawnPush();

		// If stdout is blank, we have nothing to commit
		if (branch_status == 'clean' || branch_status == 'clean_with_deploy_change') {
			// Add the trigger as a commit message and push
			console.log(chalk.bgBlue.black('Pushing to GitHub...'));
			child.exec( sh_commands.makeEmptyCommitMsg(trigger_commit_msg), function(err1, stdout1, stderr1){
				if (!err1){
					push = child.spawn( spawnPush[0], spawnPush[1], {stdio: 'inherit'} );
				} else {
					console.log(chalk.red.bold('Error commiting!'));
				}

				// When done
				push.on('close', function(code){
					if (code != 0){
						// On error, erase the commit that has the trigger because the trigger push didn't go through
						child.exec( sh_commands.revertToPreviousCommit(), function(err2, stdout2, stderr2){
							if (err2) {
								console.log(chalk.red.bold('Error pushing AND error scrubbing the push commit. You might want to grab the SHA of the last commit you made and run `git rest --soft INSERT-SHA-HERE` in order to manually remove Kestrel\'s deploy commit.'));
								console.log(chalk.yellow('Once you do that, please check our internet connection and try again'));
								throw stderr2 + '\nAND\n' + err2;
							} else {
								if (code == 128){
									console.log(chalk.red.bold('Failed!'))
									console.log(chalk.yellow('Reason: Your internet connection appears down'));
								} else if (code == 1){
									console.log(chalk.red.bold('Failed!'))
									console.log(chalk.yellow('Reason: Please pull before pushing.'));
								} else {
									console.log(chalk.red.bold('Failed! Error code: ' + code.toString()), 'Try Googling this reason code to find out more.');
								}
							}
						});
					} else {
						// Otherwise, things went great!
						// Print the commands that got us to this auspicious moment.
						// Add a `_` for the `when` value because that needs to be one single string for the cli arg reader
						console.log(chalk.cyan('Settings: ') + 'swoop deploy -e ' + bucket_environment + ' -m ' + trigger_type + ' -l ' + local_path + ' -r ' + remote_path + ' -w ' + when.replace(' ', '_'));
						console.log(chalk.green('Push successful!'));
					}
				});
			});

		} else {
			if (branch_status == 'uncommitted') throw chalk.red.bold('Error!') + chalk.yellow(' You have uncommitted changes on this branch.' + ' Please commit your changes before attempting to deploy.')
			if (branch_status == 'ahead_and_behind') throw chalk.red.bold('Error!') + chalk.yellow(' You have unpushed commits on this branch and your local branch is behind your remote.' + ' Please pull and then push your changes before attempting to deploy.')
			// EDIT: It's okay if they haven't push their commits. Like the edit above to the scrub push, removing this step will result in fewer pushes for the server to respond to. This alert is currently not being triggered because the branch status no longer as a condition where it is set to ahead.
			// if (branch_status == 'ahead') throw chalk.red.bold('Error!') + ' You have unpushed commits on this branch.' + ' Please push your changes before attempting to deploy.'.yellow;
			if (branch_status == 'behind') throw chalk.red.bold('Error!') + chalk.yellow(' Your local branch is behind your remote.' + ' Please pull, merge and push before attempting to deploy.')
		}
	});
}

/*    C R E A T E  A R C H I V E  B R A N C H   */
function addToArchive(deploySettings){
	var local_branch = deploySettings.local_branch;
	var remote_branch = deploySettings.remote_branch;

  setConfig(true);
  var repo_name = path.basename(path.resolve('.'));
  var archive_push = sh_commands.archive(config.github.login_method, config.github.account_name, config.archive.repo_name, local_branch, remote_branch);

	console.log(chalk.bgBlue.black('Pushing to GitHub...'));
	child.spawn( archive_push[0], archive_push[1], {stdio: 'inherit'} )
	  .on('close', function(code){
	  	if (code != 0){
				console.log(chalk.red.bold('Archive failed.', 'Error code: ' + chalk.bold( code.toString()) ));
				if (code == 128){
					console.log(chalk.yellow('Reason:') + chalk.bold('Your internet connection appears down'));
				}
	  	} else {
	  		console.log(chalk.green('Success!') + ' `' + local_branch + '` branch of `' + repo_name + '` archived as `' + remote_branch + '` on the `' + config.archive.repo_name + '` repo.\n  https://github.com/' + config.github.account_name + '/' + config.archive.repo_name + '/tree/' + remote_branch + '\n' + chalk.cyan('Note:') + ' Your existing repo has not been deleted. Please do that manually through GitHub:\n  https://github.com/' + config.github.account_name + '/' + repo_name + '/settings')
	  	}
	  })
}

function reportError(err, msg){
	console.log(msg, '\nReason:');
	throw err;
}

module.exports = {
	setConfig: setConfig,
	config: configClient,
	init: initAll,
	deploy: deployLastCommit,
	archive: addToArchive,
	unschedule: deployLastCommit
}
<commit_msg>print deployed url<commit_after>var octonode    = require('octonode');
var path        = require('path');
var child       = require('child_process');
var pkg_config  = require('config-tree');
var chalk			  = require('chalk');
var io 					= require('indian-ocean');

// Github authentication
var config;
var gh_client;
var gh_entity;

var sh_commands = require('./sh-commands.js');

var PROJECT_PATH = path.resolve('.');
var LOCAL_FOLDER = path.basename(PROJECT_PATH);

/*    I N I T  C O M M A N D S   */
function configClient(){
	var dir = path.dirname(__dirname);
	pkg_config.sprout(dir, 'kestrel');
}

/*    C R E A T I O N  C O M M A N D S   */
function setGitHubOrgType(gh_c){
	if (config.github.type == 'org'){
		return gh_c.org(config.github.account_name);
	} else if (config.github.type == 'individual'){
		return gh_c.me();
	}
}

function kestrelInit(cb){
	child.exec( sh_commands.kestrelInit(), cb );
}

function gitInit(current_dir, cb){
	child.exec( sh_commands.gitInit(config.github.login_method, config.github.account_name, current_dir), cb );
}

function createGitHubRepo(repo_name, cb){
	gh_entity.repo({
	  "name": repo_name,
	  "private": config.github.private_repos
	}, function(err, response){
		cb(err, response);
	});
}

function createGitHubHook(repo_name, cb){
	var gh_repo = gh_client.repo(config.github.account_name + '/' + repo_name);

	gh_repo.hook({
	  "name": "web",
	  "active": true,
	  "events": ["push", "status"],
	  "config": {
	    "url": config.server.url
	  }
	}, function(err, response){
		cb(err, response);
	});
}

function setConfig(set_gh){
  var home_dir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
  var conf_dir_path = path.join(home_dir, '.conf');
  var config_path = path.join(home_dir,  '.conf', 'kestrel-config.json');
  var conf_dir_exists = io.existsSync( conf_dir_path );
  var config_exists = io.existsSync( config_path );

  if (!conf_dir_exists) {
  	throw '~/.conf folder not found. Please run `swoop config`.';
  }
  if (!config_exists) {
  	throw '~/.conf/kestrel-config.json not found. Please run `swoop config`.';
  }

  config = config || require(config_path);
	if (set_gh){
		gh_client = gh_client || octonode.client(config.github.access_token);
		gh_entity = gh_entity || setGitHubOrgType(gh_client);
	}
	return config;
}

function getProjectName(cb){
	var current_dir = path.basename(path.resolve('./'))
	if (!io.existsSync('./.git')) {
		cb(current_dir)
	} else {
		child.exec(sh_commands.getGitRemoteProjectName(), function(err, stdout, stderr ) {
			if (err || stdout.trim() == 'Fetch URL: origin') {
				console.log(chalk.yellow('Warning: Could not get remote project name from .git folder.'))
				console.log('> This could simply mean you have initialized git but haven\'t connected it to a GitHub repository.')
				cb(current_dir)
			} else {
				// Grab the repo name from the url, if that doesn't work, default to current directory name
				var url_parts = stdout.split('/')
				var project_name = url_parts[url_parts.length - 1].replace(/\.git/, '')
				cb(project_name)
			}
		})
	}
}

function initAll(){
	setConfig(true);
	console.log('Using project name:', chalk.bold(LOCAL_FOLDER))
	kestrelInit(function(err0, stdout0, stderr1){
		if (err0){
			console.log( chalk.yellow('Step 1/4: Skipping. .kestrel folder already exists.') );
		} else {
			console.log(chalk.green('Step 1/4: `.kestrel` folder created!'));
		}

		gitInit(LOCAL_FOLDER, function(err1, stdout, stderr){
			if (err1) {
				console.log(chalk.yellow('Step 2/4: Warning:') + ' Git remote origin already set. You should manually run ' + chalk.bold('git remote set-url origin ' + sh_commands.gitInit(config.github.login_method, config.github.account_name, LOCAL_FOLDER).split('origin ')[1]) );
			} else {
				console.log(chalk.green('Step 2/4: Git init\'ed and origin set!'));
			}

			createGitHubRepo(LOCAL_FOLDER, function(err2, response){
				if (err2) {
					console.log(chalk.yellow('Step 3/4: GitHub repo creation failed!') + ' `Validation Failed` could mean it already exists.' + '\nCheck here: ' + chalk.cyan('https://github.com/' + config.github.account_name + '/' + LOCAL_FOLDER) + '\nStated reason:', err2.message);
				} else {
					console.log(chalk.green('Step 3/4: GitHub repo created!'));
				}

				createGitHubHook(LOCAL_FOLDER, function(err3){
					if (err3) {
						console.log(chalk.yellow('Step 4/4: GitHub hook creation failed!') + ' `Validation Failed` could mean it already exists.' + '\nCheck here: ' + chalk.cyan('https://github.com/' + config.github.account_name + '/' + LOCAL_FOLDER + '/settings/hooks') + '\nStated reason:', err3.message);
					} else {
						console.log(chalk.green('Step 4/4: GitHub hook created.') + '\nOnce you push you can preview it at: ' + chalk.bold(config.server.url.split(':').slice(0,2).join(':') + ':3000/' + LOCAL_FOLDER) );
					}
				});
			});
		});
	});
}

/*    D E P L O Y   C O M M A N D S   */
function checkGitStatus(gitStatus){
	gitStatus = gitStatus.trim();
	// These could also be `.indexOf` and avoid escaping
	var ahead_regex = new RegExp('ahead');
	var behind_regex = new RegExp('behind');
	var kestrel_init_regex = new RegExp('\.kestrel\/');
	var deploy_settings_regex = new RegExp('\.kestrel\/deploy-settings\.json');
	var git_status_lines = gitStatus.split('\n');

	// If it's just two lines and the second line describes a change to `.kestrel/deploy-settings.json` then we're okay.
	// It should also ignore the creation of the `.kestrel` folder
	if (git_status_lines.length == 2 && ( deploy_settings_regex.exec(git_status_lines[1]) || kestrel_init_regex.exec(git_status_lines[1])) ) return 'clean_with_deploy_change';
	// If the status has more than one line, we have uncommitted changes
	if (git_status_lines.length > 1) return 'uncommitted';
	// If the status has the word ahead and behind then we have to pull and push
	if (ahead_regex.exec(gitStatus) && behind_regex.exec(gitStatus)) return 'ahead_and_behind';
	// Allow for deployment if we are ahead
	if (ahead_regex.exec(gitStatus)) return 'clean';
	// if (ahead_regex.exec(gitStatus)) return 'ahead';
	// Don't allow for deployment if we are behind. This condition will rarely be triggered and will also be caught by git itself when your push fails
	if (behind_regex.exec(gitStatus)) return 'behind';
	return 'clean';
}

function deployLastCommit(bucket_environment, trigger_type, trigger, local_path, remote_path, when){
  setConfig(true);
	var current_dir = path.resolve('.');

	var trigger_commit_msg  = bucket_environment + '::' + trigger + '::' + local_path + '::' + remote_path + '::' + when;

	// Make sure the working branch has no outstanding commits and is neither ahead or behind
	// Normally outstanding commits wouldn't be a problem, but the push flag allows for an empty commit
	// So if we had untracked or uncommitted files, it would just push the last commit.
	child.exec( sh_commands.status(), function(err0, stdout0, stderr0){
		if (err0 !== null) throw stderr1;
		var branch_status = checkGitStatus(stdout0),
				push,
				erred_out = false,
				spawnPush = sh_commands.spawnPush();

		// If stdout is blank, we have nothing to commit
		if (branch_status == 'clean' || branch_status == 'clean_with_deploy_change') {
			// Add the trigger as a commit message and push
			console.log(chalk.bgBlue.black('Pushing to GitHub...'));
			child.exec( sh_commands.makeEmptyCommitMsg(trigger_commit_msg), function(err1, stdout1, stderr1){
				if (!err1){
					push = child.spawn( spawnPush[0], spawnPush[1], {stdio: 'inherit'} );
				} else {
					console.log(chalk.red.bold('Error commiting!'));
				}

				// When done
				push.on('close', function(code){
					if (code != 0){
						// On error, erase the commit that has the trigger because the trigger push didn't go through
						child.exec( sh_commands.revertToPreviousCommit(), function(err2, stdout2, stderr2){
							if (err2) {
								console.log(chalk.red.bold('Error pushing AND error scrubbing the push commit. You might want to grab the SHA of the last commit you made and run `git rest --soft INSERT-SHA-HERE` in order to manually remove Kestrel\'s deploy commit.'));
								console.log(chalk.yellow('Once you do that, please check our internet connection and try again'));
								throw stderr2 + '\nAND\n' + err2;
							} else {
								if (code == 128){
									console.log(chalk.red.bold('Failed!'))
									console.log(chalk.yellow('Reason: Your internet connection appears down'));
								} else if (code == 1){
									console.log(chalk.red.bold('Failed!'))
									console.log(chalk.yellow('Reason: Please pull before pushing.'));
								} else {
									console.log(chalk.red.bold('Failed! Error code: ' + code.toString()), 'Try Googling this reason code to find out more.');
								}
							}
						});
					} else {
						// Otherwise, things went great!
						// Print the commands that got us to this auspicious moment.
						// Add a `_` for the `when` value because that needs to be one single string for the cli arg reader
						console.log(chalk.cyan('Settings: ') + 'swoop deploy -e ' + bucket_environment + ' -m ' + trigger_type + ' -l ' + local_path + ' -r ' + remote_path + ' -w ' + when.replace(' ', '_'));
						console.log(chalk.green('Push successful!'));
						var index = (bucket_environment == 'staging') ? '/index.html' : ''
						console.log('View at', chalk.bold('http://' + config.publishing.buckets[bucket_environment]  + '/' + remote_path + index));
					}
				});
			});

		} else {
			if (branch_status == 'uncommitted') throw chalk.red.bold('Error!') + chalk.yellow(' You have uncommitted changes on this branch.' + ' Please commit your changes before attempting to deploy.')
			if (branch_status == 'ahead_and_behind') throw chalk.red.bold('Error!') + chalk.yellow(' You have unpushed commits on this branch and your local branch is behind your remote.' + ' Please pull and then push your changes before attempting to deploy.')
			// EDIT: It's okay if they haven't push their commits. Like the edit above to the scrub push, removing this step will result in fewer pushes for the server to respond to. This alert is currently not being triggered because the branch status no longer as a condition where it is set to ahead.
			// if (branch_status == 'ahead') throw chalk.red.bold('Error!') + ' You have unpushed commits on this branch.' + ' Please push your changes before attempting to deploy.'.yellow;
			if (branch_status == 'behind') throw chalk.red.bold('Error!') + chalk.yellow(' Your local branch is behind your remote.' + ' Please pull, merge and push before attempting to deploy.')
		}
	});
}

/*    C R E A T E  A R C H I V E  B R A N C H   */
function addToArchive(deploySettings){
	var local_branch = deploySettings.local_branch;
	var remote_branch = deploySettings.remote_branch;

  setConfig(true);
  var repo_name = path.basename(path.resolve('.'));
  var archive_push = sh_commands.archive(config.github.login_method, config.github.account_name, config.archive.repo_name, local_branch, remote_branch);

	console.log(chalk.bgBlue.black('Pushing to GitHub...'));
	child.spawn( archive_push[0], archive_push[1], {stdio: 'inherit'} )
	  .on('close', function(code){
	  	if (code != 0){
				console.log(chalk.red.bold('Archive failed.', 'Error code: ' + chalk.bold( code.toString()) ));
				if (code == 128){
					console.log(chalk.yellow('Reason:') + chalk.bold('Your internet connection appears down'));
				}
	  	} else {
	  		console.log(chalk.green('Success!') + ' `' + local_branch + '` branch of `' + repo_name + '` archived as `' + remote_branch + '` on the `' + config.archive.repo_name + '` repo.\n  https://github.com/' + config.github.account_name + '/' + config.archive.repo_name + '/tree/' + remote_branch + '\n' + chalk.cyan('Note:') + ' Your existing repo has not been deleted. Please do that manually through GitHub:\n  https://github.com/' + config.github.account_name + '/' + repo_name + '/settings')
	  	}
	  })
}

function reportError(err, msg){
	console.log(msg, '\nReason:');
	throw err;
}

module.exports = {
	setConfig: setConfig,
	config: configClient,
	init: initAll,
	deploy: deployLastCommit,
	archive: addToArchive,
	unschedule: deployLastCommit
}
<|endoftext|>


<commit_before>jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new jQuery.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         *
         */
        callRemote: function () {
            var el      = $(this),
                data    = [],
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href');

            if (el.context.tagName.toUpperCase() === 'FORM') {
                data = el.serializeArray();
            }

            // TODO: should let the developer know no url was found
            if (url !== undefined) {
                if (el.triggerAndReturn('ajax:before')) {
                    $.ajax({
                        url: url,
                        data: data,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader("Accept", "text/javascript");
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote="true"]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote="true"],input[data-remote="true"]').live('click', function (e) {
      $(this).callRemote();
      e.preventDefault();
    });

    $('a[data-method][data-remote!=true]').live('click',function(e){
      var link = $(this),
        href = link.attr('href'),
        method = link.attr('data-method'),
        form = $('<form method="post" action="'+href+'">'),
        input = $('<input name="_method" value="'+method+'" type="hidden" />'),
        csrf_input = $('<input name="authenticity_token" value="'+csrf_token+'" type="hidden" />');

      form.hide();
      form.append(input);
      form.append(csrf_input);

      $('body').append(form); // redundant?

      e.preventDefault();
      form.submit();
    });

    /**
     * disable_with handlers
     */
    $('form[data-remote="true"]').live('ajax:before', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.data('enable_with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $('form[data-remote="true"]').live('ajax:after', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.removeAttr('disabled')
                 .val(input.data('enable_with'));
        });
    });
});

<commit_msg>white space + now dynamically acquire the csrf-param<commit_after>jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new jQuery.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         *
         */
        callRemote: function () {
            var el      = $(this),
                data    = [],
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href');

            if (el.context.tagName.toUpperCase() === 'FORM') {
                data = el.serializeArray();
            }

            // TODO: should let the developer know no url was found
            if (url !== undefined) {
                if (el.triggerAndReturn('ajax:before')) {
                    $.ajax({
                        url: url,
                        data: data,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader("Accept", "text/javascript");
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote="true"]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote="true"],input[data-remote="true"]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-method][data-remote!=true]').live('click',function(e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'">'),
            input = $('<input name="_method" value="'+method+'" type="hidden" />'),
            csrf_input = $('<input name="'+csrf-param+'" value="'+csrf-token+'" type="hidden" />');

        form.hide();
        form.append(input);
        form.append(csrf_input);

        $('body').append(form); // redundant?

        e.preventDefault();
        form.submit();
    });

    /**
     * disable_with handlers
     */
    $('form[data-remote="true"]').live('ajax:before', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.data('enable_with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $('form[data-remote="true"]').live('ajax:after', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.removeAttr('disabled')
                 .val(input.data('enable_with'));
        });
    });
});

<|endoftext|>


<commit_before>import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import BrowserRouter from 'react-router-dom/BrowserRouter';
import throttle from 'lodash/throttle';
import { loadState, saveState } from './utils/localStorage';
import rootReducer from './reducers';
import App from './views/App';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, loadState(), composeEnhancers(
  applyMiddleware(thunk)
));

store.subscribe(throttle(() => {
  const { addForm, curUser } = store.getState();
  saveState({
    addForm,
    curUser
  });
}), 2000);

render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('content')
);

/*
TODO :
//Get the RecipePage.js working fully :
  The logic for updating firebase is not done but that should be easy
  after that implement button in InputListForm to save / undo changes
  then change all the other infos to be editable

- Add.js : autofocus is set on the lowest "InputListForm" instead of top of the page, fix that

- Add.js : add some sort of markdown / formatting for
  Right now not even the line breaks are showing :c

- Add.js : add transition + delay for error messages so that they don't appear instantly ?

- Add.js / RecipePage.js : fix InputListForm up / down buttons range

- addForm.js / actions : change "name" param to "field" ?

- App.js / RecipePage.js : currently if we press F5 on the page of a recipe
  We get a blank page right now because details are fetched in other page
  This will be fixed when I add storage for the recipes so I can make a quick fix
  For example adding the recipe fetching to App.js since it is importing firebase anyway

// Browse.js :
- add first to last / last to first settings for triage
- add triage by rating (later)
- add pagination of results ?

- FIX react router keeping the scrolling intact on route change!
- Change avatar (also in menu items) to use image instead of background image maybe ?
- Add transition delay as inline style for user menu items
- Convert the NavBar to be dynamic based off route
- add "fake" loader as top bar (http://ricostacruz.com/nprogress/)
- add a generic loader component (the same one as my twitch project ?)
- Add a <form> html element to the Landing page header so that enter navigates to "browse"

- use firebase rest api instead of sdk to shave client bytes

// Add.js :
- Fix the "e" key reseting the numeric field ?
- Fix minor issue when the "delete" button on a input list is being clicked,
  it triggers the error message (onBlur)
- Add a recap of errors near the submit button ? (not necessary)
- Fix the ingredients form getting rendered twice on load

// RecipePage.js :
- Check if it is best to not save changes right off the bat and
  maybe provide a way to undo changes
- See if the edit errors should be based off focus or if I can keep them the way they are now

- polish the ux, animations, transition between routes..
  look at reactcsstransitiongroup & react motion
- add extract text plugin for the css, update my webpack config in that regard
- Service Worker : check offline-plugin for webpack (check reference from kent c. dodds)
- Try to see if I can cache the recipes for offline and only update the modified records,
  check localforage, redux persist..
- localforage looks good
- find some improvements to start downloading other common routes :
  this should be automatic with offline-plugin / service worker (otherwise try prefetch plugin)
  from the landing page after it is interactive
*/
<commit_msg>Update todo list<commit_after>import React from 'react';
import { render } from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import BrowserRouter from 'react-router-dom/BrowserRouter';
import throttle from 'lodash/throttle';
import { loadState, saveState } from './utils/localStorage';
import rootReducer from './reducers';
import App from './views/App';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, loadState(), composeEnhancers(
  applyMiddleware(thunk)
));

store.subscribe(throttle(() => {
  const { addForm, curUser } = store.getState();
  saveState({
    addForm,
    curUser
  });
}), 2000);

render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('content')
);

/*
TODO :
//Get the RecipePage.js working fully :
  The logic for updating firebase is not done but that should be easy
  after that implement button in InputListForm to save / undo changes
  then change all the other infos to be editable

- Add.js : add some sort of markdown / formatting for the textareas
  Right now not even the line breaks are showing :c

- Add.js : add transition + delay for error messages so that they don't appear instantly ?

- Add.js / RecipePage.js : fix InputListForm up / down buttons range

- addForm.js / actions : change "name" param to "field" ?

- App.js / RecipePage.js : currently if we press F5 on the page of a recipe
  We get a blank page right now because details are fetched in other page
  This will be fixed when I add storage for the recipes so I can make a quick fix
  For example adding the recipe fetching to App.js since it is importing firebase anyway

- Add a modal for confirmation on important actions (delete, edit maybe ?)

// Browse.js :
- add first to last / last to first settings for triage
- add triage by rating (later)
- add pagination of results ?

- Change avatar (also in menu items) to use image instead of background image maybe ?
- Add transition delay as inline style for user menu items
- Convert the NavBar to be dynamic based off route
- add "fake" loader as top bar (http://ricostacruz.com/nprogress/)
- Add a <form> html element to the Landing page header so that enter navigates to "browse"

- use firebase rest api instead of sdk to shave client bytes

// Add.js :
- Fix the "e" key reseting the numeric field ?
- Add a recap of errors near the submit button ? (not necessary)

// RecipePage.js :
- Check if it is best to not save changes right off the bat and
  maybe provide a way to undo changes
- See if the edit errors should be based off focus or if I can keep them the way they are now

- polish the ux, animations, transition between routes..
  look at reactcsstransitiongroup & react motion
- add extract text plugin for the css, update my webpack config in that regard
- Service Worker : check offline-plugin for webpack (check reference from kent c. dodds)
- Try to see if I can cache the recipes for offline and only update the modified records,
  check localforage, redux persist..
  localforage looks good
- find some improvements to start downloading other common routes :
  this should be automatic with offline-plugin / service worker (otherwise try prefetch plugin)
  from the landing page after it is interactive
*/
<|endoftext|>


<commit_before>import Main from './views/Main/main';
import SignUp from './views/signUp/signUp';
import SignIn from './views/signIn/signIn';

import Router from './modules/router/router';

const main = document.getElementsByClassName('main')[0];

// background
main.style.background = `url(static/img/backgrounds/${Math.floor(Math.random() * 3)}.jpg) no-repeat center fixed`;
main.style.backgroundSize = 'cover';

const router = new Router();

router
    .use('', Main)
    .use('/signup', SignUp)
    .use('/signin', SignIn)
    .start();

window.addEventListener('click', event => {
    const element = event.target;
    if (!element.getAttribute('data-url')) {
        return;
    }
    router.go(element.getAttribute('data-url'));
});<commit_msg>fix import dir<commit_after>import Main from './views/Main/main';
import SignUp from './views/SignUp/signUp';
import SignIn from './views/SignIn/signIn';

import Router from './modules/router/router';

const main = document.getElementsByClassName('main')[0];

// background
main.style.background = `url(static/img/backgrounds/${Math.floor(Math.random() * 3)}.jpg) no-repeat center fixed`;
main.style.backgroundSize = 'cover';

const router = new Router();

router
    .use('', Main)
    .use('/signup', SignUp)
    .use('/signin', SignIn)
    .start();

window.addEventListener('click', event => {
    const element = event.target;
    if (!element.getAttribute('data-url')) {
        return;
    }
    router.go(element.getAttribute('data-url'));
});<|endoftext|>


<commit_before>var path = require('path');

var Mocha = require('mocha');

/**
 * @param {Object} [options]
 * @param {String} [options.reporter] One of Mocha's reporters.
 * @param {Function} [options.featureNameFormatter(feature)] Custom Feature name formatter.
 * @param {Function} [options.scenarioNameFormatter(feature, scenario)] Custom Scenario name formatter.
 * @param {Function} [options.stepNameFormatter(feature, scenario, step)] Custom Step name formatter.
 */
module.exports = function (options) {

  // gathers options from the command line (in "--option_key:option_value" form)
  process.argv.reduce(function (obj, value) {
    var groups = /^--(\w+):(.+)$/.exec(value);
    if (groups !== null) {
      obj[groups[1]] = groups[2];
    }
    return obj;
  }, options || (options = {}));

  if (!options.reporter) {
    return; // activate only if reporter has been passed in
  }

  // currently there is no way to unregister Cucumber's "formatter" (cucumber@0.4.0)
  // as the result resorting to some REALLY nasty stuff here (suppressing output outside of "safe" boundaries)

  var output = (function () {
    var stdout = process.stdout;
    var originalWrite = stdout.write;
    var suppressedWrite = function () {
      return true;
    };
    suppressedWrite._original = originalWrite;
    return {
      off: function (fn) {
        if (fn) {
          return function () {
            output.off();
            fn.apply(this, arguments);
          };
        }
        stdout.write = suppressedWrite;
      },
      on: function () {
        stdout.write = originalWrite;
      }
    };
  }());

  var originalRegisterHandler = this.registerHandler;
  this.registerHandler = function (eventName, handler) {
    originalRegisterHandler.call(this, eventName, function (event, callback) {
      output.on();
      handler.call(this, event, output.off(callback));
    });
  };

  var originalDefineStep = this.defineStep;
  this.Given = this.When = this.Then = this.defineStep = function (name, fn) {
    originalDefineStep.call(this, name, function () {
      output.on();
      var args = Array.prototype.slice.call(arguments);
      var originalCallback = args[args.length - 1];
      originalCallback.pending = output.off(originalCallback.pending);
      originalCallback.fail = output.off(originalCallback.fail);
      args[args.length - 1] = output.off(originalCallback);
      fn.apply(this, args);
    });
  };

  var runner, featureIndex, scenarioIndex, stepIndex;

  this.registerHandler('BeforeFeatures', function (event, callback) {
    var rootSuite = new Mocha.Suite('');

    var features = event.getPayloadItem('features');
    if (!features) {
      throw new Error('This version of cucumber-caffeinator requires cucumber 0.4.1+. ' +
        'Check https://github.com/shyiko/cucumber-js-caffeinator for more information.');
    }
    var cwd = process.cwd();
    features.getFeatures().syncForEach(function (feature) {
      var suite = new Mocha.Suite(options.featureNameFormatter ?
        options.featureNameFormatter(feature) :
        feature.getName() + ' (' + path.relative(cwd, feature.getUri()) + ')');
      rootSuite.addSuite(suite);
      feature.getFeatureElements().syncForEach(function (scenario) {
        var innerSuite = new Mocha.Suite(options.scenarioNameFormatter ?
          options.scenarioNameFormatter(feature, scenario) :
          scenario.getName() + ' (' + path.relative(cwd, scenario.getUri()) + ':' + scenario.getLine() + ')');
        suite.addSuite(innerSuite);
        var stepCollections = [scenario.getSteps()];
        if (feature.hasBackground()) {
          stepCollections.unshift(feature.getBackground().getSteps());
        }
        stepCollections.forEach(function (steps) {
          steps.syncForEach(function (step) {
            innerSuite.addTest(new Mocha.Test(options.stepNameFormatter ?
              options.stepNameFormatter(feature, scenario, step) :
              step.getKeyword() + step.getName(), function () { /* omitted */ }));
          });
        });
      });
    });

    runner = new Mocha.Runner(rootSuite);
    runner.ignoreLeaks = true;

    var initializeReporter = function (name) {
      var context = {};
      Mocha.prototype.reporter.call(context, name);
      return new context._reporter(runner);
    };

    initializeReporter(options.reporter);

    featureIndex = 0;
    runner.emit('start');
    process.nextTick(callback);
  });

  this.registerHandler('BeforeFeature', function (event, callback) {
    scenarioIndex = 0;
    runner.emit('suite', runner.suite.suites[featureIndex]);
    process.nextTick(callback);
  });

  this.registerHandler('BeforeScenario', function (event, callback) {
    stepIndex = 0;
    runner.emit('suite', runner.suite.suites[featureIndex].suites[scenarioIndex]);
    process.nextTick(callback);
  });

  this.registerHandler('BeforeStep', function (event, callback) {
    runner.emit('test', runner.suite.suites[featureIndex].suites[scenarioIndex].tests[stepIndex]);
    process.nextTick(callback);
  });

  this.registerHandler('StepResult', function (event, callback) {
    var stepResult = event.getPayloadItem('stepResult');
    var test = runner.suite.suites[featureIndex].suites[scenarioIndex].tests[stepIndex];
    test.duration = stepResult.getDuration() / 1e6;
    if (stepResult.isSuccessful()) {
      runner.emit('pass', test);
    } else if (stepResult.isPending()) {
      runner.emit('pending', test);
    } else if (stepResult.isUndefined() || stepResult.isSkipped()) {
      runner.emit('pending', test);
    } else {
      var err = stepResult.getFailureException();
      runner.emit('fail', test, err);
    }
    process.nextTick(callback);
  });

  this.registerHandler('AfterStep', function (event, callback) {
    runner.emit('test end', runner.suite.suites[featureIndex].suites[scenarioIndex].tests[stepIndex++]);
    process.nextTick(callback);
  });

  this.registerHandler('AfterScenario', function (event, callback) {
    runner.emit('suite end', runner.suite.suites[featureIndex].suites[scenarioIndex++]);
    process.nextTick(callback);
  });

  this.registerHandler('AfterFeature', function (event, callback) {
    runner.emit('suite end', runner.suite.suites[featureIndex++]);
    process.nextTick(callback);
  });

  this.registerHandler('AfterFeatures', function (event, callback) {
    runner.emit('end');
    process.nextTick(callback);
  });

};<commit_msg>Replaced NaN test duration with 0<commit_after>var path = require('path');

var Mocha = require('mocha');

/**
 * @param {Object} [options]
 * @param {String} [options.reporter] One of Mocha's reporters.
 * @param {Function} [options.featureNameFormatter(feature)] Custom Feature name formatter.
 * @param {Function} [options.scenarioNameFormatter(feature, scenario)] Custom Scenario name formatter.
 * @param {Function} [options.stepNameFormatter(feature, scenario, step)] Custom Step name formatter.
 */
module.exports = function (options) {

  // gathers options from the command line (in "--option_key:option_value" form)
  process.argv.reduce(function (obj, value) {
    var groups = /^--(\w+):(.+)$/.exec(value);
    if (groups !== null) {
      obj[groups[1]] = groups[2];
    }
    return obj;
  }, options || (options = {}));

  if (!options.reporter) {
    return; // activate only if reporter has been passed in
  }

  // currently there is no way to unregister Cucumber's "formatter" (cucumber@0.4.0)
  // as the result resorting to some REALLY nasty stuff here (suppressing output outside of "safe" boundaries)

  var output = (function () {
    var stdout = process.stdout;
    var originalWrite = stdout.write;
    var suppressedWrite = function () {
      return true;
    };
    suppressedWrite._original = originalWrite;
    return {
      off: function (fn) {
        if (fn) {
          return function () {
            output.off();
            fn.apply(this, arguments);
          };
        }
        stdout.write = suppressedWrite;
      },
      on: function () {
        stdout.write = originalWrite;
      }
    };
  }());

  var originalRegisterHandler = this.registerHandler;
  this.registerHandler = function (eventName, handler) {
    originalRegisterHandler.call(this, eventName, function (event, callback) {
      output.on();
      handler.call(this, event, output.off(callback));
    });
  };

  var originalDefineStep = this.defineStep;
  this.Given = this.When = this.Then = this.defineStep = function (name, fn) {
    originalDefineStep.call(this, name, function () {
      output.on();
      var args = Array.prototype.slice.call(arguments);
      var originalCallback = args[args.length - 1];
      originalCallback.pending = output.off(originalCallback.pending);
      originalCallback.fail = output.off(originalCallback.fail);
      args[args.length - 1] = output.off(originalCallback);
      fn.apply(this, args);
    });
  };

  var runner, featureIndex, scenarioIndex, stepIndex;

  this.registerHandler('BeforeFeatures', function (event, callback) {
    var rootSuite = new Mocha.Suite('');

    var features = event.getPayloadItem('features');
    if (!features) {
      throw new Error('This version of cucumber-caffeinator requires cucumber 0.4.1+. ' +
        'Check https://github.com/shyiko/cucumber-js-caffeinator for more information.');
    }
    var cwd = process.cwd();
    features.getFeatures().syncForEach(function (feature) {
      var suite = new Mocha.Suite(options.featureNameFormatter ?
        options.featureNameFormatter(feature) :
        feature.getName() + ' (' + path.relative(cwd, feature.getUri()) + ')');
      rootSuite.addSuite(suite);
      feature.getFeatureElements().syncForEach(function (scenario) {
        var innerSuite = new Mocha.Suite(options.scenarioNameFormatter ?
          options.scenarioNameFormatter(feature, scenario) :
          scenario.getName() + ' (' + path.relative(cwd, scenario.getUri()) + ':' + scenario.getLine() + ')');
        suite.addSuite(innerSuite);
        var stepCollections = [scenario.getSteps()];
        if (feature.hasBackground()) {
          stepCollections.unshift(feature.getBackground().getSteps());
        }
        stepCollections.forEach(function (steps) {
          steps.syncForEach(function (step) {
            innerSuite.addTest(new Mocha.Test(options.stepNameFormatter ?
              options.stepNameFormatter(feature, scenario, step) :
              step.getKeyword() + step.getName(), function () { /* omitted */ }));
          });
        });
      });
    });

    runner = new Mocha.Runner(rootSuite);
    runner.ignoreLeaks = true;

    var initializeReporter = function (name) {
      var context = {};
      Mocha.prototype.reporter.call(context, name);
      return new context._reporter(runner);
    };

    initializeReporter(options.reporter);

    featureIndex = 0;
    runner.emit('start');
    process.nextTick(callback);
  });

  this.registerHandler('BeforeFeature', function (event, callback) {
    scenarioIndex = 0;
    runner.emit('suite', runner.suite.suites[featureIndex]);
    process.nextTick(callback);
  });

  this.registerHandler('BeforeScenario', function (event, callback) {
    stepIndex = 0;
    runner.emit('suite', runner.suite.suites[featureIndex].suites[scenarioIndex]);
    process.nextTick(callback);
  });

  this.registerHandler('BeforeStep', function (event, callback) {
    runner.emit('test', runner.suite.suites[featureIndex].suites[scenarioIndex].tests[stepIndex]);
    process.nextTick(callback);
  });

  this.registerHandler('StepResult', function (event, callback) {
    var stepResult = event.getPayloadItem('stepResult');
    var test = runner.suite.suites[featureIndex].suites[scenarioIndex].tests[stepIndex];
    test.duration = stepResult.getDuration() / 1e6 || 0;
		if (stepResult.isSuccessful()) {
      runner.emit('pass', test);
    } else if (stepResult.isPending()) {
      runner.emit('pending', test);
    } else if (stepResult.isUndefined() || stepResult.isSkipped()) {
      runner.emit('pending', test);
    } else {
      var err = stepResult.getFailureException();
      runner.emit('fail', test, err);
    }
    process.nextTick(callback);
  });

  this.registerHandler('AfterStep', function (event, callback) {
    runner.emit('test end', runner.suite.suites[featureIndex].suites[scenarioIndex].tests[stepIndex++]);
    process.nextTick(callback);
  });

  this.registerHandler('AfterScenario', function (event, callback) {
    runner.emit('suite end', runner.suite.suites[featureIndex].suites[scenarioIndex++]);
    process.nextTick(callback);
  });

  this.registerHandler('AfterFeature', function (event, callback) {
    runner.emit('suite end', runner.suite.suites[featureIndex++]);
    process.nextTick(callback);
  });

  this.registerHandler('AfterFeatures', function (event, callback) {
    runner.emit('end');
    process.nextTick(callback);
  });

};<|endoftext|>


<commit_before>'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var React = require('react');
var ReactDom = require('react-dom');
var f1DOM = require('f1-dom');
var merge = require('deep-extend');

class ReactF1 extends React.Component {

  constructor(props) {
    super(props);

    this.hasMounted = false;
    this.f1 = null;

    this.state = {};
  }

  setupFromProps(props) {
    if (!this.f1) {
      this.initFromProps(props);
    } else {
      this.updateFromProps(props);
    }
  }

  initFromProps(props) {
    if (this.hasMounted && props.go && props.states && props.transitions) {

      var el = ReactDom.findDOMNode(this);
      var f1 = this.f1 = f1DOM({
        el: el,
        states: props.states,
        transitions: props.transitions
      });

      f1.on('state', this.handleState.bind(this));
      f1.on('update', this.handleUpdate.bind(this));
      this.updateListenersFromProps(props);

      this.setState({
        states: props.states
      });

      f1.init(props.go);
    }
  }

  updateFromProps(props) {
    var states;

    if (props.states) {

      merge(this.state.states, props.states);

      this.setState({
        states: this.state.states
      });

      // force an update to f1 since we received new props
      this.f1.update();
    }

    if (props.go) {
      if (this.f1) {
        this.f1.go(props.go, props.onComplete);
      }
    }

    this.updateListenersFromProps(props);
  }

  updateListenersFromProps(props) {
    this.setState({
      onUpdate: props.onUpdate,
      onState: props.onState
    });
  }

  handleUpdate() {
    if (this.state.onUpdate) {
      this.state.onUpdate.apply(undefined, arguments);
    }
  }

  handleState() {
    if (this.state.onState) {
      this.state.onState.apply(undefined, arguments);
    }
  }

  componentWillReceiveProps(nextProps) {
    this.setupFromProps(nextProps);
  }

  componentDidMount() {
    this.hasMounted = true;
    this.setupFromProps(this.props);
  }

  componentWillUnmount() {
    if (this.f1) {
      this.f1.destroy();
    }
  }

  render() {
    var style = merge(
      {
        perspective: '1000px'
      },
      this.props.style
    );

    if (!this.f1) {
      style = merge({}, this.props.style, {
        display: 'none'
      });
    }

    return React.createElement(
      'div',
      _extends({}, this.props, {
        style: style
      }),
      this.props.children
    );
  }
}

function findTargets(children, targets) {
  targets = targets || {};

  React.Children.forEach(children, function (child) {
    if (child.props) {
      var targetName = child.props['f1-target'];

      if (targetName) {
        targets[targetName] = child;
      }

      if (child.props.children) {
        findTargets(child.props.children, targets);
      }
    }
  });

  return targets;
}

module.exports = ReactF1;<commit_msg>Removed unused function Fixed warning for React 15.2.0+<commit_after>'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var React = require('react');
var ReactDom = require('react-dom');
var f1DOM = require('f1-dom');
var merge = require('deep-extend');

class ReactF1 extends React.Component {

  constructor(props) {
    super(props);

    this.hasMounted = false;
    this.f1 = null;

    this.state = {};
  }

  setupFromProps(props) {
    if (!this.f1) {
      this.initFromProps(props);
    } else {
      this.updateFromProps(props);
    }
  }

  initFromProps(props) {
    if (this.hasMounted && props.go && props.states && props.transitions) {

      var el = ReactDom.findDOMNode(this);
      var f1 = this.f1 = f1DOM({
        el: el,
        states: props.states,
        transitions: props.transitions
      });

      f1.on('state', this.handleState.bind(this));
      f1.on('update', this.handleUpdate.bind(this));
      this.updateListenersFromProps(props);

      this.setState({
        states: props.states
      });

      f1.init(props.go);
    }
  }

  updateFromProps(props) {
    var states;

    if (props.states) {

      merge(this.state.states, props.states);

      this.setState({
        states: this.state.states
      });

      // force an update to f1 since we received new props
      this.f1.update();
    }

    if (props.go) {
      if (this.f1) {
        this.f1.go(props.go, props.onComplete);
      }
    }

    this.updateListenersFromProps(props);
  }

  updateListenersFromProps(props) {
    this.setState({
      onUpdate: props.onUpdate,
      onState: props.onState
    });
  }

  handleUpdate() {
    if (this.state.onUpdate) {
      this.state.onUpdate.apply(undefined, arguments);
    }
  }

  handleState() {
    if (this.state.onState) {
      this.state.onState.apply(undefined, arguments);
    }
  }

  componentWillReceiveProps(nextProps) {
    this.setupFromProps(nextProps);
  }

  componentDidMount() {
    this.hasMounted = true;
    this.setupFromProps(this.props);
  }

  componentWillUnmount() {
    if (this.f1) {
      this.f1.destroy();
    }
  }

  cleanProps(props) {
    delete props.go;
    delete props.transitions;
    delete props.states;
    delete props.onComplete;
    return props;
  }

  render() {
    var style = merge(
      {
        perspective: '1000px'
      },
      this.props.style
    );

    if (!this.f1) {
      style = merge({}, this.props.style, {
        display: 'none'
      });
    }

    var props = _extends({}, this.props, {
      style: style
    });

    return React.createElement(
      'div',
      this.cleanProps(props),
      this.props.children
    );
  }
}

module.exports = ReactF1;<|endoftext|>


<commit_before>import './styles.styl';
import React from 'react';
import App from './App';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { tasks } from './reducers';

let root = document.getElementById('app');
let store = createStore(tasks);

React.render(
  <Provider store={store}>
      { () => <App /> }
  </Provider>,
  root);
<commit_msg>Introduce redux dev tools<commit_after>import './styles.styl';
import React from 'react';
import App from './App';
import { compose, createStore } from 'redux';
import { devTools, persistState } from 'redux-devtools';
import { Provider } from 'react-redux';
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react';
import { tasks } from './reducers';

const finalCreateStore = compose(
  // Provides support for DevTools:
  devTools(),
  // Lets you write ?debug_session=<name> in address bar to persist debug sessions
  persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
  createStore
);

let root = document.getElementById('app');
let store = finalCreateStore(tasks);

React.render(
  <div>
    <Provider store={store}>
      { () => <App /> }
    </Provider>
    <DebugPanel top right bottom>
      <DevTools store={store} monitor={LogMonitor} />
    </DebugPanel>
  </div>,
  root);
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import classNames from 'classnames';
import moment from 'moment';
import debounce from 'lodash/debounce';
import range from 'lodash/range';
import {getScrollSpeed, getMonthsForYear, keyCodes, parseDate, validDate, validDisplay, validLayout} from './utils';
import defaultLocale from './locale';
import defaultTheme from './theme';
import Today from './Today';
import Header from './Header';
import List from './List';
import Weekdays from './Weekdays';
import Years from './Years';

const containerStyle = require('./Container.scss');
const dayStyle = require('./Day/Day.scss');
const style = {
	container: containerStyle,
	day: dayStyle
};

export default class InfiniteCalendar extends Component {
	constructor(props) {
		super();

		// Initialize
		this.updateLocale(props.locale);
		this.updateYears(props);
		this.state = {
			selectedDate: this.parseSelectedDate(props.selectedDate),
			display: props.display
		};
	}
	static defaultProps = {
		width: 400,
		height: 500,
		rowHeight: 56,
		overscanMonthCount: 4,
		layout: 'portrait',
		display: 'days',
		selectedDate: new Date(),
		min: {year: 1980, month: 0, day: 0},
		minDate: {year: 1980, month: 0, day: 0},
		max: {year: 2050, month: 11, day: 31},
		maxDate: {year: 2050, month: 11, day: 31},
		keyboardSupport: true,
		autoFocus: true,
		shouldHeaderAnimate: true,
		showOverlay: true,
		showTodayHelper: true,
		showHeader: true,
		tabIndex: 1,
		locale: {},
		theme: {},
		hideYearsOnSelect: true
	};
	static propTypes = {
		selectedDate: validDate,
		min: validDate,
		max: validDate,
		minDate: validDate,
		maxDate: validDate,
		locale: PropTypes.object,
		theme: PropTypes.object,
		width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
		height: PropTypes.number,
		rowHeight: PropTypes.number,
		className: PropTypes.string,
		overscanMonthCount: PropTypes.number,
		disabledDays: PropTypes.arrayOf(PropTypes.number),
		disabledDates: PropTypes.arrayOf(validDate),
		beforeSelect: PropTypes.func,
		onSelect: PropTypes.func,
		afterSelect: PropTypes.func,
		onScroll: PropTypes.func,
		onScrollEnd: PropTypes.func,
		keyboardSupport: PropTypes.bool,
		autoFocus: PropTypes.bool,
		onKeyDown: PropTypes.func,
		tabIndex: PropTypes.number,
		layout: validLayout,
		display: validDisplay,
		hideYearsOnSelect: PropTypes.bool,
		shouldHeaderAnimate: PropTypes.bool,
		showOverlay: PropTypes.bool,
		showTodayHelper: PropTypes.bool,
		showHeader: PropTypes.bool
	};
	componentDidMount() {
		let {autoFocus, keyboardSupport} = this.props;
		this.node = this.refs.node;
		this.list = this.refs.List;

		if (keyboardSupport && autoFocus) {
			this.node.focus();
		}
	}
	componentWillReceiveProps(next) {
		let {min, minDate, max, maxDate, locale, selectedDate} = this.props;
		let {display} = this.state;

		if (next.locale !== locale) {
			this.updateLocale(next.locale);
		}
		if (next.min !== min || next.minDate !== minDate || next.max !== max || next.maxDate !== maxDate) {
			this.updateYears(next);
		}
		if (next.selectedDate !== selectedDate) {
			this.setState({
				selectedDate: this.parseSelectedDate(next.selectedDate)
			});
		} else if (next.minDate !== minDate || next.maxDate !== maxDate) {
			// Need to make sure the currently selected date is not before the new minDate or after maxDate
			let _selectedDate = this.parseSelectedDate(this.state.selectedDate);
			if (!_selectedDate.isSame(this.state.selectedDate, 'day')) {
				this.setState({
					selectedDate: _selectedDate
				});
			}
		}
		if (next.display !== display) {
			this.setState({
				display: next.display
			});
		}
	}
	parseSelectedDate(selectedDate) {
		if (selectedDate) {
			selectedDate = moment(selectedDate);

			// Selected Date should not be before min date or after max date
			if (selectedDate.isBefore(this._minDate)) {
				return this._minDate;
			} else if (selectedDate.isAfter(this._maxDate)) {
				return this._maxDate;
			}
		}

		return selectedDate;
	}
	updateYears(props = this.props) {
		let min = this._min = moment(props.min);
		let max = this._max = moment(props.max);
		this._minDate = moment(props.minDate);
		this._maxDate = moment(props.maxDate);

		this.years = range(min.year(), max.year() + 1).map((year) => getMonthsForYear(year, min, max));
		this.months = [].concat.apply([], this.years);
	}
	updateLocale(locale) {
		locale = this.getLocale(locale);
		moment.updateLocale(locale.name, locale);
		moment.locale(locale.name);
	}
	getDisabledDates(disabledDates) {
		return disabledDates && disabledDates.map((date) => moment(date).format('YYYYMMDD'));
	}
	getLocale(customLocale = this.props.locale) {
		return Object.assign({}, defaultLocale, customLocale);
	}
	getTheme(customTheme = this.props.theme) {
		return Object.assign({}, defaultTheme, customTheme);
	}
	onDaySelect = (selectedDate, e) => {
		let {afterSelect, beforeSelect, onSelect} = this.props;

		if (!beforeSelect || typeof beforeSelect == 'function' && beforeSelect(selectedDate)) {
			if (typeof onSelect == 'function') {
				onSelect(selectedDate, e);
			}

			this.setState({
				selectedDate,
				highlightedDate: selectedDate.clone()
			}, () => {
				this.clearHighlight();
				if (typeof afterSelect == 'function') {
					afterSelect(selectedDate);
				}
			});
		}
	};
	getCurrentOffset = () => {
		return this.scrollTop;
	}
	getDateOffset = (date) => {
		return this.list && this.list.getDateOffset(date);
	};
	scrollTo = (offset) => {
		return this.list && this.list.scrollTo(offset);
	}
	scrollToDate = (date = moment(), offset) => {
		return this.list && this.list.scrollToDate(date, offset);
	};
	getScrollSpeed = getScrollSpeed();
	onScroll = ({scrollTop}) => {
		let {onScroll, showOverlay, showTodayHelper} = this.props;
		let {isScrolling} = this.state;
		let scrollSpeed = this.scrollSpeed = Math.abs(this.getScrollSpeed(scrollTop));
		this.scrollTop = scrollTop;

		// We only want to display the months overlay if the user is rapidly scrolling
		if (showOverlay && scrollSpeed >= 50 && !isScrolling) {
			this.setState({
				isScrolling: true
			});
		}

		if (showTodayHelper) {
			this.updateTodayHelperPosition(scrollSpeed);
		}
		if (typeof onScroll == 'function') {
			onScroll(scrollTop);
		}
		this.onScrollEnd();
	};
	onScrollEnd = debounce(() => {
		let {onScrollEnd, showTodayHelper} = this.props;
		let {isScrolling} = this.state;

		if (isScrolling) this.setState({isScrolling: false});
		if (showTodayHelper) this.updateTodayHelperPosition(0);
		if (typeof onScrollEnd == 'function') onScrollEnd(this.scrollTop);
	}, 150);
	updateTodayHelperPosition = (scrollSpeed) => {
		if (!this.todayOffset) this.todayOffset = this.getDateOffset(this.today.date);
		let {scrollTop} = this;
		let {showToday} = this.state;
		let newState;

		if (scrollTop >= this.todayOffset + 480) {
			if (showToday !== 1) newState = 1;
		} else if (scrollTop <= this.todayOffset - 500) {
			if (showToday !== -1) newState = -1;
		} else if (showToday && scrollSpeed <= 1) {
			newState = false;
		}

		if (scrollTop == 0) {
			newState = false;
		}

		if (newState != null) {
			this.setState({showToday: newState});
		}
	};
	handleKeyDown = (e) => {
		let {maxDate, minDate, onKeyDown} = this.props;
		let {display, selectedDate, highlightedDate, showToday} = this.state;
		let delta = 0;

		if (typeof onKeyDown == 'function') {
			onKeyDown(e);
		}
		if ([keyCodes.left, keyCodes.up, keyCodes.right, keyCodes.down].indexOf(e.keyCode) > -1 && typeof e.preventDefault == 'function') {
			e.preventDefault();
		}

		if (display == 'days') {
			if (!highlightedDate) {
				highlightedDate = this.state.selectedDate.clone();
				this.setState({highlightedDate});
			}

			switch (e.keyCode) {
				case keyCodes.enter:
					this.onDaySelect(moment(highlightedDate), e);
					return;
				case keyCodes.left:
					delta = -1;
					break;
				case keyCodes.right:
					delta = +1;
					break;
				case keyCodes.down:
					delta = +7;
					break;
				case keyCodes.up:
					delta = -7;
					break;
			}

			if (delta) {
				let {rowHeight} = this.props;
				let newHighlightedDate = moment(highlightedDate).add(delta, 'days');

				// Make sure the new highlighted date isn't before min / max
				if (newHighlightedDate.isBefore(minDate)) {
					newHighlightedDate = moment(minDate);
				} else if (newHighlightedDate.isAfter(maxDate)) {
					newHighlightedDate = moment(maxDate);
				}

				// Update the highlight indicator
				this.clearHighlight();

				// Scroll the view
				if (!this.currentOffset) this.currentOffset = this.getCurrentOffset();
				let currentOffset = this.currentOffset;
				let monthOffset = this.getDateOffset(newHighlightedDate);
				let navOffset = (showToday) ? 36 : 0;

				let highlightedEl = this.highlightedEl = this.node.querySelector(`[data-date='${newHighlightedDate.format('YYYYMMDD')}']`);

				// Edge-case: if the user tries to use the keyboard when the new highlighted date isn't rendered because it's too far off-screen
				// We need to scroll to the month of the new highlighted date so it renders
				if (!highlightedEl) {
					this.scrollTo(monthOffset - navOffset);
					return;
				}

				highlightedEl.classList.add(style.day.highlighted);

				let dateOffset = highlightedEl.offsetTop - rowHeight;
				let newOffset = monthOffset + dateOffset;

				if (currentOffset !== newOffset) {
					this.currentOffset = newOffset;
					this.scrollTo(newOffset - navOffset);
				}

				// Update the reference to the currently highlighted date
				this.setState({
					highlightedDate: newHighlightedDate
				});

			}
		} else if (display == 'years') {
			switch (e.keyCode) {
				case keyCodes.enter:
				case keyCodes.escape:
					this.setDisplay('days');
					this.scrollToDate(selectedDate, -40);
					return;
				case keyCodes.down:
					delta = +1;
					break;
				case keyCodes.up:
					delta = -1;
					break;
			}

			if (delta) {
				let newSelectedDate = selectedDate.clone().add(delta, 'year');

				if (!newSelectedDate.isAfter(maxDate, 'day') && !newSelectedDate.isBefore(minDate, 'day')) {
					this.onDaySelect(newSelectedDate, e);
				}
			}
		}
	};
	clearHighlight() {
		if (this.highlightedEl) {
			this.highlightedEl.classList.remove(style.day.highlighted);
			this.highlightedEl = null;
		}
	}
	setDisplay = (display) => {
		this.setState({display});
	}
	render() {
		let {
			className,
			disabledDays,
			height,
			hideYearsOnSelect,
			keyboardSupport,
			layout,
			overscanMonthCount,
			min,
			minDate,
			max,
			maxDate,
			shouldHeaderAnimate,
			showTodayHelper,
			showHeader,
			tabIndex,
			width,
			...other
		} = this.props;
		let disabledDates = this.getDisabledDates(this.props.disabledDates);
		let locale = this.getLocale();
		let theme = this.getTheme();
		let {display, isScrolling, selectedDate, showToday} = this.state;
		let today = this.today = parseDate(moment());

		// Selected date should not be disabled
		if (selectedDate && (disabledDates && disabledDates.indexOf(selectedDate.format('YYYYMMDD')) !== -1 || disabledDays && disabledDays.indexOf(selectedDate.day()) !== -1)) {
			selectedDate = null;
		}

		return (

				{showHeader &&

				}

					<Weekdays theme={theme} />

						{showTodayHelper &&
							<Today scrollToDate={this.scrollToDate} show={showToday} today={today} theme={theme} locale={locale} />
						}
						<List
							ref="List"
							{...other}
							width={width}
							height={height}
							selectedDate={parseDate(selectedDate)}
							disabledDates={disabledDates}
							disabledDays={disabledDays}
							months={this.months}
							onDaySelect={this.onDaySelect}
							onScroll={this.onScroll}
							isScrolling={isScrolling}
							today={today}
							min={parseDate(min)}
							minDate={parseDate(minDate)}
							maxDate={parseDate(maxDate)}
							theme={theme}
							locale={locale}
							overscanMonthCount={overscanMonthCount}
						/>

					{display == 'years' &&
						<Years
							ref="years"
							width={width}
							height={height}
							onDaySelect={this.onDaySelect}
							minDate={minDate}
							maxDate={maxDate}
							selectedDate={selectedDate}
							theme={theme}
							years={range(moment(min).year(), moment(max).year() + 1)}
							setDisplay={this.setDisplay}
							scrollToDate={this.scrollToDate}
							hideYearsOnSelect={hideYearsOnSelect}
						/>
					}

		);
	}
}
<commit_msg>Keyboard support didn't work if selectedDate was blank<commit_after>import React, {Component, PropTypes} from 'react';
import classNames from 'classnames';
import moment from 'moment';
import debounce from 'lodash/debounce';
import range from 'lodash/range';
import {getScrollSpeed, getMonthsForYear, keyCodes, parseDate, validDate, validDisplay, validLayout} from './utils';
import defaultLocale from './locale';
import defaultTheme from './theme';
import Today from './Today';
import Header from './Header';
import List from './List';
import Weekdays from './Weekdays';
import Years from './Years';

const containerStyle = require('./Container.scss');
const dayStyle = require('./Day/Day.scss');
const style = {
	container: containerStyle,
	day: dayStyle
};

export default class InfiniteCalendar extends Component {
	constructor(props) {
		super();

		// Initialize
		this.updateLocale(props.locale);
		this.updateYears(props);
		this.state = {
			selectedDate: this.parseSelectedDate(props.selectedDate),
			display: props.display
		};
	}
	static defaultProps = {
		width: 400,
		height: 500,
		rowHeight: 56,
		overscanMonthCount: 4,
		layout: 'portrait',
		display: 'days',
		selectedDate: new Date(),
		min: {year: 1980, month: 0, day: 0},
		minDate: {year: 1980, month: 0, day: 0},
		max: {year: 2050, month: 11, day: 31},
		maxDate: {year: 2050, month: 11, day: 31},
		keyboardSupport: true,
		autoFocus: true,
		shouldHeaderAnimate: true,
		showOverlay: true,
		showTodayHelper: true,
		showHeader: true,
		tabIndex: 1,
		locale: {},
		theme: {},
		hideYearsOnSelect: true
	};
	static propTypes = {
		selectedDate: validDate,
		min: validDate,
		max: validDate,
		minDate: validDate,
		maxDate: validDate,
		locale: PropTypes.object,
		theme: PropTypes.object,
		width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
		height: PropTypes.number,
		rowHeight: PropTypes.number,
		className: PropTypes.string,
		overscanMonthCount: PropTypes.number,
		disabledDays: PropTypes.arrayOf(PropTypes.number),
		disabledDates: PropTypes.arrayOf(validDate),
		beforeSelect: PropTypes.func,
		onSelect: PropTypes.func,
		afterSelect: PropTypes.func,
		onScroll: PropTypes.func,
		onScrollEnd: PropTypes.func,
		keyboardSupport: PropTypes.bool,
		autoFocus: PropTypes.bool,
		onKeyDown: PropTypes.func,
		tabIndex: PropTypes.number,
		layout: validLayout,
		display: validDisplay,
		hideYearsOnSelect: PropTypes.bool,
		shouldHeaderAnimate: PropTypes.bool,
		showOverlay: PropTypes.bool,
		showTodayHelper: PropTypes.bool,
		showHeader: PropTypes.bool
	};
	componentDidMount() {
		let {autoFocus, keyboardSupport} = this.props;
		this.node = this.refs.node;
		this.list = this.refs.List;

		if (keyboardSupport && autoFocus) {
			this.node.focus();
		}
	}
	componentWillReceiveProps(next) {
		let {min, minDate, max, maxDate, locale, selectedDate} = this.props;
		let {display} = this.state;

		if (next.locale !== locale) {
			this.updateLocale(next.locale);
		}
		if (next.min !== min || next.minDate !== minDate || next.max !== max || next.maxDate !== maxDate) {
			this.updateYears(next);
		}
		if (next.selectedDate !== selectedDate) {
			this.setState({
				selectedDate: this.parseSelectedDate(next.selectedDate)
			});
		} else if (next.minDate !== minDate || next.maxDate !== maxDate) {
			// Need to make sure the currently selected date is not before the new minDate or after maxDate
			let _selectedDate = this.parseSelectedDate(this.state.selectedDate);
			if (!_selectedDate.isSame(this.state.selectedDate, 'day')) {
				this.setState({
					selectedDate: _selectedDate
				});
			}
		}
		if (next.display !== display) {
			this.setState({
				display: next.display
			});
		}
	}
	parseSelectedDate(selectedDate) {
		if (selectedDate) {
			selectedDate = moment(selectedDate);

			// Selected Date should not be before min date or after max date
			if (selectedDate.isBefore(this._minDate)) {
				return this._minDate;
			} else if (selectedDate.isAfter(this._maxDate)) {
				return this._maxDate;
			}
		}

		return selectedDate;
	}
	updateYears(props = this.props) {
		let min = this._min = moment(props.min);
		let max = this._max = moment(props.max);
		this._minDate = moment(props.minDate);
		this._maxDate = moment(props.maxDate);

		this.years = range(min.year(), max.year() + 1).map((year) => getMonthsForYear(year, min, max));
		this.months = [].concat.apply([], this.years);
	}
	updateLocale(locale) {
		locale = this.getLocale(locale);
		moment.updateLocale(locale.name, locale);
		moment.locale(locale.name);
	}
	getDisabledDates(disabledDates) {
		return disabledDates && disabledDates.map((date) => moment(date).format('YYYYMMDD'));
	}
	getLocale(customLocale = this.props.locale) {
		return Object.assign({}, defaultLocale, customLocale);
	}
	getTheme(customTheme = this.props.theme) {
		return Object.assign({}, defaultTheme, customTheme);
	}
	onDaySelect = (selectedDate, e) => {
		let {afterSelect, beforeSelect, onSelect} = this.props;

		if (!beforeSelect || typeof beforeSelect == 'function' && beforeSelect(selectedDate)) {
			if (typeof onSelect == 'function') {
				onSelect(selectedDate, e);
			}

			this.setState({
				selectedDate,
				highlightedDate: selectedDate.clone()
			}, () => {
				this.clearHighlight();
				if (typeof afterSelect == 'function') {
					afterSelect(selectedDate);
				}
			});
		}
	};
	getCurrentOffset = () => {
		return this.scrollTop;
	}
	getDateOffset = (date) => {
		return this.list && this.list.getDateOffset(date);
	};
	scrollTo = (offset) => {
		return this.list && this.list.scrollTo(offset);
	}
	scrollToDate = (date = moment(), offset) => {
		return this.list && this.list.scrollToDate(date, offset);
	};
	getScrollSpeed = getScrollSpeed();
	onScroll = ({scrollTop}) => {
		let {onScroll, showOverlay, showTodayHelper} = this.props;
		let {isScrolling} = this.state;
		let scrollSpeed = this.scrollSpeed = Math.abs(this.getScrollSpeed(scrollTop));
		this.scrollTop = scrollTop;

		// We only want to display the months overlay if the user is rapidly scrolling
		if (showOverlay && scrollSpeed >= 50 && !isScrolling) {
			this.setState({
				isScrolling: true
			});
		}

		if (showTodayHelper) {
			this.updateTodayHelperPosition(scrollSpeed);
		}
		if (typeof onScroll == 'function') {
			onScroll(scrollTop);
		}
		this.onScrollEnd();
	};
	onScrollEnd = debounce(() => {
		let {onScrollEnd, showTodayHelper} = this.props;
		let {isScrolling} = this.state;

		if (isScrolling) this.setState({isScrolling: false});
		if (showTodayHelper) this.updateTodayHelperPosition(0);
		if (typeof onScrollEnd == 'function') onScrollEnd(this.scrollTop);
	}, 150);
	updateTodayHelperPosition = (scrollSpeed) => {
		if (!this.todayOffset) this.todayOffset = this.getDateOffset(this.today.date);
		let {scrollTop} = this;
		let {showToday} = this.state;
		let newState;

		if (scrollTop >= this.todayOffset + 480) {
			if (showToday !== 1) newState = 1;
		} else if (scrollTop <= this.todayOffset - 500) {
			if (showToday !== -1) newState = -1;
		} else if (showToday && scrollSpeed <= 1) {
			newState = false;
		}

		if (scrollTop == 0) {
			newState = false;
		}

		if (newState != null) {
			this.setState({showToday: newState});
		}
	};
	handleKeyDown = (e) => {
		let {maxDate, minDate, onKeyDown} = this.props;
		let {display, selectedDate, highlightedDate, showToday} = this.state;
		let delta = 0;

		if (typeof onKeyDown == 'function') {
			onKeyDown(e);
		}
		if ([keyCodes.left, keyCodes.up, keyCodes.right, keyCodes.down].indexOf(e.keyCode) > -1 && typeof e.preventDefault == 'function') {
			e.preventDefault();
		}

		if (!selectedDate) {
			selectedDate = moment();
		}

		if (display == 'days') {
			if (!highlightedDate) {
				highlightedDate = selectedDate.clone();
				this.setState({highlightedDate});
			}

			switch (e.keyCode) {
				case keyCodes.enter:
					this.onDaySelect(moment(highlightedDate), e);
					return;
				case keyCodes.left:
					delta = -1;
					break;
				case keyCodes.right:
					delta = +1;
					break;
				case keyCodes.down:
					delta = +7;
					break;
				case keyCodes.up:
					delta = -7;
					break;
			}

			if (delta) {
				let {rowHeight} = this.props;
				let newHighlightedDate = moment(highlightedDate).add(delta, 'days');

				// Make sure the new highlighted date isn't before min / max
				if (newHighlightedDate.isBefore(minDate)) {
					newHighlightedDate = moment(minDate);
				} else if (newHighlightedDate.isAfter(maxDate)) {
					newHighlightedDate = moment(maxDate);
				}

				// Update the highlight indicator
				this.clearHighlight();

				// Scroll the view
				if (!this.currentOffset) this.currentOffset = this.getCurrentOffset();
				let currentOffset = this.currentOffset;
				let monthOffset = this.getDateOffset(newHighlightedDate);
				let navOffset = (showToday) ? 36 : 0;

				let highlightedEl = this.highlightedEl = this.node.querySelector(`[data-date='${newHighlightedDate.format('YYYYMMDD')}']`);

				// Edge-case: if the user tries to use the keyboard when the new highlighted date isn't rendered because it's too far off-screen
				// We need to scroll to the month of the new highlighted date so it renders
				if (!highlightedEl) {
					this.scrollTo(monthOffset - navOffset);
					return;
				}

				highlightedEl.classList.add(style.day.highlighted);

				let dateOffset = highlightedEl.offsetTop - rowHeight;
				let newOffset = monthOffset + dateOffset;

				if (currentOffset !== newOffset) {
					this.currentOffset = newOffset;
					this.scrollTo(newOffset - navOffset);
				}

				// Update the reference to the currently highlighted date
				this.setState({
					highlightedDate: newHighlightedDate
				});

			}
		} else if (display == 'years') {
			switch (e.keyCode) {
				case keyCodes.enter:
				case keyCodes.escape:
					this.setDisplay('days');
					this.scrollToDate(selectedDate, -40);
					return;
				case keyCodes.down:
					delta = +1;
					break;
				case keyCodes.up:
					delta = -1;
					break;
			}

			if (delta) {
				let newSelectedDate = selectedDate.clone().add(delta, 'year');

				if (!newSelectedDate.isAfter(maxDate, 'day') && !newSelectedDate.isBefore(minDate, 'day')) {
					this.onDaySelect(newSelectedDate, e);
				}
			}
		}
	};
	clearHighlight() {
		if (this.highlightedEl) {
			this.highlightedEl.classList.remove(style.day.highlighted);
			this.highlightedEl = null;
		}
	}
	setDisplay = (display) => {
		this.setState({display});
	}
	render() {
		let {
			className,
			disabledDays,
			height,
			hideYearsOnSelect,
			keyboardSupport,
			layout,
			overscanMonthCount,
			min,
			minDate,
			max,
			maxDate,
			shouldHeaderAnimate,
			showTodayHelper,
			showHeader,
			tabIndex,
			width,
			...other
		} = this.props;
		let disabledDates = this.getDisabledDates(this.props.disabledDates);
		let locale = this.getLocale();
		let theme = this.getTheme();
		let {display, isScrolling, selectedDate, showToday} = this.state;
		let today = this.today = parseDate(moment());

		// Selected date should not be disabled
		if (selectedDate && (disabledDates && disabledDates.indexOf(selectedDate.format('YYYYMMDD')) !== -1 || disabledDays && disabledDays.indexOf(selectedDate.day()) !== -1)) {
			selectedDate = null;
		}

		return (

				{showHeader &&

				}

					<Weekdays theme={theme} />

						{showTodayHelper &&
							<Today scrollToDate={this.scrollToDate} show={showToday} today={today} theme={theme} locale={locale} />
						}
						<List
							ref="List"
							{...other}
							width={width}
							height={height}
							selectedDate={parseDate(selectedDate)}
							disabledDates={disabledDates}
							disabledDays={disabledDays}
							months={this.months}
							onDaySelect={this.onDaySelect}
							onScroll={this.onScroll}
							isScrolling={isScrolling}
							today={today}
							min={parseDate(min)}
							minDate={parseDate(minDate)}
							maxDate={parseDate(maxDate)}
							theme={theme}
							locale={locale}
							overscanMonthCount={overscanMonthCount}
						/>

					{display == 'years' &&
						<Years
							ref="years"
							width={width}
							height={height}
							onDaySelect={this.onDaySelect}
							minDate={minDate}
							maxDate={maxDate}
							selectedDate={selectedDate}
							theme={theme}
							years={range(moment(min).year(), moment(max).year() + 1)}
							setDisplay={this.setDisplay}
							scrollToDate={this.scrollToDate}
							hideYearsOnSelect={hideYearsOnSelect}
						/>
					}

		);
	}
}
<|endoftext|>