<commit_before>'use strict';

const minmax = require('../gutil').minmax;

const groups = {
  handle: 'permissions::groups',
  exec: function groups (data) {
    const db = this.execute('database::use');

    db.all('SELECT * FROM permgroups', (err, rows) => {
      if (data.done) data.done(err, rows);
    });
  },
  commands: [{
    handle: 'groups',
    exec: function groupscmd (data) {
      const done = (error, rows) => {
        if (error || !rows.length) {
          data.user.sendMessage(
            error
            ? 'A database error occurred.'
            : 'Nothing found.'
          );
          return;
        }

        rows = rows.sort((a, b) => a.name.localeCompare(b.name)).map(row => {
          return (`

              ${row.name}
              ${row.level}

          `);
        });

        while (rows.length)
          data.user.sendMessage(`

                Group Name
                Group Level

              ${rows.splice(0, 15).join('')}

          `);
      };

      this.execute('permissions::groups', { done });
    },
    info: () => `

USAGE: groups

Lists all groups, and their levels.

`
  }]
};

const groupmod = {
  handle: 'permissions::groupmod',
  exec: function groupmod (data) {
    const db = this.execute('database::use');
    const args = [data.name, data.level];

    db.run('INSERT OR REPLACE INTO permgroups values(?, ?)', args, err => {
      if (data.done) data.done(err);
    });
  },
  commands: [{
    handle: 'groupmod',
    exec: function groupmod (data) {
      if (!data.message) return;

      const parsed = this.execute('parser::htmltotext', {
        html: data.message
      });

      if (!parsed) return;

      const pieces = parsed.split(' ');
      let level = parseInt(pieces.pop(), 10);

      if (Number.isNaN(level)) {
        data.user.sendMessage('Needs level.');
        return;
      }

      const name = pieces.join(' ');

      if (!name) {
        data.user.sendMessage('Needs name.');
        return;
      }

      const conf = this.config.extensions.permissions;

      level = minmax(0, level, conf.maxlevel || 100);

      this.execute('permissions::groupmod', {
        name,
        level,
        done: error => data.user.sendMessage(
          error
          ? 'A database error occurred.'
          : `Group [ ${name} ] set. Using level [ ${level} ].`
        )
      });
    },
    info: () => `

USAGE: groupmod GROUP_NAME GROUP_LEVEL

Modifies the permissions level of GROUP_NAME
to the new value of GROUP_LEVEL.
If GROUP_NAME does not exist, it will be created.

`
  }]
};

const groupdel = {
  handle: 'permissions::groupdel',
  exec: function groupdel (data) {
    const db = this.execute('database::use');
    const args = [data.name];

    const reply = (error, message) => {
      if (data.done) data.done(error, error ? null : message);
    };

    db.run('DELETE FROM permusers WHERE permgroup = ?', args, err => {
      reply(err, `Deleted all users with group [ ${data.name} ].`);
    });

    db.run('DELETE FROM permcommands WHERE permgroup = ?', args, err => {
      reply(err, `Deleted all commands with group [ ${data.name} ].`);
    });

    db.run('DELETE FROM permgroups WHERE name = ?', args, err => {
      reply(err, `Deleted group [ ${data.name} ].`);
    });
  },
  commands: [{
    handle: 'groupdel',
    exec: function groupdelcmd (data) {
      if (!data.message) return;

      const name = this.execute('parser::htmltotext', {
        html: data.message
      });

      if (!name) return;

      const done = (err, msg) => data.user.sendMessage(err ? err.message : msg);

      this.execute('permissions::groupdel', { name, done });
    },
    info: () => `

USAGE: groupdel GROUP_NAME

Deletes all users and commands belonging to
GROUP_NAME before deleting GROUP_NAME.

`
  }]
};

module.exports = {
  handle: 'permissions',
  needs: ['database', 'parser'],
  init: stumble => {
    stumble.space.set('_STANDARD_PERMISSIONS_', true);

    const db = stumble.execute('database::use');

    db.run(`
      CREATE TABLE IF NOT EXISTS permgroups(
        name TEXT UNIQUE,
        level INTEGER
      )
    `);

    db.run(`
      CREATE TABLE IF NOT EXISTS permusers(
        hash TEXT UNIQUE,
        permgroup TEXT
      )
    `);

    db.run(`
      CREATE TABLE IF NOT EXISTS permcommands(
        name TEXT UNIQUE,
        permgroup TEXT
      )
    `);
  },
  term: stumble => stumble.space.delete('_STANDARD_PERMISSIONS_'),
  extensions: [groups, groupmod, groupdel]
};
<commit_msg>users, usermod, table tweak<commit_after>'use strict';

const minmax = require('../gutil').minmax;

const groups = {
  handle: 'permissions::groups',
  exec: function groups (data) {
    const db = this.execute('database::use');

    db.all('SELECT * FROM permgroups', (err, rows) => {
      if (data.done) data.done(err, rows);
    });
  },
  commands: [{
    handle: 'groups',
    exec: function groupscmd (data) {
      const done = (error, rows) => {
        if (error || !rows.length) {
          data.user.sendMessage(
            error
            ? 'A database error occurred.'
            : 'Nothing found.'
          );
          return;
        }

        rows = rows.sort((a, b) => a.name.localeCompare(b.name)).map(row => {
          return (`

              ${row.name}
              ${row.level}

          `);
        });

        while (rows.length)
          data.user.sendMessage(`

                Group Name
                Group Level

              ${rows.splice(0, 15).join('')}

          `);
      };

      this.execute('permissions::groups', { done });
    },
    info: () => `

USAGE: groups

Lists all groups, and their levels.

`
  }]
};

const groupmod = {
  handle: 'permissions::groupmod',
  exec: function groupmod (data) {
    const db = this.execute('database::use');
    const args = [data.name, data.level];

    db.run('INSERT OR REPLACE INTO permgroups values(?, ?)', args, err => {
      if (data.done) data.done(err);
    });
  },
  commands: [{
    handle: 'groupmod',
    exec: function groupmod (data) {
      if (!data.message) return;

      const parsed = this.execute('parser::htmltotext', {
        html: data.message
      });

      if (!parsed) return;

      const pieces = parsed.split(' ');
      let level = parseInt(pieces.pop(), 10);

      if (Number.isNaN(level)) {
        data.user.sendMessage('Needs level.');
        return;
      }

      const name = pieces.join(' ');

      if (!name) {
        data.user.sendMessage('Needs name.');
        return;
      }

      const conf = this.config.extensions.permissions;

      level = minmax(0, level, conf.maxlevel || 100);

      this.execute('permissions::groupmod', {
        name,
        level,
        done: error => data.user.sendMessage(
          error
          ? 'A database error occurred.'
          : `Group [ ${name} ] set. Using level [ ${level} ].`
        )
      });
    },
    info: () => `

USAGE: groupmod GROUP_NAME GROUP_LEVEL

Modifies the permissions level of GROUP_NAME
to the new value of GROUP_LEVEL.
If GROUP_NAME does not exist, it will be created.

`
  }]
};

const groupdel = {
  handle: 'permissions::groupdel',
  exec: function groupdel (data) {
    const db = this.execute('database::use');
    const args = [data.name];

    const reply = (error, message) => {
      if (data.done) data.done(error, error ? null : message);
    };

    db.run('DELETE FROM permusers WHERE permgroup = ?', args, err => {
      reply(err, `Deleted all users with group [ ${data.name} ].`);
    });

    db.run('DELETE FROM permcommands WHERE permgroup = ?', args, err => {
      reply(err, `Deleted all commands with group [ ${data.name} ].`);
    });

    db.run('DELETE FROM permgroups WHERE name = ?', args, err => {
      reply(err, `Deleted group [ ${data.name} ].`);
    });
  },
  commands: [{
    handle: 'groupdel',
    exec: function groupdelcmd (data) {
      if (!data.message) return;

      const name = this.execute('parser::htmltotext', {
        html: data.message
      });

      if (!name) return;

      const done = (err, msg) => data.user.sendMessage(err ? err.message : msg);

      this.execute('permissions::groupdel', { name, done });
    },
    info: () => `

USAGE: groupdel GROUP_NAME

Deletes all users and commands belonging to
GROUP_NAME before deleting GROUP_NAME.

`
  }]
};

const users = {
  handle: 'permissions::users',
  exec: function users (data) {
    const db = this.execute('database::use');

    db.all('SELECT * FROM permusers', (err, rows) => {
      if (data.done) data.done(err, rows);
    });
  },
  commands: [{
    handle: 'users',
    exec: function userscmd (data) {
      const done = (error, rows) => {
        if (error || !rows.length) {
          data.user.sendMessage(
            error
            ? 'A database error occurred.'
            : 'Nothing found.'
          );
          return;
        }

        rows = rows.sort((a, b) => a.name.localeCompare(b.name)).map(row => {
          return (`

              ${row.name}
              ${row.permgroup}

          `);
        });

        while (rows.length)
          data.user.sendMessage(`

                User Name
                Group Name

              ${rows.splice(0, 15).join('')}

          `);
      };

      this.execute('permissions::users', { done });
    },
    info: () => `

USAGE: users

Lists all users, and their group.

`
  }]
};

const usermod = {
  handle: 'permissions::usermod',
  exec: function usermod (data) {
    const db = this.execute('database::use');

    const args = [data.group];

    db.get('SELECT 1 FROM permgroups WHERE name = ?', args, (gerr, res) => {
      if (gerr || !res) {
        if (data.done) data.done(gerr || 'Group does not exist.');
        return;
      }

      args.unshift(data.name);
      args.unshift(data.hash);

      db.run('INSERT OR REPLACE INTO permusers values(?, ?, ?)', args, err => {
        if (data.done) data.done(err);
      });
    });
  },
  commands: [{
    handle: 'usermod',
    exec: function usermod (data) {
      if (!data.message) return;

      const name = this.execute('parser::htmltotext', {
        html: data.message
      });

      const user = this.client.userByName(name);

      if (!user) {
        data.user.sendMessage('User not found.');
        return;
      }

      if (!user.hash) {
        data.user.sendMessage('User not registered.');
        return;
      }

      let timer;

      const followup = (msg, usr) => {
        if (usr !== data.user) return;

        this.removeListener('message', followup);
        clearTimeout(timer);

        if (msg.startsWith(this.config.operator)) {
          usr.sendMessage('Unsafe operation sequence. Aborting [ usermod ].');
          return;
        }

        const group = this.execute('parser::htmltotext', { html: msg });

        this.execute('permissions::usermod', {
          hash: user.hash,
          name,
          group,
          done: err => data.user.sendMessage(err ||
            `User [ ${name} ] set. Using group [ ${group} ].`
          )
        });
      };

      this.on('message', followup);

      timer = setTimeout(() => this.removeListener('message', followup), 5000);

      data.user.sendMessage(`The contents of your next message
        will set the group for [ ${user.name} ]. You have five seconds.`);
    },
    info: () => `

USAGE: usermod USER_NAME

Modifies the group USER_NAME belongs to.
New value is taken from the next message.

`
  }]
};

module.exports = {
  handle: 'permissions',
  needs: ['database', 'parser'],
  init: stumble => {
    stumble.space.set('_STANDARD_PERMISSIONS_', true);

    const db = stumble.execute('database::use');

    db.run(`
      CREATE TABLE IF NOT EXISTS permgroups(
        name TEXT UNIQUE,
        level INTEGER
      )
    `);

    db.run(`
      CREATE TABLE IF NOT EXISTS permusers(
        hash TEXT UNIQUE,
        name TEXT,
        permgroup TEXT
      )
    `);

    db.run(`
      CREATE TABLE IF NOT EXISTS permcommands(
        name TEXT UNIQUE,
        permgroup TEXT
      )
    `);
  },
  term: stumble => stumble.space.delete('_STANDARD_PERMISSIONS_'),
  extensions: [
    groups, groupmod, groupdel,
    users, usermod
  ]
};
<|endoftext|>


<commit_before>const filter = require('./create-filters');

module.exports = function (queryParams) {
  const aggregationObjects = {
    filter: {
      bool: {
        should: [
          { term: {'type.base': 'agent'} },
          { term: {'type.base': 'object'} },
          { term: {'type.base': 'archive'} }
        ]
      }
    },
    aggs: {
      category: {
        filter: filter(queryParams),
        aggs: {
          category_filters: {
            terms: { field: 'categories.name' }
          }
        }
      },
      maker: {
        filter: filter(queryParams),
        aggs: {
          maker_filters: {
            terms: { field: 'lifecycle.creation.maker.summary_title' }
          }
        }
      },
      type: {
        filter: {
          bool: {
            must: filter(queryParams).bool.must.concat({ term: {'type.base': 'object'} })
          }
        },
        aggs: {
          type_filters: {
            terms: { field: 'name.value_lower' }
          }
        }
      },
      place: {
        filter: filter(queryParams),
        aggs: {
          place_filters: {
            terms: { field: 'lifecycle.creation.places.summary_title' }
          }
        }
      },
      user: {
        filter: filter(queryParams),
        aggs: {
          user_filters: {
            terms: { field: 'agents.summary_title' }
          }
        }
      },
      archive: {
        filter: filter(queryParams),
        aggs: {
          archive_filters: {
            terms: { field: 'fonds.summary_title' }
          }
        }
      },
      occupation: {
        filter: filter(queryParams),
        aggs: {
          occupation_filters: {
            terms: {field: 'occupation'}
          }
        }
      },
      place_born: {
        filter: filter(queryParams),
        aggs: {
          place_born_filters: {
            terms: {field: 'lifecycle.birth.location.name.value'}
          }
        }
      },
      organisation: {
        filter: filter(queryParams),
        aggs: {
          organisations_filters: {
            terms: {field: 'type.sub_type'}
          }
        }
      },
      count: {
        filter: filter(queryParams),
        aggs: {
          count_filters: {
            value_count: {field: 'admin.id'}
          }
        }
      },
      material: {
        filter: filter(queryParams),
        aggs: {
          material_filters: {
            terms: { field: 'materials' }
          }
        }
      },
      museum: {
        filter: filter(queryParams),
        aggs: {
          museum_filters: {
            terms: {size: 25, field: 'locations.name.value'}
          }
        }
      },
      gallery: {
        filter: filter(queryParams),
        aggs: {
          gallery_filters: {
            terms: {size: 30, field: 'locations.name.value'}
          }
        }
      }
    }
  };

  return aggregationObjects;
};
<commit_msg>suppresses unknown maker/photographer from facets #623<commit_after>const filter = require('./create-filters');

module.exports = function (queryParams) {
  const aggregationObjects = {
    filter: {
      bool: {
        should: [
          { term: {'type.base': 'agent'} },
          { term: {'type.base': 'object'} },
          { term: {'type.base': 'archive'} }
        ]
      }
    },
    aggs: {
      category: {
        filter: filter(queryParams),
        aggs: {
          category_filters: {
            terms: { field: 'categories.name' }
          }
        }
      },
      maker: {
        filter: {
          bool: {
            must: filter(queryParams).bool.must.concat({ not: { terms: {'lifecycle.creation.maker.summary_title': ['Unknown maker', 'Unknown photographer']} } })
          }
        },
        aggs: {
          maker_filters: {
            terms: { field: 'lifecycle.creation.maker.summary_title' }
          }
        }
      },
      type: {
        filter: {
          bool: {
            must: filter(queryParams).bool.must.concat({ term: {'type.base': 'object'} })
          }
        },
        aggs: {
          type_filters: {
            terms: { field: 'name.value_lower' }
          }
        }
      },
      place: {
        filter: filter(queryParams),
        aggs: {
          place_filters: {
            terms: { field: 'lifecycle.creation.places.summary_title' }
          }
        }
      },
      user: {
        filter: filter(queryParams),
        aggs: {
          user_filters: {
            terms: { field: 'agents.summary_title' }
          }
        }
      },
      archive: {
        filter: filter(queryParams),
        aggs: {
          archive_filters: {
            terms: { field: 'fonds.summary_title' }
          }
        }
      },
      occupation: {
        filter: filter(queryParams),
        aggs: {
          occupation_filters: {
            terms: {field: 'occupation'}
          }
        }
      },
      place_born: {
        filter: filter(queryParams),
        aggs: {
          place_born_filters: {
            terms: {field: 'lifecycle.birth.location.name.value'}
          }
        }
      },
      organisation: {
        filter: filter(queryParams),
        aggs: {
          organisations_filters: {
            terms: {field: 'type.sub_type'}
          }
        }
      },
      count: {
        filter: filter(queryParams),
        aggs: {
          count_filters: {
            value_count: {field: 'admin.id'}
          }
        }
      },
      material: {
        filter: filter(queryParams),
        aggs: {
          material_filters: {
            terms: { field: 'materials' }
          }
        }
      },
      museum: {
        filter: filter(queryParams),
        aggs: {
          museum_filters: {
            terms: {size: 25, field: 'locations.name.value'}
          }
        }
      },
      gallery: {
        filter: filter(queryParams),
        aggs: {
          gallery_filters: {
            terms: {size: 30, field: 'locations.name.value'}
          }
        }
      }
    }
  };

  return aggregationObjects;
};
<|endoftext|>


<commit_before>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	util = require('util'),
	super_ = require('../field');

/**
 * Name FieldType Constructor
 * @extends Field
 * @api public
 */

function name(list, path, options) {
	// TODO: implement filtering, hard-coded as disabled for now
	options.nofilter = true;
	name.super_.call(this, list, path, options);
}

/*!
 * Inherit from Field
 */

util.inherits(name, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * Adds String properties for .first and .last name, and a virtual
 * with get() and set() methods for .full
 *
 * @api public
 */

name.prototype.addToSchema = function() {

	var schema = this.list.schema;

	var paths = this.paths = {
		first: this._path.append('.first'),
		last: this._path.append('.last'),
		full: this._path.append('.full')
	};

	schema.nested[this.path] = true;
	schema.add({
		first: String,
		last: String
	}, this.path + '.');

	schema.virtual(paths.full).get(function () {
		return _.compact([this.get(paths.first), this.get(paths.last)]).join(' ');
	});

	schema.virtual(paths.full).set(function(value) {

		if (typeof value !== 'string') {
			this.set(paths.first, undefined);
			this.set(paths.last, undefined);
			return;
		}

		var split = value.split(' ');
		this.set(paths.first, split.shift());
		this.set(paths.last, split.join(' ') || undefined);

	});

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */

name.prototype.format = function(item) {
	return item.get(this.paths.full);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

name.prototype.validateInput = function(data, required, item) {

	if (!(this.path in data || this.paths.first in data || this.paths.last in data || this.paths.full in data) && item && item.get(this.paths.full)) return true;

	return (!required || (data[this.paths.full] || data[this.paths.first] || data[this.paths.last])) ? true : false;

};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

name.prototype.isModified = function(item) {
	return item.isModified(this.paths.first) ||
		item.isModified(this.paths.last);
};

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

name.prototype.updateItem = function(item, data) {

	if (!_.isObject(data)) return;

	var paths = this.paths,
		setValue;

	if (this.path in data && _.isString(data[this.path])) {

		// Allow the root path as an alias to {path}.full

		item.set(paths.full, data[this.path]);

	} else if (this.path in data && _.isObject(data[this.path])) {

		// Allow a nested object like { path: { first: 'Jed' } }

		var valueObj = data[this.path];

		setValue = function(key) {
			if (key in valueObj && valueObj[key] !== item.get(paths[key])) {
				item.set(paths[key], valueObj[key]);
			}
		};

	} else {

		// Default to flattened paths like { 'path.first': 'Jed' }

		setValue = function(key) {
			if (paths[key] in data && data[paths[key]] !== item.get(paths[key])) {
				item.set(paths[key], data[paths[key]]);
			}
		};

	}

	if (setValue) {
		_.each(['full', 'first', 'last'], setValue);
	}

};

/*!
 * Export class
 */

exports = module.exports = name;
<commit_msg>Updating name.validateInput to match new updateItem logic<commit_after>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	util = require('util'),
	super_ = require('../field');

/**
 * Name FieldType Constructor
 * @extends Field
 * @api public
 */

function name(list, path, options) {
	// TODO: implement filtering, hard-coded as disabled for now
	options.nofilter = true;
	name.super_.call(this, list, path, options);
}

/*!
 * Inherit from Field
 */

util.inherits(name, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * Adds String properties for .first and .last name, and a virtual
 * with get() and set() methods for .full
 *
 * @api public
 */

name.prototype.addToSchema = function() {

	var schema = this.list.schema;

	var paths = this.paths = {
		first: this._path.append('.first'),
		last: this._path.append('.last'),
		full: this._path.append('.full')
	};

	schema.nested[this.path] = true;
	schema.add({
		first: String,
		last: String
	}, this.path + '.');

	schema.virtual(paths.full).get(function () {
		return _.compact([this.get(paths.first), this.get(paths.last)]).join(' ');
	});

	schema.virtual(paths.full).set(function(value) {

		if (typeof value !== 'string') {
			this.set(paths.first, undefined);
			this.set(paths.last, undefined);
			return;
		}

		var split = value.split(' ');
		this.set(paths.first, split.shift());
		this.set(paths.last, split.join(' ') || undefined);

	});

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */

name.prototype.format = function(item) {
	return item.get(this.paths.full);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

name.prototype.validateInput = function(data, required, item) {

	// Input is valid if none was provided, but the item has data
	if (!(this.path in data || this.paths.first in data || this.paths.last in data || this.paths.full in data) && item && item.get(this.paths.full)) return true;

	// Input is valid if the field is not required
	if (!required) return true;

	// Otherwise check for valid strings in the provided data,
	// which may be nested or use flattened paths.

	if (_.isObject(data[this.path])) {
		return (data[this.path].full || data[this.path].first || data[this.path].last) ? true : false;
	} else {
		return (data[this.paths.full] || data[this.paths.first] || data[this.paths.last]) ? true : false;
	}

};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

name.prototype.isModified = function(item) {
	return item.isModified(this.paths.first) || item.isModified(this.paths.last);
};

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

name.prototype.updateItem = function(item, data) {

	if (!_.isObject(data)) return;

	var paths = this.paths,
		setValue;

	if (this.path in data && _.isString(data[this.path])) {

		// Allow the root path as an alias to {path}.full

		item.set(paths.full, data[this.path]);

	} else if (this.path in data && _.isObject(data[this.path])) {

		// Allow a nested object like { path: { first: 'Jed' } }

		var valueObj = data[this.path];

		setValue = function(key) {
			if (key in valueObj && valueObj[key] !== item.get(paths[key])) {
				item.set(paths[key], valueObj[key]);
			}
		};

	} else {

		// Default to flattened paths like { 'path.first': 'Jed' }

		setValue = function(key) {
			if (paths[key] in data && data[paths[key]] !== item.get(paths[key])) {
				item.set(paths[key], data[paths[key]]);
			}
		};

	}

	if (setValue) {
		_.each(['full', 'first', 'last'], setValue);
	}

};

/*!
 * Export class
 */

exports = module.exports = name;
<|endoftext|>


<commit_before>/*
 * Copyright 2009-2011 Mozilla Foundation and contributors
 * Licensed under the New BSD license. See LICENSE.txt or:
 * http://opensource.org/licenses/BSD-3-Clause
 */

define(function(require, exports, module) {

'use strict';

var Promise = require('util/promise');
var l10n = require('util/l10n');

var types = require('gcli/types');
var Type = require('gcli/types').Type;
var Status = require('gcli/types').Status;
var Conversion = require('gcli/types').Conversion;

function DateType(typeSpec) {
  // ECMA 5.1 §15.9.1.1
  // @see http://stackoverflow.com/questions/11526504/minimum-and-maximum-date
  typeSpec = typeSpec || {};

  this._step = typeSpec.step || 1;
  this._min = new Date(-8640000000000000);
  this._max = new Date(8640000000000000);

  if (typeSpec.min != null) {
    if (typeof typeSpec.min === 'string') {
      this._min = toDate(typeSpec.min);
    }
    else if (isDate(typeSpec.min) || typeof typeSpec.min === 'function') {
      this._min = typeSpec.min;
    }
    else {
      throw new Error('date min value must be a string a date or a function');
    }
  }

  if (typeSpec.max != null) {
    if (typeof typeSpec.max === 'string') {
      this._max = toDate(typeSpec.max);
    }
    else if (isDate(typeSpec.max) || typeof typeSpec.max === 'function') {
      this._max = typeSpec.max;
    }
    else {
      throw new Error('date max value must be a string a date or a function');
    }
  }
}

DateType.prototype = Object.create(Type.prototype);

DateType.prototype.stringify = function(value) {
  if (!isDate(value)) {
    return '';
  }

  var str = value.toISOString();

  var timePos = str.indexOf('23:00:00.000');
  if (timePos == -1) {
    timePos = str.indexOf('00:00:00.000');
  }

  if (timePos != -1) {
    // This looks like a plain date without a time component; remove the time
    str = str.substr(0, timePos - 1);
  }

  return str;
};

DateType.prototype.getMin = function(context) {
  if (typeof this._min === 'function') {
    return this._min(context);
  }
  if (isDate(this._min)) {
    return this._min;
  }
  return undefined;
};

DateType.prototype.getMax = function(context) {
  if (typeof this._max === 'function') {
    return this._max(context);
  }
  if (isDate(this._max)) {
    return this._max;
  }
  return undefined;
};

DateType.prototype.parse = function(arg, context) {
  var value;

  if (arg.text.replace(/\s/g, '').length === 0) {
    return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
  }

  // Lots of room for improvement here: 1h ago, in two days, etc.
  // Should "1h ago" dynamically update the step?
  if (arg.text === 'now') {
    value = new Date();
  }
  else if (arg.text === 'yesterday') {
    value = new Date().setDate(new Date().getDate() - 1);
  }
  else if (arg.text === 'tomorrow') {
    value = new Date().setDate(new Date().getDate() + 1);
  }
  else {
    var millis = Date.parse(arg.text);

    if (isNaN(millis)) {
      var msg = l10n.lookupFormat('typesDateNan', [ arg.text ]);
      return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
    }

    value = new Date(millis);
  }

  return Promise.resolve(new Conversion(value, arg));
};

DateType.prototype.decrement = function(value, context) {
  if (!isDate(value)) {
    return new Date();
  }

  var newValue = new Date(value);
  newValue.setDate(value.getDate() - this._step);

  if (newValue >= this.getMin(context)) {
    return newValue;
  }
  else {
    return this.getMin(context);
  }
};

DateType.prototype.increment = function(value, context) {
  if (!isDate(value)) {
    return new Date();
  }

  var newValue = new Date(value);
  newValue.setDate(value.getDate() + this._step);

  if (newValue <= this.getMax(context)) {
    return newValue;
  }
  else {
    return this.getMax();
  }
};

DateType.prototype.name = 'date';

/**
 * Utility to convert a string to a date, throwing if the date can't be
 * parsed rather than having an invalid date
 */
function toDate(str) {
  var millis = Date.parse(str);
  if (isNaN(millis)) {
    throw new Error(l10n.lookupFormat('typesDateNan', [ str ]));
  }
  return new Date(millis);
}

/**
 * Is |thing| a valid date?
 * @see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
 */
function isDate(thing) {
  return Object.prototype.toString.call(thing) === '[object Date]'
          && !isNaN(thing.getTime());
};

/**
 * Registration and de-registration.
 */
exports.startup = function() {
  types.addType(DateType);
};

exports.shutdown = function() {
  types.removeType(DateType);
};

});
<commit_msg>date-773271: Fix broken TZ assumption<commit_after>/*
 * Copyright 2009-2011 Mozilla Foundation and contributors
 * Licensed under the New BSD license. See LICENSE.txt or:
 * http://opensource.org/licenses/BSD-3-Clause
 */

define(function(require, exports, module) {

'use strict';

var Promise = require('util/promise');
var l10n = require('util/l10n');

var types = require('gcli/types');
var Type = require('gcli/types').Type;
var Status = require('gcli/types').Status;
var Conversion = require('gcli/types').Conversion;

function DateType(typeSpec) {
  // ECMA 5.1 §15.9.1.1
  // @see http://stackoverflow.com/questions/11526504/minimum-and-maximum-date
  typeSpec = typeSpec || {};

  this._step = typeSpec.step || 1;
  this._min = new Date(-8640000000000000);
  this._max = new Date(8640000000000000);

  if (typeSpec.min != null) {
    if (typeof typeSpec.min === 'string') {
      this._min = toDate(typeSpec.min);
    }
    else if (isDate(typeSpec.min) || typeof typeSpec.min === 'function') {
      this._min = typeSpec.min;
    }
    else {
      throw new Error('date min value must be a string a date or a function');
    }
  }

  if (typeSpec.max != null) {
    if (typeof typeSpec.max === 'string') {
      this._max = toDate(typeSpec.max);
    }
    else if (isDate(typeSpec.max) || typeof typeSpec.max === 'function') {
      this._max = typeSpec.max;
    }
    else {
      throw new Error('date max value must be a string a date or a function');
    }
  }
}

DateType.prototype = Object.create(Type.prototype);

DateType.prototype.stringify = function(value) {
  if (!isDate(value)) {
    return '';
  }

  var str = value.toISOString();

  if (value.getUTCHours() === 0 && value.getUTCMinutes() === 0 &&
      value.getUTCSeconds() === 0 && value.getUTCMilliseconds() === 0) {
    var tpos = str.indexOf('T');
    str = str.substr(0, tpos);
  }

  return str;
};

DateType.prototype.getMin = function(context) {
  if (typeof this._min === 'function') {
    return this._min(context);
  }
  if (isDate(this._min)) {
    return this._min;
  }
  return undefined;
};

DateType.prototype.getMax = function(context) {
  if (typeof this._max === 'function') {
    return this._max(context);
  }
  if (isDate(this._max)) {
    return this._max;
  }
  return undefined;
};

DateType.prototype.parse = function(arg, context) {
  var value;

  if (arg.text.replace(/\s/g, '').length === 0) {
    return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
  }

  // Lots of room for improvement here: 1h ago, in two days, etc.
  // Should "1h ago" dynamically update the step?
  if (arg.text === 'now') {
    value = new Date();
  }
  else if (arg.text === 'yesterday') {
    value = new Date().setDate(new Date().getDate() - 1);
  }
  else if (arg.text === 'tomorrow') {
    value = new Date().setDate(new Date().getDate() + 1);
  }
  else {
    var millis = Date.parse(arg.text);

    if (isNaN(millis)) {
      var msg = l10n.lookupFormat('typesDateNan', [ arg.text ]);
      return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
    }

    value = new Date(millis);
  }

  return Promise.resolve(new Conversion(value, arg));
};

DateType.prototype.decrement = function(value, context) {
  if (!isDate(value)) {
    return new Date();
  }

  var newValue = new Date(value);
  newValue.setDate(value.getDate() - this._step);

  if (newValue >= this.getMin(context)) {
    return newValue;
  }
  else {
    return this.getMin(context);
  }
};

DateType.prototype.increment = function(value, context) {
  if (!isDate(value)) {
    return new Date();
  }

  var newValue = new Date(value);
  newValue.setDate(value.getDate() + this._step);

  if (newValue <= this.getMax(context)) {
    return newValue;
  }
  else {
    return this.getMax();
  }
};

DateType.prototype.name = 'date';

/**
 * Utility to convert a string to a date, throwing if the date can't be
 * parsed rather than having an invalid date
 */
function toDate(str) {
  var millis = Date.parse(str);
  if (isNaN(millis)) {
    throw new Error(l10n.lookupFormat('typesDateNan', [ str ]));
  }
  return new Date(millis);
}

/**
 * Is |thing| a valid date?
 * @see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
 */
function isDate(thing) {
  return Object.prototype.toString.call(thing) === '[object Date]'
          && !isNaN(thing.getTime());
};

/**
 * Registration and de-registration.
 */
exports.startup = function() {
  types.addType(DateType);
};

exports.shutdown = function() {
  types.removeType(DateType);
};

});
<|endoftext|>


<commit_before>var isExpired = require("./util/isExpired");
var $error = require("./../types/error");
var onError = require("./onError");
var onValue = require("./onValue");
var onMissing = require("./onMissing");
var isMaterialized = require("./util/isMaterialzed");
var expireNode = require("./../support/expireNode");

/**
 * When we land on a valueType (or nothing) then we need to report it out to
 * the outerResults through errors, missing, or values.
 *
 * @private
 */
module.exports = function onValueType(
    model, node, path, depth, seed, outerResults, branchInfo,
    requestedPath, optimizedPath, optimizedLength, isJSONG, fromReference) {

    var currType = node && node.$type;

    // There are is nothing here, ether report value, or report the value
    // that is missing.  If there is no type then report the missing value.
    if (!node || !currType) {
        if (isMaterialized(model)) {
            onValue(model, node, seed, depth, outerResults, branchInfo,
                    requestedPath, optimizedPath, optimizedLength,
                    isJSONG);
        } else {
            onMissing(model, path, depth,
                      outerResults, requestedPath,
                      optimizedPath, optimizedLength);
        }
        return;
    }

    // If there are expired value, then report it as missing
    else if (isExpired(node)) {
        if (!node.$_invalidated) {
            expireNode(node, model._root.expired, model._root);
        }
        onMissing(model, path, depth,
                  outerResults, requestedPath,
                  optimizedPath, optimizedLength);
    }

    // If there is an error, then report it as a value if
    else if (currType === $error) {
        if (fromReference) {
            requestedPath[depth] = null;
            depth += 1;
        }
        if (isJSONG || model._treatErrorsAsValues) {
            onValue(model, node, seed, depth, outerResults, branchInfo,
                    requestedPath, optimizedPath, optimizedLength,
                    isJSONG);
        } else {
            onError(model, node, depth, requestedPath, outerResults);
        }
    }

    // Report the value
    else {
        if (fromReference) {
            requestedPath[depth] = null;
            depth += 1;
        }
        onValue(model, node, seed, depth, outerResults, branchInfo,
                requestedPath, optimizedPath, optimizedLength, isJSONG);
    }
};
<commit_msg>Emits empty atoms and nulls<commit_after>var isExpired = require("./util/isExpired");
var $error = require("./../types/error");
var onError = require("./onError");
var onValue = require("./onValue");
var onMissing = require("./onMissing");
var isMaterialized = require("./util/isMaterialzed");
var expireNode = require("./../support/expireNode");

/**
 * When we land on a valueType (or nothing) then we need to report it out to
 * the outerResults through errors, missing, or values.
 *
 * @private
 */
module.exports = function onValueType(
    model, node, path, depth, seed, outerResults, branchInfo,
    requestedPath, optimizedPath, optimizedLength, isJSONG, fromReference) {

    var currType = node && node.$type;

    // There are is nothing here, ether report value, or report the value
    // that is missing.  If there is no type then report the missing value.
    if (!node || !currType) {
        var materialized = isMaterialized(model)
        if (materialized || !isJSONG) {
            onValue(model, node, seed, depth, outerResults, branchInfo,
                    requestedPath, optimizedPath, optimizedLength,
                    isJSONG);
        }

        if (!materialized) {
            onMissing(model, path, depth,
                      outerResults, requestedPath,
                      optimizedPath, optimizedLength);
        }
        return;
    }

    // If there are expired value, then report it as missing
    else if (isExpired(node)) {
        if (!node.$_invalidated) {
            expireNode(node, model._root.expired, model._root);
        }
        onMissing(model, path, depth,
                  outerResults, requestedPath,
                  optimizedPath, optimizedLength);
    }

    // If there is an error, then report it as a value if
    else if (currType === $error) {
        if (fromReference) {
            requestedPath[depth] = null;
            depth += 1;
        }
        if (isJSONG || model._treatErrorsAsValues) {
            onValue(model, node, seed, depth, outerResults, branchInfo,
                    requestedPath, optimizedPath, optimizedLength,
                    isJSONG);
        } else {
            onError(model, node, depth, requestedPath, outerResults);
        }
    }

    // Report the value
    else {
        if (fromReference) {
            requestedPath[depth] = null;
            depth += 1;
        }
        onValue(model, node, seed, depth, outerResults, branchInfo,
                requestedPath, optimizedPath, optimizedLength, isJSONG);
    }
};
<|endoftext|>


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

//-------------------------------------------------===------------------------------------------------------
//                                                Const
//-------------------------------------------------===------------------------------------------------------

//Containers
var CONTAINER_TAG_CLASSES_MAP = {
    'container': 'container',
    'container-fluid': 'container-fluid'
};

//Clearfix
var CLEARFIX_TAG = 'clearfix',
    CLEARFIX_CLASS = 'clearfix';

//Columns
var COL_TAG = 'c',
    NUMBERED_COL_TAG_REGEXP = /^c([1-9]|1[0-2])$/,
    NUMBERED_COL_CLASS_TEMPLATE = 'col-md-%s',
    COL_SIZE_CLASS_TEMPLATE = 'col-%s-%s',
    DEFAULT_COL_SIZE_CLASS = 'col-md-1',
    IMPLIED_COL_SIZE_CLASS = 'col-md-12',
    NUMBERED_PROPERTY_CLASS_TEMPLATE = 'col-md-%s-%s',
    PROPERTY_CLASS_TEMPLATE = 'col-%s-%s-%s';

//Column attributes
var SIZE_MODIFIER_REGEXP = /^(xs|sm|md|lg)([1-9]|1[0-2])$/,
    PROPERTY_MODIFIER_REGEXP = /^(xs|sm|md|lg)([0-9]|1[0-2])$/,
    NUMBERED_PROPERTY_ATTR_REGEXP = /^(offset|push|pull)([0-9]|1[0-2])$/,
    COMPOUND_ATTR_MODIFIER_SEPARATOR = '-',
    COMPOUND_SIZE_ATTR_PREFIX_REGEXP = /^(size):(\S+)$/,
    COMPOUND_PROPERTY_ATTR_PREFIX_REGEXP = /^(offset|push|pull):(\S+)$/;

//Row
var ROW_TAG = 'r',
    ROW_CLASS = 'row',
    IMPLIED_COL_START_TAG_TOKEN = {
        tagName: 'div',
        selfClosing: false,
        attrs: [
            {
                name: 'class',
                value: IMPLIED_COL_SIZE_CLASS
            }
        ]
    };

//-------------------------------------------------===------------------------------------------------------
//                                        Tag conversion utils
//-------------------------------------------------===------------------------------------------------------

function getClassAttr(startTag) {
    var attrs = startTag.attrs;

    for (var i = 0; i < attrs.length; i++) {
        if (attrs[i].name === 'class')
            return attrs[i];
    }

    return null;
}

function convertToDiv(startTag, classNamesToAdd) {
    var classAttr = getClassAttr(startTag);

    if (typeof classNamesToAdd === 'string')
        classNamesToAdd = [classNamesToAdd];

    if (!classAttr) {
        classAttr = {name: 'class', value: ''};
        startTag.attrs.push(classAttr);
    }

    var classNames = classAttr.value.length ? classAttr.value.trim().split(/s+/) : [];

    classAttr.value = classNamesToAdd.concat(classNames).join(' ');
    startTag.tagName = 'div';

    return startTag;
}

//-------------------------------------------------===------------------------------------------------------
//                                     Column attributes conversion
//-------------------------------------------------===------------------------------------------------------

//Method builders
function buildCompoundAttrConverterMethods(converter) {
    converter.condition = function (attrName) {
        return converter.prefixRegExp.test(attrName);
    };

    converter.toClassNames = function (attrName) {
        var prefixMatch = attrName.match(converter.prefixRegExp),
            propertyName = prefixMatch[1];

        return prefixMatch[2]
            .trim()
            .split(COMPOUND_ATTR_MODIFIER_SEPARATOR)
            .filter(function (item) {
                return converter.modifierRegExp.test(item);
            })
            .map(function (modifier) {
                var match = modifier.match(converter.modifierRegExp),
                    modifier = match[1],
                    size = match[2],
                    className = util.format(converter.classNameTemplate, modifier);

                if (!converter.isSizeAttr)
                    className = util.format(className, propertyName);

                return util.format(className, size);
            });
    };
}

function buildAttrConverterMethods(converter) {
    converter.condition = function (attrName) {
        return converter.attrRegExp.test(attrName);
    };

    converter.toClassNames = function (attrName) {
        var match = attrName.match(converter.attrRegExp),
            className = converter.classNameTemplate;

        for (var i = 1; i < match.length; i++)
            className = util.format(className, match[i]);

        return [className];
    };
}

//Converters
var ColumnAttrConverters = [
    {
        name: 'Size attribute',
        isSizeAttr: true,
        isCompound: false,
        attrRegExp: SIZE_MODIFIER_REGEXP,
        classNameTemplate: COL_SIZE_CLASS_TEMPLATE
    },

    {
        name: 'Compound size attribute',
        isSizeAttr: true,
        isCompound: true,
        prefixRegExp: COMPOUND_SIZE_ATTR_PREFIX_REGEXP,
        modifierRegExp: SIZE_MODIFIER_REGEXP,
        classNameTemplate: COL_SIZE_CLASS_TEMPLATE
    },

    {
        name: 'Numbered property attribute (offsetN, pullN, pushN)',
        isSizeAttr: false,
        isCompound: false,
        attrRegExp: NUMBERED_PROPERTY_ATTR_REGEXP,
        classNameTemplate: NUMBERED_PROPERTY_CLASS_TEMPLATE
    },

    {
        name: 'Compound property attribute (offset:, pull:, push:)',
        isSizeAttr: false,
        isCompound: true,
        prefixRegExp: COMPOUND_PROPERTY_ATTR_PREFIX_REGEXP,
        modifierRegExp: PROPERTY_MODIFIER_REGEXP,
        classNameTemplate: PROPERTY_CLASS_TEMPLATE
    }
];

//Build converter methods
ColumnAttrConverters = ColumnAttrConverters.map(function (converter) {
    var methodBuilder = converter.isCompound ?
                        buildCompoundAttrConverterMethods :
                        buildAttrConverterMethods;

    methodBuilder(converter);
    return converter;
});

//-------------------------------------------------===------------------------------------------------------
//                                              Transpiler
//-------------------------------------------------===------------------------------------------------------

var GridTranspiler = function (ctx) {
    this.ctx = ctx;
    this.pendingRowTokens = [];
    this.expectingColStartTag = false;
    this.rowImpliedColFlagsStack = [];
};

//Clearfix processing
GridTranspiler.prototype._processClearfix = function (startTag) {
    //NOTE: if <clearfix> is self-closing then emit implied end tag
    if (startTag.selfClosing) {
        startTag.selfClosing = false;
        this.ctx.emit.endTag('div');
    }

    return convertToDiv(startTag, CLEARFIX_CLASS)
};

//Row processing
GridTranspiler.prototype._processRowStartTag = function (startTag) {
    this.expectingColStartTag = true;
    this.rowImpliedColFlagsStack.push(false);

    return convertToDiv(startTag, ROW_CLASS);
};

GridTranspiler.prototype._processRowEndTag = function () {
    var impliedColFlag = this.rowImpliedColFlagsStack.pop();

    if (impliedColFlag) {
        //NOTE: both column and row end tags transpiles to

        //so we can just emit additional
 here.
        this.ctx.emit.endTag('div');
    }

    return 'div';
};

GridTranspiler.prototype._ensureImpliedColStartTag = function () {
    if (this.expectingColStartTag) {
        //NOTE: mark topmost <r> on the stack with implied column flag
        var stackTop = this.rowImpliedColFlagsStack.length - 1;
        this.rowImpliedColFlagsStack[stackTop] = true;
        this.expectingColStartTag = false;

        //NOTE: emit implied column and insert pending row tokens after it
        this.ctx.emit.startTag(IMPLIED_COL_START_TAG_TOKEN);
        this._flushPendingRowTokens();

        return true;
    }

    return false;
};

GridTranspiler.prototype._flushPendingRowTokens = function () {
    if (this.pendingRowTokens.length) {
        for (var i = 0; i < this.pendingRowTokens.length; i++) {
            var tokenEntry = this.pendingRowTokens[i];
            this.ctx.emit[tokenEntry.type](tokenEntry.token);
        }

        this.pendingRowTokens = [];
        return true;
    }

    return false;
};

//Columns
GridTranspiler.prototype._isColTagName = function (tn) {
    return tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn);
};

GridTranspiler.prototype._convertColTagToSizeClassName = function (tn) {
    if (tn === COL_TAG)
        return null;

    var size = tn.match(NUMBERED_COL_TAG_REGEXP)[1];
    return util.format(NUMBERED_COL_CLASS_TEMPLATE, size);
};

GridTranspiler.prototype._ensureColHasSize = function (hasSizeAttrs, sizeClassName, attrClassNames) {
    //NOTE: if it's not a numbered column and we don't have size class name
    //then fallback to the default column size
    if (!sizeClassName && !hasSizeAttrs)
        sizeClassName = DEFAULT_COL_SIZE_CLASS;

    if (sizeClassName)
        attrClassNames.splice(0, 0, sizeClassName);

    return attrClassNames;
};

GridTranspiler.prototype._convertColAttrsToClassNames = function (attrs, sizeClassName) {
    var attrClassNames = [],
        hasSizeAttrs = false;

    for (var i = attrs.length - 1; i >= 0; i--) {
        for (var j = 0; j < ColumnAttrConverters.length; j++) {
            var attrName = attrs[i].name;

            if (ColumnAttrConverters[j].condition(attrName)) {
                var converted = ColumnAttrConverters[j].toClassNames(attrName);

                hasSizeAttrs |= ColumnAttrConverters[j].isSizeAttr;

                //NOTE: since we traverse in reverse order we concat
                //existing class names to newly converted
                attrClassNames = converted.concat(attrClassNames);
                attrs.splice(i, 1);

                break;
            }
        }
    }

    return this._ensureColHasSize(hasSizeAttrs, sizeClassName, attrClassNames);
};

GridTranspiler.prototype._processColStartTag = function (startTag) {
    this.expectingColStartTag = false;

    //NOTE: if we have pending row tokens, insert them before column
    if (this._flushPendingRowTokens()) {
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var sizeClassName = this._convertColTagToSizeClassName(startTag.tagName),
        classNames = this._convertColAttrsToClassNames(startTag.attrs, sizeClassName);

    return convertToDiv(startTag, classNames);
};

//Token handlers
GridTranspiler.prototype.onStartTag = function (startTag) {
    var tn = startTag.tagName,
        isCol = this._isColTagName(tn);

    if (isCol)
        return this._processColStartTag(startTag);

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var containerClass = CONTAINER_TAG_CLASSES_MAP[tn];

    if (containerClass)
        return convertToDiv(startTag, containerClass);

    if (tn === CLEARFIX_TAG)
        return this._processClearfix(startTag);

    if (tn === ROW_TAG)
        return this._processRowStartTag(startTag);
};

GridTranspiler.prototype.onEndTag = function (tn) {
    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.endTag(tn);
        return null;
    }

    if (tn === ROW_TAG)
        return this._processRowEndTag();

    if (CONTAINER_TAG_CLASSES_MAP[tn] || tn === CLEARFIX_TAG ||
        tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn)) {
        //NOTE: end tag is always transpiled to

        return 'div';
    }
};

GridTranspiler.prototype.onText = function (text) {
    //NOTE: new lines and spaces do not trigger implied column generation.
    //Therefore we don't know how it will be adopted, let's store it for now.
    if (this.expectingColStartTag && !text.trim()) {
        this.pendingRowTokens.push({type: 'text', token: text});
        return null;
    }

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.text(text);
        return null;
    }
};

GridTranspiler.prototype.onComment = function (comment) {
    //NOTE: like with whitespace text in row we need to decide
    //later who will adopt comment. So we just store it for now.
    if (this.expectingColStartTag) {
        this.pendingRowTokens.push({type: 'comment', token: comment});
        return null;
    }
};

//-------------------------------------------------===------------------------------------------------------
//                                             ineed plugin
//-------------------------------------------------===------------------------------------------------------

module.exports = {
    name: 'grid',
    extends: 'reprocess',

    init: function (ctx) {
        var plugin = this,
            transpiler = new GridTranspiler(ctx);

        ['onStartTag', 'onEndTag', 'onText', 'onComment'].forEach(function (handler) {
            plugin[handler] = function (token) {
                return transpiler[handler](token);
            };
        });
    }
};
<commit_msg>Grid: Refactor a little bit<commit_after>var util = require('util');

//-------------------------------------------------===------------------------------------------------------
//                                                Const
//-------------------------------------------------===------------------------------------------------------

//Containers
var CONTAINER_TAG_CLASSES_MAP = {
    'container': 'container',
    'container-fluid': 'container-fluid'
};

//Clearfix
var CLEARFIX_TAG =
    CLEARFIX_CLASS = 'clearfix';

//Columns
var COL_TAG = 'c',
    SIZE_CLASS_REGEXP = /^col-(xs|sm|md|lg)-([1-9]|1[0-2])$/,
    NUMBERED_COL_TAG_REGEXP = /^c([1-9]|1[0-2])$/,
    NUMBERED_COL_CLASS_TEMPLATE = 'col-md-%s',
    COL_SIZE_CLASS_TEMPLATE = 'col-%s-%s',
    DEFAULT_COL_SIZE_CLASS = 'col-md-1',
    IMPLIED_COL_SIZE_CLASS = 'col-md-12',
    NUMBERED_PROPERTY_CLASS_TEMPLATE = 'col-md-%s-%s',
    PROPERTY_CLASS_TEMPLATE = 'col-%s-%s-%s';

//Column attributes
var SIZE_MODIFIER_REGEXP = /^(xs|sm|md|lg)([1-9]|1[0-2])$/,
    PROPERTY_MODIFIER_REGEXP = /^(xs|sm|md|lg)([0-9]|1[0-2])$/,
    NUMBERED_PROPERTY_ATTR_REGEXP = /^(offset|push|pull)([0-9]|1[0-2])$/,
    COMPOUND_ATTR_MODIFIER_SEPARATOR = '-',
    COMPOUND_SIZE_ATTR_PREFIX_REGEXP = /^(size):(\S+)$/,
    COMPOUND_PROPERTY_ATTR_PREFIX_REGEXP = /^(offset|push|pull):(\S+)$/;

//Row
var ROW_TAG = 'r',
    ROW_CLASS = 'row',
    IMPLIED_COL_START_TAG_TOKEN = {
        tagName: 'div',
        selfClosing: false,
        attrs: [
            {
                name: 'class',
                value: IMPLIED_COL_SIZE_CLASS
            }
        ]
    };

//-------------------------------------------------===------------------------------------------------------
//                                        Tag conversion utils
//-------------------------------------------------===------------------------------------------------------

function getClassAttr(startTag) {
    var attrs = startTag.attrs;

    for (var i = 0; i < attrs.length; i++) {
        if (attrs[i].name === 'class')
            return attrs[i];
    }

    return null;
}

function convertToDiv(startTag, classNamesToAdd) {
    var classAttr = getClassAttr(startTag);

    if (typeof classNamesToAdd === 'string')
        classNamesToAdd = [classNamesToAdd];

    if (!classAttr) {
        classAttr = {name: 'class', value: ''};
        startTag.attrs.push(classAttr);
    }

    var classNames = classAttr.value.length ? classAttr.value.trim().split(/s+/) : [];

    classAttr.value = classNamesToAdd.concat(classNames).join(' ');
    startTag.tagName = 'div';

    return startTag;
}

//-------------------------------------------------===------------------------------------------------------
//                                     Column attributes conversion
//-------------------------------------------------===------------------------------------------------------

//Method builders
function buildCompoundAttrConverterMethods(converter) {
    converter.condition = function (attrName) {
        return converter.prefixRegExp.test(attrName);
    };

    converter.toClassNames = function (attrName) {
        var prefixMatch = attrName.match(converter.prefixRegExp),
            propertyName = prefixMatch[1],
            modifiers = prefixMatch[2];

        return modifiers
            .trim()
            .split(COMPOUND_ATTR_MODIFIER_SEPARATOR)
            .filter(function (item) {
                return converter.modifierRegExp.test(item);
            })
            .map(function (modifier) {
                var match = modifier.match(converter.modifierRegExp),
                    screenSize = match[1],
                    units = match[2],
                    className = util.format(converter.classNameTemplate, screenSize);

                if (converter.renderPropertyName)
                    className = util.format(className, propertyName);

                return util.format(className, units);
            });
    };
}

function buildAttrConverterMethods(converter) {
    converter.condition = function (attrName) {
        return converter.attrRegExp.test(attrName);
    };

    converter.toClassNames = function (attrName) {
        var match = attrName.match(converter.attrRegExp),
            className = converter.classNameTemplate;

        for (var i = 1; i < match.length; i++)
            className = util.format(className, match[i]);

        return [className];
    };
}

//Convert attributes to CSS class names
function convertAttrsToClassNames(attrs, converters) {
    var attrClassNames = [];

    for (var i = attrs.length - 1; i >= 0; i--) {
        for (var j = 0; j < converters.length; j++) {
            var attrName = attrs[i].name;

            if (converters[j].condition(attrName)) {
                var converted = converters[j].toClassNames(attrName);

                //NOTE: since we traverse in reverse order we concat
                //existing class names to newly converted
                attrClassNames = converted.concat(attrClassNames);
                attrs.splice(i, 1);

                break;
            }
        }
    }

    return attrClassNames;
}

//Converters
var ColumnAttrConverters = [
    {
        name: 'Size attribute',
        isCompound: false,
        attrRegExp: SIZE_MODIFIER_REGEXP,
        classNameTemplate: COL_SIZE_CLASS_TEMPLATE
    },

    {
        name: 'Compound size attribute',
        isCompound: true,
        renderPropertyName: false,
        prefixRegExp: COMPOUND_SIZE_ATTR_PREFIX_REGEXP,
        modifierRegExp: SIZE_MODIFIER_REGEXP,
        classNameTemplate: COL_SIZE_CLASS_TEMPLATE
    },

    {
        name: 'Numbered property attribute (offsetN, pullN, pushN)',
        isCompound: false,
        attrRegExp: NUMBERED_PROPERTY_ATTR_REGEXP,
        classNameTemplate: NUMBERED_PROPERTY_CLASS_TEMPLATE
    },

    {
        name: 'Compound property attribute (offset:, pull:, push:)',
        isCompound: true,
        renderPropertyName: true,
        prefixRegExp: COMPOUND_PROPERTY_ATTR_PREFIX_REGEXP,
        modifierRegExp: PROPERTY_MODIFIER_REGEXP,
        classNameTemplate: PROPERTY_CLASS_TEMPLATE
    }
];

//Build converter methods
ColumnAttrConverters = ColumnAttrConverters.map(function (converter) {
    var methodBuilder = converter.isCompound ?
                        buildCompoundAttrConverterMethods :
                        buildAttrConverterMethods;

    methodBuilder(converter);
    return converter;
});

//-------------------------------------------------===------------------------------------------------------
//                                              Transpiler
//-------------------------------------------------===------------------------------------------------------

var GridTranspiler = function (ctx) {
    this.ctx = ctx;
    this.pendingRowTokens = [];
    this.expectingColStartTag = false;
    this.rowImpliedColFlagsStack = [];
};

//Clearfix processing
GridTranspiler.prototype._processClearfix = function (startTag) {
    //NOTE: if <clearfix> is self-closing then emit implied end tag
    if (startTag.selfClosing) {
        startTag.selfClosing = false;
        this.ctx.emit.endTag('div');
    }

    return convertToDiv(startTag, CLEARFIX_CLASS)
};

//Row processing
GridTranspiler.prototype._processRowStartTag = function (startTag) {
    this.expectingColStartTag = true;
    this.rowImpliedColFlagsStack.push(false);

    return convertToDiv(startTag, ROW_CLASS);
};

GridTranspiler.prototype._processRowEndTag = function () {
    var impliedColFlag = this.rowImpliedColFlagsStack.pop();

    if (impliedColFlag) {
        //NOTE: both column and row end tags transpiles to

        //so we can just emit additional
 here.
        this.ctx.emit.endTag('div');
    }

    return 'div';
};

GridTranspiler.prototype._ensureImpliedColStartTag = function () {
    if (this.expectingColStartTag) {
        //NOTE: mark topmost <r> on the stack with implied column flag
        var stackTop = this.rowImpliedColFlagsStack.length - 1;
        this.rowImpliedColFlagsStack[stackTop] = true;
        this.expectingColStartTag = false;

        //NOTE: emit implied column and insert pending row tokens after it
        this.ctx.emit.startTag(IMPLIED_COL_START_TAG_TOKEN);
        this._flushPendingRowTokens();

        return true;
    }

    return false;
};

GridTranspiler.prototype._flushPendingRowTokens = function () {
    if (this.pendingRowTokens.length) {
        for (var i = 0; i < this.pendingRowTokens.length; i++) {
            var tokenEntry = this.pendingRowTokens[i];
            this.ctx.emit[tokenEntry.type](tokenEntry.token);
        }

        this.pendingRowTokens = [];
        return true;
    }

    return false;
};

//Columns
GridTranspiler.prototype._isColTagName = function (tn) {
    return tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn);
};

GridTranspiler.prototype._convertColTagToSizeClassName = function (tn) {
    if (tn === COL_TAG)
        return null;

    var size = tn.match(NUMBERED_COL_TAG_REGEXP)[1];
    return util.format(NUMBERED_COL_CLASS_TEMPLATE, size);
};

GridTranspiler.prototype._convertColAttrsToClassNames = function (attrs, sizeClassName) {
    var attrClassNames = convertAttrsToClassNames(attrs, ColumnAttrConverters);

    if (!sizeClassName) {
        var hasSizeAttrs = false;

        for (var i = 0; i < attrClassNames.length; i++) {
            if (SIZE_CLASS_REGEXP.test(attrClassNames[i])) {
                hasSizeAttrs = true;
                break;
            }
        }

        //NOTE: if it's not a numbered column and we don't have size class name
        //then fallback to the default column size
        if (!hasSizeAttrs)
            sizeClassName = DEFAULT_COL_SIZE_CLASS;
    }

    if (sizeClassName)
        attrClassNames.splice(0, 0, sizeClassName);

    return attrClassNames;
};

GridTranspiler.prototype._processColStartTag = function (startTag) {
    this.expectingColStartTag = false;

    //NOTE: if we have pending row tokens, insert them before column
    if (this._flushPendingRowTokens()) {
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var sizeClassName = this._convertColTagToSizeClassName(startTag.tagName),
        classNames = this._convertColAttrsToClassNames(startTag.attrs, sizeClassName);

    return convertToDiv(startTag, classNames);
};

//Token handlers
GridTranspiler.prototype.onStartTag = function (startTag) {
    var tn = startTag.tagName,
        isCol = this._isColTagName(tn);

    if (isCol)
        return this._processColStartTag(startTag);

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.startTag(startTag);
        return null;
    }

    var containerClass = CONTAINER_TAG_CLASSES_MAP[tn];

    if (containerClass)
        return convertToDiv(startTag, containerClass);

    if (tn === CLEARFIX_TAG)
        return this._processClearfix(startTag);

    if (tn === ROW_TAG)
        return this._processRowStartTag(startTag);
};

GridTranspiler.prototype.onEndTag = function (tn) {
    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.endTag(tn);
        return null;
    }

    if (tn === ROW_TAG)
        return this._processRowEndTag();

    if (CONTAINER_TAG_CLASSES_MAP[tn] || tn === CLEARFIX_TAG ||
        tn === COL_TAG || NUMBERED_COL_TAG_REGEXP.test(tn)) {
        //NOTE: end tag is always transpiled to

        return 'div';
    }
};

GridTranspiler.prototype.onText = function (text) {
    //NOTE: new lines and spaces do not trigger implied column generation.
    //Therefore we don't know how it will be adopted, let's store it for now.
    if (this.expectingColStartTag && !text.trim()) {
        this.pendingRowTokens.push({type: 'text', token: text});
        return null;
    }

    if (this._ensureImpliedColStartTag()) {
        //NOTE: if we have additional tokens emitted we need to move current token after them
        this.ctx.emit.text(text);
        return null;
    }
};

GridTranspiler.prototype.onComment = function (comment) {
    //NOTE: like with whitespace text in row we need to decide
    //later who will adopt comment. So we just store it for now.
    if (this.expectingColStartTag) {
        this.pendingRowTokens.push({type: 'comment', token: comment});
        return null;
    }
};

//-------------------------------------------------===------------------------------------------------------
//                                             ineed plugin
//-------------------------------------------------===------------------------------------------------------

module.exports = {
    name: 'grid',
    extends: 'reprocess',

    init: function (ctx) {
        var plugin = this,
            transpiler = new GridTranspiler(ctx);

        ['onStartTag', 'onEndTag', 'onText', 'onComment'].forEach(function (handler) {
            plugin[handler] = function (token) {
                return transpiler[handler](token);
            };
        });
    }
};
<|endoftext|>


<commit_before>'use strict';
/**
 * @file Update an account from specified access_token
 *
 */

var async = require('async');
var restify = require('restify');
var rarity = require('rarity');

var Token = require('../models/token.js');

/**
 * This handler downloads data for the specified access_token and starts uploading them onto AnyFetch.
 *
 */
module.exports = function(updateAccount, queue, workers) {
  return function(req, res, next) {
    if(!req.params.access_token) {
      console.warn("ERR: Missing access_token parameter.");
      return next(new restify.MissingParameterError("Missing access_token parameter."));
    }
    if(!req.params.api_url) {
      console.warn("ERR: Missing api_url parameter.");
      return next(new restify.MissingParameterError("Missing api_url parameter."));
    }
    if(!req.params.documents_per_update && !req.params.identifier) {
      console.warn("ERR: Missing documents_per_update parameter.");
      return next(new restify.MissingParameterError("Missing documents_per_update parameter."));
    }

    Token.findOne({anyfetchToken: req.params.access_token}, function(err, token) {
      if(err) {
        console.warn("ERR:", err);
        return next(err);
      }
      if(!token) {
        console.warn("ERR: Unknown token.");
        return next(new restify.InvalidArgumentError("Unknown token."));
      }
      if(token.isUpdating) {
        console.warn("ERR: Already processing");
        return next(new restify.TooManyRequestsError('Already processing'));
      }
      else {
        res.send(202);
      }
      next();

      async.waterfall([
        function updateToken(cb) {
          if (req.params.identifier) {
            return cb(null);
          }

          // Update token
          token.lastUpdate = new Date();
          token.isUpdating = true;

          token.save(rarity.slice(1, cb));
        },
        function callUpdateAccount(cb) {
          var queues = {};

          Object.keys(workers).forEach(function(name) {
            queues[name] = [];
          });

          updateAccount(token.data || {}, (req.params.ideatifier) ? null : token.cursor, queues, function(err, newCursor, providerData) {
            cb(err, queues, newCursor, providerData);
          });
        },
        function updateToken(queues, newCursor, providerData, cb) {
          if (req.params.identifier) {
            return cb(null, queues);
          }

          if(providerData) {
            token.data = providerData;
            token.markModified('data');
          }
          token.cursor = newCursor;
          token.isUpdating = false;

          token.save(rarity.carryAndSlice([queues], 2, cb));
        },
        function executeQueue(queues, cb) {
          Object.keys(workers).forEach(function(name) {
            // The limit is applied for each queue. (For Anyfetch's providers, queues are addition and deletion)
            var documentsCount = 0;
            console.log("User `" + token.accountName + "` pushed " + queues[name].length + " tasks onto queue `" + name + "`");
            queues[name].every(function(task) {
              if((documentsCount < req.params.documents_per_update && !req.params.identifier) || (task.identifier === req.params.identifier)) {
                if (req.params.identifier) {
                  console.log("Reproviding", task.title, "(", task.identifier, ")");
                }

                task._anyfetchToken = token.anyfetchToken;
                task._anyfetchApiUrl = req.params.api_url;
                queue.create(name, task).removeOnComplete(true).save();
                documentsCount += 1;
                return (req.params.identifier) ? false : true;
              }
              return (req.params.identifier) ? true : false;
            });
          });
          cb(null);
        }
      ], function(err) {
        if(err) {
          console.warn('ERR:', err);
        }
      });
    });
  };
};
<commit_msg>Don't send 'Already processing' if the last update is 5 hours before<commit_after>'use strict';
/**
 * @file Update an account from specified access_token
 *
 */

var async = require('async');
var restify = require('restify');
var rarity = require('rarity');

var Token = require('../models/token.js');

/**
 * This handler downloads data for the specified access_token and starts uploading them onto AnyFetch.
 *
 */
module.exports = function(updateAccount, queue, workers) {
  return function(req, res, next) {
    if(!req.params.access_token) {
      console.warn("ERR: Missing access_token parameter.");
      return next(new restify.MissingParameterError("Missing access_token parameter."));
    }
    if(!req.params.api_url) {
      console.warn("ERR: Missing api_url parameter.");
      return next(new restify.MissingParameterError("Missing api_url parameter."));
    }
    if(!req.params.documents_per_update && !req.params.identifier) {
      console.warn("ERR: Missing documents_per_update parameter.");
      return next(new restify.MissingParameterError("Missing documents_per_update parameter."));
    }

    Token.findOne({anyfetchToken: req.params.access_token}, function(err, token) {
      if(err) {
        console.warn("ERR:", err);
        return next(err);
      }
      if(!token) {
        console.warn("ERR: Unknown token.");
        return next(new restify.InvalidArgumentError("Unknown token."));
      }
      if(token.isUpdating && Date.now() - token.lastUpdate.getTime() > 5 * 3600 * 1000) {
        console.warn("ERR: Already processing");
        return next(new restify.TooManyRequestsError('Already processing'));
      }
      else {
        res.send(202);
      }
      next();

      async.waterfall([
        function updateToken(cb) {
          if (req.params.identifier) {
            return cb(null);
          }

          // Update token
          token.lastUpdate = new Date();
          token.isUpdating = true;

          token.save(rarity.slice(1, cb));
        },
        function callUpdateAccount(cb) {
          var queues = {};

          Object.keys(workers).forEach(function(name) {
            queues[name] = [];
          });

          updateAccount(token.data || {}, (req.params.ideatifier) ? null : token.cursor, queues, function(err, newCursor, providerData) {
            cb(err, queues, newCursor, providerData);
          });
        },
        function updateToken(queues, newCursor, providerData, cb) {
          if (req.params.identifier) {
            return cb(null, queues);
          }

          if(providerData) {
            token.data = providerData;
            token.markModified('data');
          }
          token.cursor = newCursor;
          token.isUpdating = false;

          token.save(rarity.carryAndSlice([queues], 2, cb));
        },
        function executeQueue(queues, cb) {
          Object.keys(workers).forEach(function(name) {
            // The limit is applied for each queue. (For Anyfetch's providers, queues are addition and deletion)
            var documentsCount = 0;
            console.log("User `" + token.accountName + "` pushed " + queues[name].length + " tasks onto queue `" + name + "`");
            queues[name].every(function(task) {
              if((documentsCount < req.params.documents_per_update && !req.params.identifier) || (task.identifier === req.params.identifier)) {
                if (req.params.identifier) {
                  console.log("Reproviding", task.title, "(", task.identifier, ")");
                }

                task._anyfetchToken = token.anyfetchToken;
                task._anyfetchApiUrl = req.params.api_url;
                queue.create(name, task).removeOnComplete(true).save();
                documentsCount += 1;
                return (req.params.identifier) ? false : true;
              }
              return (req.params.identifier) ? true : false;
            });
          });
          cb(null);
        }
      ], function(err) {
        if(err) {
          console.warn('ERR:', err);
        }
      });
    });
  };
};
<|endoftext|>


<commit_before>// Copyright (c) 2012, Joyent, Inc. All rights reserved.
var assert = require('assert-plus');
var EventEmitter = require('events').EventEmitter;
var restify = require('restify');
var shelljs = require('shelljs');
var spawn = require('child_process').spawn;
var util = require('util');

function HeartbeatClient(options) {
        assert.object(options, 'options');
        assert.object(options.log, 'options.log');
        assert.string(options.url, 'options.url');
        assert.string(options.primaryUrl, 'options.primaryUrl');
        assert.number(options.heartbeatInterval, 'options.heartbeatInterval');

        EventEmitter.call(this);

        var self = this;

        this.log = options.log;

        /**
        * The primary server's url. http://10.0.0.0:1234
        */
        this.primaryUrl = options.primaryUrl;

        this.url = options.url;

        this.client = restify.createJsonClient({
                url: self.primaryUrl,
                version: '*',
                log: options.log
        });

        this.heartbeatInterval = options.heartbeatInterval;
        this.intervalId = null;

        this.log.trace('initializing HeartbeatClient with options', options);
}

module.exports = HeartbeatClient;
util.inherits(HeartbeatClient, EventEmitter);

/**
* Posts a heartbeat request to the primary peer in the shard
*/
function _postHeartbeat(self, callback) {
        var log = self.log;
        var request = {
                host: self.url
        };

        self.log.trace({
                request: request
        }, 'sending heartbeat request');

        self.client.post('/heartbeat', request, function(err, req, res, obj) {
                if (err) {
                        log.trace({err: err},
                               'error posting heartbeat request');
                        return callback(err);
                }

                self.log.trace({
                        response: obj
                }, 'successfully posted heartbeat request');

                return callback(null);
        });
        return true;
}

HeartbeatClient.prototype.start = function start(callback) {
        var self = this;
        var log = self.log;
        log.trace({
                url: self.primaryUrl,
                intervalId: self.intervalId
        }, 'starting heartbeat');
        if (self.intervalId !== null) {
                log.warn({
                        url: self.primaryUrl,
                        intervalId: self.intervalId
                }, 'heartbeat is already running');
        } else {
                self.intervalId = setInterval(function() {
                        _postHeartbeat(self, function() {});
                }, self.heartbeatInterval);
        }

        return callback();
};

HeartbeatClient.prototype.stop = function stop(callback) {
        var self = this;
        var log = self.log;

        log.trace({
                url: self.primaryUrl,
                intervalId: self.intervalId
        }, 'stopping heartbeat');

        if (self.intervalId) {
                clearInterval(self.intervalId);
                self.intervalId = null;
        } else {
                log.warn({
                        url: self.primaryUrl
                }, 'heartbeat is already stopped');
        }

        if (typeof (callback) === 'function') {
                return callback();
        }

        return true;
};
<commit_msg>MANTA-708 check for host and port when instantiating heartbeatclient<commit_after>// Copyright (c) 2012, Joyent, Inc. All rights reserved.
var assert = require('assert-plus');
var EventEmitter = require('events').EventEmitter;
var restify = require('restify');
var shelljs = require('shelljs');
var spawn = require('child_process').spawn;
var util = require('util');
var url = require('url');

function HeartbeatClient(options) {
        assert.object(options, 'options');
        assert.object(options.log, 'options.log');
        assert.string(options.url, 'options.url');
        assert.string(options.primaryUrl, 'options.primaryUrl');
        assert.number(options.heartbeatInterval, 'options.heartbeatInterval');

        EventEmitter.call(this);

        var self = this;

        this.log = options.log;

        /**
        * The primary server's url. http://10.0.0.0:1234
        */
        this.primaryUrl = options.primaryUrl;

        this.url = options.url;
        var parsedUrl = url.parse(self.url);
        if (!parsedUrl.host || !parsedUrl.port) {
                throw new Error('url %s does not contain host or port',
                                self.url);
        }

        this.client = restify.createJsonClient({
                url: self.primaryUrl,
                version: '*',
                log: options.log
        });

        this.heartbeatInterval = options.heartbeatInterval;
        this.intervalId = null;

        this.log.trace('initializing HeartbeatClient with options', options);
}

module.exports = HeartbeatClient;
util.inherits(HeartbeatClient, EventEmitter);

/**
* Posts a heartbeat request to the primary peer in the shard
*/
function _postHeartbeat(self, callback) {
        var log = self.log;
        var request = {
                host: self.url
        };

        self.log.trace({
                request: request
        }, 'sending heartbeat request');

        self.client.post('/heartbeat', request, function(err, req, res, obj) {
                if (err) {
                        log.error({err: err},
                               'error posting heartbeat request');
                        return callback(err);
                }

                self.log.trace({
                        response: obj
                }, 'successfully posted heartbeat request');

                return callback(null);
        });
        return true;
}

HeartbeatClient.prototype.start = function start(callback) {
        var self = this;
        var log = self.log;
        log.trace({
                url: self.primaryUrl,
                intervalId: self.intervalId
        }, 'starting heartbeat');
        if (self.intervalId !== null) {
                log.warn({
                        url: self.primaryUrl,
                        intervalId: self.intervalId
                }, 'heartbeat is already running');
        } else {
                self.intervalId = setInterval(function() {
                        _postHeartbeat(self, function() {});
                }, self.heartbeatInterval);
        }

        return callback();
};

HeartbeatClient.prototype.stop = function stop(callback) {
        var self = this;
        var log = self.log;

        log.trace({
                url: self.primaryUrl,
                intervalId: self.intervalId
        }, 'stopping heartbeat');

        if (self.intervalId) {
                clearInterval(self.intervalId);
                self.intervalId = null;
        } else {
                log.warn({
                        url: self.primaryUrl
                }, 'heartbeat is already stopped');
        }

        if (typeof (callback) === 'function') {
                return callback();
        }

        return true;
};
<|endoftext|>


<commit_before>'use strict';

const helpers = require('../helpers');
const logger = require('../logger');
const config = require('../../config/lib/helpers/replies');

/**
 * @return {Object}
 */
module.exports.templates = {
  campaignClosed: () => config.campaignClosed,
  // TODO: Macro replies will eventually get moved into this replies helper config.
  subscriptionStatusActive: () => helpers.macro.getMacro('subscriptionStatusActive'),
};

/**
 * Creates and sends outbound-reply Message with given messageText and messageTemplate.
 * @param {object} req
 * @param {object} res
 * @param {string} messageText
 * @param {string} messageTemplate
 */
module.exports.sendReply = async function (req, res, messageText, messageTemplate) {
  logger.debug('sendReply', { messageTemplate }, req);

  try {
    const user = await helpers.user.updateByMemberMessageReq(req);
    helpers.request.setUser(req, user);
    logger.debug('updated user', { id: user.id }, req);

    const direction = 'outbound-reply';
    const notARetry = !req.isARetryRequest();
    const retryWithoutLoadedOutboundMessage = (req.isARetryRequest() && !req.outboundMessage);

    // If member is unsubscribed. They should not receive a reply
    if (!helpers.user.isSubscriber(user)) {
      helpers.request.setSuppressOutbound(req);
    }

    /**
     * If it's not a retry request, we have to create and set the lastOutboundMessage.
     * If it is a retry request, but no outbound was created as part of the retry, we need to
     * create and set the lastOutboundMessage as well.
     */
    if (notARetry || retryWithoutLoadedOutboundMessage) {
      await req.conversation
        .createAndSetLastOutboundMessage(direction, messageText, messageTemplate, req);
    }

    if (helpers.request.shouldSuppressOutboundReply(req)) {
      logger.debug('sendReply: suppressing reply', {}, req);
    } else {
      await req.conversation.postLastOutboundMessageToPlatform(req);
    }

    const messages = {
      inbound: [req.inboundMessage],
      outbound: [req.conversation.lastOutboundMessage],
    };

    return helpers.response.sendData(res, { messages });
  } catch (err) {
    /**
     * This catches a user update error or post to platform error.
     * I think we should expose this error metadata in NewRelic.
     */
    return helpers.errorNoticeable.sendErrorResponse(res, err);
  }
};

/**
 * @param {string} string
 * @param {object} signup
 * @return {string}
 */
function replaceQuantity(string, signup) {
  let quantity = signup.totalQuantitySubmitted;
  const draft = signup.draftReportbackSubmission;
  if (draft) {
    quantity = draft.quantity;
  }

  if (quantity) {
    return string.replace(/{{quantity}}/gi, quantity);
  }
  return string;
}

/**
 * Sends given template for req.topic as reply.
 * @param {object} req
 * @param {object} res
 * @param {string} messageTemplate
 */
module.exports.sendReplyWithTopicTemplate = function (req, res, messageTemplate) {
  let messageText;
  try {
    messageText = helpers.topic.getTopicTemplateText(req.topic, messageTemplate);
    if (req.signup) {
      messageText = replaceQuantity(messageText, req.signup);
    }
  } catch (err) {
    return helpers.sendErrorResponse(res, err);
  }
  return exports.sendReply(req, res, messageText, messageTemplate);
};

module.exports.autoReply = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'autoReply');
};

module.exports.askCaption = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askCaption');
};

module.exports.askPhoto = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askPhoto');
};

module.exports.askQuantity = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askQuantity');
};

module.exports.askWhyParticipated = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askWhyParticipated');
};

module.exports.completedPhotoPost = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'completedPhotoPost');
};

module.exports.completedTextPost = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'completedTextPost');
};

module.exports.invalidAskYesNoResponse = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidAskYesNoResponse');
};

module.exports.invalidCaption = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidCaption');
};

module.exports.invalidPhoto = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidPhoto');
};

module.exports.invalidQuantity = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidQuantity');
};

module.exports.invalidText = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidText');
};

module.exports.invalidWhyParticipated = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidWhyParticipated');
};

module.exports.rivescriptReply = function (req, res, messageText) {
  return exports.sendReply(req, res, messageText, 'rivescript');
};

module.exports.saidNo = function (req, res, messageText) {
  return exports.sendReply(req, res, messageText, 'saidNo');
};

module.exports.saidYes = function (req, res, messageText) {
  return exports.sendReply(req, res, messageText, 'saidYes');
};

module.exports.startPhotoPostAutoReply = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'startPhotoPostAutoReply');
};

module.exports.sendReplyWithStaticTemplate = function (req, res, templateName) {
  const text = config[templateName].text;
  return exports.sendReply(req, res, text, templateName);
};

module.exports.badWords = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'badWords');
};

module.exports.campaignClosed = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'campaignClosed');
};

module.exports.noCampaign = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'noCampaign');
};

module.exports.noReply = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'noReply');
};
<commit_msg>chore: sends the reply to member if they are unsubscribing in this request as opposed to being ubsubscribed and texting us<commit_after>'use strict';

const helpers = require('../helpers');
const logger = require('../logger');
const config = require('../../config/lib/helpers/replies');

/**
 * @return {Object}
 */
module.exports.templates = {
  campaignClosed: () => config.campaignClosed,
  // TODO: Macro replies will eventually get moved into this replies helper config.
  subscriptionStatusActive: () => helpers.macro.getMacro('subscriptionStatusActive'),
};

/**
 * Creates and sends outbound-reply Message with given messageText and messageTemplate.
 * @param {object} req
 * @param {object} res
 * @param {string} messageText
 * @param {string} messageTemplate
 */
module.exports.sendReply = async function (req, res, messageText, messageTemplate) {
  logger.debug('sendReply', { messageTemplate }, req);

  try {
    const wasSubscriber = helpers.user.isSubscriber(req.user);
    const user = await helpers.user.updateByMemberMessageReq(req);
    const isUnsubscribed = !helpers.user.isSubscriber(user);

    // set updated user
    helpers.request.setUser(req, user);
    logger.debug('updated user', { id: user.id }, req);

    const direction = 'outbound-reply';
    const notARetry = !req.isARetryRequest();
    const retryWithoutLoadedOutboundMessage = (req.isARetryRequest() && !req.outboundMessage);

    // If member is unsubscribed. They should not receive a reply
    if (isUnsubscribed) {
      // However, we do want to send them the `subscriptionStatusStop` template when they from
      // being subscribed to the opposite as a product of this text (texting "STOP")
      if (!wasSubscriber) {
        helpers.request.setSuppressOutbound(req);
      }
    }

    /**
     * If it's not a retry request, we have to create and set the lastOutboundMessage.
     * If it is a retry request, but no outbound was created as part of the retry, we need to
     * create and set the lastOutboundMessage as well.
     */
    if (notARetry || retryWithoutLoadedOutboundMessage) {
      await req.conversation
        .createAndSetLastOutboundMessage(direction, messageText, messageTemplate, req);
    }

    if (helpers.request.shouldSuppressOutboundReply(req)) {
      logger.debug('sendReply: suppressing reply', {}, req);
    } else {
      await req.conversation.postLastOutboundMessageToPlatform(req);
    }

    const messages = {
      inbound: [req.inboundMessage],
      outbound: [req.conversation.lastOutboundMessage],
    };

    return helpers.response.sendData(res, { messages });
  } catch (err) {
    /**
     * This catches a user update error or post to platform error.
     * I think we should expose this error metadata in NewRelic.
     */
    return helpers.errorNoticeable.sendErrorResponse(res, err);
  }
};

/**
 * @param {string} string
 * @param {object} signup
 * @return {string}
 */
function replaceQuantity(string, signup) {
  let quantity = signup.totalQuantitySubmitted;
  const draft = signup.draftReportbackSubmission;
  if (draft) {
    quantity = draft.quantity;
  }

  if (quantity) {
    return string.replace(/{{quantity}}/gi, quantity);
  }
  return string;
}

/**
 * Sends given template for req.topic as reply.
 * @param {object} req
 * @param {object} res
 * @param {string} messageTemplate
 */
module.exports.sendReplyWithTopicTemplate = function (req, res, messageTemplate) {
  let messageText;
  try {
    messageText = helpers.topic.getTopicTemplateText(req.topic, messageTemplate);
    if (req.signup) {
      messageText = replaceQuantity(messageText, req.signup);
    }
  } catch (err) {
    return helpers.sendErrorResponse(res, err);
  }
  return exports.sendReply(req, res, messageText, messageTemplate);
};

module.exports.autoReply = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'autoReply');
};

module.exports.askCaption = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askCaption');
};

module.exports.askPhoto = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askPhoto');
};

module.exports.askQuantity = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askQuantity');
};

module.exports.askWhyParticipated = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'askWhyParticipated');
};

module.exports.completedPhotoPost = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'completedPhotoPost');
};

module.exports.completedTextPost = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'completedTextPost');
};

module.exports.invalidAskYesNoResponse = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidAskYesNoResponse');
};

module.exports.invalidCaption = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidCaption');
};

module.exports.invalidPhoto = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidPhoto');
};

module.exports.invalidQuantity = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidQuantity');
};

module.exports.invalidText = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidText');
};

module.exports.invalidWhyParticipated = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'invalidWhyParticipated');
};

module.exports.rivescriptReply = function (req, res, messageText) {
  return exports.sendReply(req, res, messageText, 'rivescript');
};

module.exports.saidNo = function (req, res, messageText) {
  return exports.sendReply(req, res, messageText, 'saidNo');
};

module.exports.saidYes = function (req, res, messageText) {
  return exports.sendReply(req, res, messageText, 'saidYes');
};

module.exports.startPhotoPostAutoReply = function (req, res) {
  return exports.sendReplyWithTopicTemplate(req, res, 'startPhotoPostAutoReply');
};

module.exports.sendReplyWithStaticTemplate = function (req, res, templateName) {
  const text = config[templateName].text;
  return exports.sendReply(req, res, text, templateName);
};

module.exports.badWords = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'badWords');
};

module.exports.campaignClosed = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'campaignClosed');
};

module.exports.noCampaign = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'noCampaign');
};

module.exports.noReply = function (req, res) {
  return exports.sendReplyWithStaticTemplate(req, res, 'noReply');
};
<|endoftext|>


<commit_before>const readline = require('readline')
const stripAnsi = require('strip-ansi')
const spawn = require('cross-spawn')
const wcwidth = require('wcwidth')
const spinner = require('./spinner')
const logger = require('./logger')
const SAOError = require('./SAOError')

let cachedNpmClient = null

function setNpmClient(npmClient) {
  if (npmClient) {
    cachedNpmClient = npmClient
    return npmClient
  }

  const { stdout, status } = spawn.sync('yarn', ['--version'])
  cachedNpmClient = status === 0 && stdout ? 'yarn' : 'npm'
  return cachedNpmClient
}

function getNpmClient() {
  return cachedNpmClient || setNpmClient()
}

module.exports = async ({
  cwd,
  npmClient,
  installArgs,
  packages,
  saveDev,
  registry
}) => {
  npmClient = npmClient || getNpmClient()
  const packageName = packages ? packages.join(', ') : 'packages'

  return new Promise((resolve, reject) => {
    const args = [packages ? 'add' : 'install'].concat(packages ? packages : [])
    if (saveDev) {
      args.push(npmClient === 'npm' ? '-D' : '--dev')
    }
    if (registry) {
      args.push('--registry', registry)
    }

    installArgs = args.concat(installArgs || [])
    logger.debug(npmClient, installArgs.join(' '))
    spinner.start(`Installing ${packageName} with ${npmClient}`)
    const ps = spawn(npmClient, args.concat(installArgs || []), {
      stdio: [0, 'pipe', 'pipe'],
      cwd,
      env: Object.assign(
        {
          FORCE_COLOR: true,
          /* eslint-disable camelcase */
          npm_config_color: 'always',
          npm_config_progress: true
          /* eslint-enable camelcase */
        },
        process.env
      )
    })

    let output = ''
    const stream = process.stderr

    ps.stdout &&
      ps.stdout.on('data', data => {
        output += data
        spinner.stop()
        stream.write(data)
        spinner.start()
      })

    ps.stderr &&
      ps.stderr.on('data', data => {
        output += data
        spinner.stop()
        stream.write(data)
        spinner.start()
      })

    ps.on('close', code => {
      spinner.stop()
      // Clear output when succeeded
      if (code === 0) {
        const columns = stream.columns || 80
        const lineCount = stripAnsi(output)
          .split('\n')
          .reduce((count, line) => {
            return count + Math.max(1, Math.ceil(wcwidth(line) / columns))
          }, 0)
        for (let i = 0; i < lineCount; i++) {
          if (i > 0) {
            readline.moveCursor(stream, 0, -1)
          }
          readline.clearLine(stream, 0)
          readline.cursorTo(stream, 0)
        }
        logger.success(`Installed ${packageName}`)
      } else {
        throw new SAOError(`Failed to install ${packageName} in ${cwd}`)
      }
      resolve({ code, npmClient })
    })

    ps.on('error', reject)
  })
}

module.exports.getNpmClient = getNpmClient
module.exports.setNpmClient = setNpmClient
<commit_msg>fix: handle installArgs correctly<commit_after>const readline = require('readline')
const stripAnsi = require('strip-ansi')
const spawn = require('cross-spawn')
const wcwidth = require('wcwidth')
const spinner = require('./spinner')
const logger = require('./logger')
const SAOError = require('./SAOError')

let cachedNpmClient = null

function setNpmClient(npmClient) {
  if (npmClient) {
    cachedNpmClient = npmClient
    return npmClient
  }

  const { stdout, status } = spawn.sync('yarn', ['--version'])
  cachedNpmClient = status === 0 && stdout ? 'yarn' : 'npm'
  return cachedNpmClient
}

function getNpmClient() {
  return cachedNpmClient || setNpmClient()
}

module.exports = async ({
  cwd,
  npmClient,
  installArgs,
  packages,
  saveDev,
  registry
}) => {
  npmClient = npmClient || getNpmClient()
  const packageName = packages ? packages.join(', ') : 'packages'

  return new Promise((resolve, reject) => {
    const args = [packages ? 'add' : 'install'].concat(packages ? packages : [])
    if (saveDev) {
      args.push(npmClient === 'npm' ? '-D' : '--dev')
    }
    if (registry) {
      args.push('--registry', registry)
    }

    if (installArgs) {
      args.push(...installArgs)
    }

    logger.debug(npmClient, args.join(' '))
    spinner.start(`Installing ${packageName} with ${npmClient}`)
    const ps = spawn(npmClient, args, {
      stdio: [0, 'pipe', 'pipe'],
      cwd,
      env: Object.assign(
        {
          FORCE_COLOR: true,
          /* eslint-disable camelcase */
          npm_config_color: 'always',
          npm_config_progress: true
          /* eslint-enable camelcase */
        },
        process.env
      )
    })

    let output = ''
    const stream = process.stderr

    ps.stdout &&
      ps.stdout.on('data', data => {
        output += data
        spinner.stop()
        stream.write(data)
        spinner.start()
      })

    ps.stderr &&
      ps.stderr.on('data', data => {
        output += data
        spinner.stop()
        stream.write(data)
        spinner.start()
      })

    ps.on('close', code => {
      spinner.stop()
      // Clear output when succeeded
      if (code === 0) {
        const columns = stream.columns || 80
        const lineCount = stripAnsi(output)
          .split('\n')
          .reduce((count, line) => {
            return count + Math.max(1, Math.ceil(wcwidth(line) / columns))
          }, 0)
        for (let i = 0; i < lineCount; i++) {
          if (i > 0) {
            readline.moveCursor(stream, 0, -1)
          }
          readline.clearLine(stream, 0)
          readline.cursorTo(stream, 0)
        }
        logger.success(`Installed ${packageName}`)
      } else {
        throw new SAOError(`Failed to install ${packageName} in ${cwd}`)
      }
      resolve({ code, npmClient })
    })

    ps.on('error', reject)
  })
}

module.exports.getNpmClient = getNpmClient
module.exports.setNpmClient = setNpmClient
<|endoftext|>