<commit_before>// # field mixin

/*
This mixin gets mixed into all field components.
*/

'use strict';

import shared from './shared';

export default {
  ...shared,

  // Create a unique id for the field
  getInitialState: function() {
    return {
      id: Math.random()
        .toString(36)
        .replace(/[^a-z]+/g, '')
        .substr(0, 20),
    };
  },

  // Signal a change in value.
  onChangeValue: function(value) {
    this.props.onChange(value, {
      field: this.props.field,
    });
  },

  // Bubble up a value.
  onBubbleValue: function(value, info) {
    this.props.onChange(value, info);
  },

  // Delegate rendering back to config so it can be wrapped.
  renderWithConfig: function() {
    return this.props.config.renderFieldComponent(this);
  },
};
<commit_msg>:sparkles: allow field def to pass id in<commit_after>// # field mixin

/*
This mixin gets mixed into all field components.
*/

'use strict';

import shared from './shared';

export default {
  ...shared,

  // Create a unique id for the field
  getInitialState: function() {
    return {
      id:
        this.props.field.id ||
        Math.random()
          .toString(36)
          .replace(/[^a-z]+/g, '')
          .substr(0, 20),
    };
  },

  // Signal a change in value.
  onChangeValue: function(value) {
    this.props.onChange(value, {
      field: this.props.field,
    });
  },

  // Bubble up a value.
  onBubbleValue: function(value, info) {
    this.props.onChange(value, info);
  },

  // Delegate rendering back to config so it can be wrapped.
  renderWithConfig: function() {
    return this.props.config.renderFieldComponent(this);
  },
};
<|endoftext|>


<commit_before>import store from "../store";

export default {
  methods: {
    checkArray(values, key) {
      let firstElem = -1;
      let emptyElem = 0;
      let fillElem = 0;
      values.forEach((e, i) => {
        if (e[key] === "") {
          if (firstElem === -1) {
            firstElem = i;
          }
          emptyElem += 1;
        } else {
          fillElem += 1;
        }
      });
      if (emptyElem > 1) {
        values.splice(firstElem, 1);
      }
      return fillElem === values.length;
    },
    filterArray(values, key) {
      return values.filter(e => e[key] && e[key] !== "");
    },
    telephone(value) {
      let v = value;
      if (v && v.length > 0) {
        v = v.replace(/[^0-9]/g, "");
        if (v.length === 10) {
          v = v.replace(/(\d{3})(\d{3})(\d{2})(\d{2})/, "+7 ($1) $2-$3-$4");
        }
        if (v.length === 11) {
          if (v[0] === "8") {
            v = `7${v.slice(1)}`;
          }
          v = v.replace(
            /(\d)(\d{3})(\d{3})(\d{2})(\d{2})/,
            "+$1 ($2) $3-$4-$5"
          );
        }
        if (v.length === 7) {
          v = v.replace(/(\d{3})(\d{2})(\d{2})/, "$1-$2-$3");
        }
      }
      return v;
    },
    close() {
      let current_path = this.$router.currentRoute.path;
      let last = store.getters.last;
      store.dispatch("add_from", current_path);
      this.$router.push(last);
    }
  }
};
<commit_msg>simplification func<commit_after>import store from "../store";

export default {
  methods: {
    checkArray(values) {
      let newValues = values ? values.filter(e => e !== "") : [];
      newValues.push("");
      return newValues;
    },
    filterArray(values, key) {
      return values.filter(e => e[key] && e[key] !== "");
    },
    telephone(value) {
      let v = value;
      if (v && v.length > 0) {
        v = v.replace(/[^0-9]/g, "");
        if (v.length === 10) {
          v = v.replace(/(\d{3})(\d{3})(\d{2})(\d{2})/, "+7 ($1) $2-$3-$4");
        }
        if (v.length === 11) {
          if (v[0] === "8") {
            v = `7${v.slice(1)}`;
          }
          v = v.replace(
            /(\d)(\d{3})(\d{3})(\d{2})(\d{2})/,
            "+$1 ($2) $3-$4-$5"
          );
        }
        if (v.length === 7) {
          v = v.replace(/(\d{3})(\d{2})(\d{2})/, "$1-$2-$3");
        }
      }
      return v;
    },
    close() {
      let current_path = this.$router.currentRoute.path;
      let last = store.getters.last;
      store.dispatch("add_from", current_path);
      this.$router.push(last);
    }
  }
};
<|endoftext|>


<commit_before>module.exports = function () {
  const type = this.thinky.type
  const object = type.object
  const string = type.string
  const bool = type.boolean

  return {
    tableName: 'Guild',
    schema: {
      id: string(),
      permissions: object().default({}),
      deleted: bool().default(false),
      prefix: string().default(process.env.CLIENT_PREFIX),
      lang: string().default('en'),
      tz: string().default('utc')
    },
    cache: true,
    expiry: 10
  }
}
<commit_msg>:zap: Increase guild cache expiry<commit_after>module.exports = function () {
  const type = this.thinky.type
  const object = type.object
  const string = type.string
  const bool = type.boolean

  return {
    tableName: 'Guild',
    schema: {
      id: string(),
      permissions: object().default({}),
      deleted: bool().default(false),
      prefix: string().default(process.env.CLIENT_PREFIX),
      lang: string().default('en'),
      tz: string().default('utc')
    },
    cache: true,
    expiry: 300 * 1000
  }
}
<|endoftext|>


<commit_before>// Import the neccesary modules.
import mongoose from 'mongoose';

// The movie schema used by mongoose.
const MovieSchema = new mongoose.Schema({
  _id: {
    type: String,
    required: true,
    index: {
      unique: true
    }
  },
  imdb_id: String,
  title: String,
  year: String,
  slug: String,
  synopsis: String,
  runtime: String,
  rating: {
    percentage: Number,
    watching: Number,
    votes: Number,
    loved: Number,
    hated: Number
  },
  country: String,
  last_updated: Number,
  images: {
    banner: String,
    fanart: String,
    poster: String
  },
  genres: [],
  released: Number,
  trailer: {
    type: String,
    default: null
  },
  certification: String,
  torrents: {}
});

// Create the movie model.
const Movie = mongoose.model('Movie', MovieSchema);

/**
 * A model object for movies.
 * @type {Movie}
 */
export default Movie;
<commit_msg>removed unused fields in rating<commit_after>// Import the neccesary modules.
import mongoose from 'mongoose';

// The movie schema used by mongoose.
const MovieSchema = new mongoose.Schema({
  _id: {
    type: String,
    required: true,
    index: {
      unique: true
    }
  },
  imdb_id: String,
  title: String,
  year: String,
  slug: String,
  synopsis: String,
  runtime: String,
  rating: {
    percentage: Number,
    watching: Number,
    votes: Number
  },
  country: String,
  last_updated: Number,
  images: {
    banner: String,
    fanart: String,
    poster: String
  },
  genres: [],
  released: Number,
  trailer: {
    type: String,
    default: null
  },
  certification: String,
  torrents: {}
});

// Create the movie model.
const Movie = mongoose.model('Movie', MovieSchema);

/**
 * A model object for movies.
 * @type {Movie}
 */
export default Movie;
<|endoftext|>


<commit_before>var mongoose = require('mongoose'),
    schema = new mongoose.Schema({
        title: { type: String, require: true, trim: true },
        brief: { type: String, require: true },
        text: { type: String, require: true },
        date: { type: Date, index: { unique: true }, require: true },
		updated: { type: Date, require: true, default: Date.now }
    });

module.exports.model = mongoose.model('entry', schema);<commit_msg>removed unique constraint by date<commit_after>var mongoose = require('mongoose'),
    schema = new mongoose.Schema({
        title: { type: String, require: true, trim: true },
        brief: { type: String, require: true },
        text: { type: String, require: true },
        date: { type: Date, require: true },
		updated: { type: Date, require: true, default: Date.now }
    });

module.exports.model = mongoose.model('entry', schema);<|endoftext|>


<commit_before>import Immutable from 'immutable'
import kebabCase from 'lodash/string/kebabCase'
import debug from 'debug'

import enhanceHanson from '../lib/enhance-hanson'
import pluralizeArea from '../lib/pluralize-area'

const migrationLog = debug('gobbldygook:data-migration:study')

export async function loadArea({name, type}) {
	const db = require('../lib/db')

	if (!name) {
		throw new Error(`loadArea(): 'name' must be provided`)
	}
	else if (!type) {
		throw new Error(`loadArea(): 'type' must be provided`)
	}

	const path = `${pluralizeArea(type)}/${kebabCase(name)}.yaml`

	let data = await db.stores.areas.get(path)

	if (!data) {
		throw new Error(`Could not load area ${path}`)
	}

	const enhanced = enhanceHanson(data, {topLevel: true})

	return enhanced
}

export const StudyRecord = Immutable.Record({
	id: '',
	type: '',
	name: '',
	revision: null,
	data: Promise.resolve({}),
})

export function expandOldType(type) {
	if (type === 'm') {
		return 'major'
	}
	else if (type === 'c') {
		return 'concentration'
	}
	else if (type === 'd') {
		return 'degree'
	}
	else if (type === 'e') {
		return 'emphasis'
	}
}

export function expandOldName(name) {
	if (name === 'csci') {
		return 'Computer Science'
	}
	else if (name === 'math') {
		return 'Math'
	}
	else if (name === 'phys') {
		return 'Physics'
	}
	else if (name === 'asian') {
		return 'Asian Studies'
	}
	else if (name === 'stat') {
		return 'Statistics'
	}
	else if (name === 'japan') {
		return 'Japan Studies'
	}
	else if (name === 'ba') {
		return 'Bachelor of Arts'
	}
}

export function expandOldRevisionYear(revisionYear) {
	return `${revisionYear}-${parseInt(String(revisionYear).slice(2, 4)) + 1}`
}

export function migrateFromOldSave({id, revisionYear}) {
	const [t, n] = id.split('-')
	const type = expandOldType(t)
	const name = expandOldName(n)
	const revision = expandOldRevisionYear(revisionYear)
	return {name, type, revision}
}

export default class Study extends StudyRecord {
	constructor(args) {
		let {name, type, revision} = args

		// migrate from older area save style
		if ('id' in args) {
			migrationLog(`migrating ${args.id}`);
			({name, type, revision} = migrateFromOldSave(args))
		}

		const id = `${kebabCase(name)}-${type}?rev=${revision}`
		const data = loadArea({name, type, revision})
			.catch(err => ({_error: err.message}))

		super({
			type,
			name,
			revision,
			id,
			data,
		})
	}

	toJSON() {
		return {
			type: this.type,
			name: this.name,
			revision: this.revision,
		}
	}
}
<commit_msg>be consistent with regards to accessing idb stores<commit_after>import Immutable from 'immutable'
import kebabCase from 'lodash/string/kebabCase'
import debug from 'debug'

import enhanceHanson from '../lib/enhance-hanson'
import pluralizeArea from '../lib/pluralize-area'

const migrationLog = debug('gobbldygook:data-migration:study')

export async function loadArea({name, type}) {
	const db = require('../lib/db')

	if (!name) {
		throw new Error(`loadArea(): 'name' must be provided`)
	}
	else if (!type) {
		throw new Error(`loadArea(): 'type' must be provided`)
	}

	const path = `${pluralizeArea(type)}/${kebabCase(name)}.yaml`

	let data = await db.store('areas').get(path)

	if (!data) {
		throw new Error(`Could not load area ${path}`)
	}

	const enhanced = enhanceHanson(data, {topLevel: true})

	return enhanced
}

export const StudyRecord = Immutable.Record({
	id: '',
	type: '',
	name: '',
	revision: null,
	data: Promise.resolve({}),
})

export function expandOldType(type) {
	if (type === 'm') {
		return 'major'
	}
	else if (type === 'c') {
		return 'concentration'
	}
	else if (type === 'd') {
		return 'degree'
	}
	else if (type === 'e') {
		return 'emphasis'
	}
}

export function expandOldName(name) {
	if (name === 'csci') {
		return 'Computer Science'
	}
	else if (name === 'math') {
		return 'Math'
	}
	else if (name === 'phys') {
		return 'Physics'
	}
	else if (name === 'asian') {
		return 'Asian Studies'
	}
	else if (name === 'stat') {
		return 'Statistics'
	}
	else if (name === 'japan') {
		return 'Japan Studies'
	}
	else if (name === 'ba') {
		return 'Bachelor of Arts'
	}
}

export function expandOldRevisionYear(revisionYear) {
	return `${revisionYear}-${parseInt(String(revisionYear).slice(2, 4)) + 1}`
}

export function migrateFromOldSave({id, revisionYear}) {
	const [t, n] = id.split('-')
	const type = expandOldType(t)
	const name = expandOldName(n)
	const revision = expandOldRevisionYear(revisionYear)
	return {name, type, revision}
}

export default class Study extends StudyRecord {
	constructor(args) {
		let {name, type, revision} = args

		// migrate from older area save style
		if ('id' in args) {
			migrationLog(`migrating ${args.id}`);
			({name, type, revision} = migrateFromOldSave(args))
		}

		const id = `${kebabCase(name)}-${type}?rev=${revision}`
		const data = loadArea({name, type, revision})
			.catch(err => ({_error: err.message}))

		super({
			type,
			name,
			revision,
			id,
			data,
		})
	}

	toJSON() {
		return {
			type: this.type,
			name: this.name,
			revision: this.revision,
		}
	}
}
<|endoftext|>


<commit_before>/** @flow */
import React                             from "react"; // eslint-disable-line no-unused-vars
import Immutable                         from 'immutable';
import _flattenDeep                      from 'lodash/flattenDeep';
import _compact                          from 'lodash/compact';
import { createAction, handleActions }   from 'redux-actions';
import { cacheStore }                    from '../utils/create-cache-store';
import availableExperiments              from '../utils/available-experiments';
import getKey                            from '../utils/get-key';
import generateWinActions                from '../utils/generate-win-actions';
import { immutableExperiment, immutableExperimentVariation } from '../utils/wraps-immutable';

//
// Redux Action Types
//
export const RESET          = 'redux-ab-test/RESET';
export const LOAD           = 'redux-ab-test/LOAD';
export const SET_AUDIENCE   = 'redux-ab-test/SET_AUDIENCE';
export const SET_ACTIVE     = 'redux-ab-test/SET_ACTIVE';
export const ACTIVATE       = 'redux-ab-test/ACTIVATE';
export const DEACTIVATE     = 'redux-ab-test/DEACTIVATE';
export const PLAY           = 'redux-ab-test/PLAY';
export const WIN            = 'redux-ab-test/WIN';

//
// Redux Action Creators:
//
export const reset         = createAction(RESET,            ()              => Immutable.fromJS({}));
export const load          = createAction(LOAD,             (opts = {})     => Immutable.fromJS(opts) );
export const setAudience   = createAction(SET_AUDIENCE,     (audience = {}) => Immutable.fromJS({audience}));
export const setActive     = createAction(SET_ACTIVE,       (active = {})   => Immutable.fromJS({active}));
export const activate      = createAction(ACTIVATE,         immutableExperiment );
export const deactivate    = createAction(DEACTIVATE,       immutableExperiment );
export const play          = createAction(PLAY,             immutableExperimentVariation );
export const win           = createAction(WIN,              ({experiment, variation, actionType, actionPayload}) => Immutable.fromJS({experiment, variation, actionType, actionPayload}) );

export const initialState = Immutable.fromJS({
  experiments:          [ /** Array of "experiment objects" */                        ],
  availableExperiments: { /** Hash of  "experiment key" => "experiment objects" */    },
  running:              { /** "experiment id" => counter  */                          },
  active:               { /** "experiment id" => "variation id" */                    },
  winners:              { /** "experiment id" => "variation id" */                    },
  audience:             { /** Any props you want to use for user/session targeting */ },
  route:                { pathName: null, search: null, action: null, query: {}, params: {} },
  key_path:             ['key'],
  types_path:           ['win_action_types'],
  props_path:           ['componentProps'],
  audience_path:        ['audienceProps'],
  persistent_path:      ['persistentExperience'],
  route_path:           ['routeProps'],
  win_action_types:     { /** Array of Redux Action Types */ },
});

export const middleware = (store:Object) => (next:Function) => (action:Object) => {
  // Process the input action
  const output = next(action);

  // Multi-plex the action output if we are listening for any wins
  const reduxAbTest = store.getState().reduxAbTest;
  if (reduxAbTest) {
    const actions = generateWinActions({
      reduxAbTest,
      win,
      actionType:    action.type,
      actionPayload: action.payload
    });
    actions.forEach( action => next(action) );
  }

  // Return the original action's output
  return output;
};

export const flattenCompact = (list) => Immutable.List( _compact(_flattenDeep(Immutable.fromJS([list]).toJS())) );

const reducers = {
  /**
   * RESET the experiments state.
   */
  [RESET]: (state, { }) => {
    cacheStore.clear();
    return initialState;
  },

  /**
   * LOAD the available experiments. and reset the state of the server
   */
  [LOAD]: (state, { payload }) => {
    const key_path        = flattenCompact(payload.get('key_path',      state.get('key_path')));
    const types_path      = flattenCompact(payload.get('types_path',    state.get('types_path')));
    const props_path      = flattenCompact(payload.get('props_path',    state.get('props_path')));
    const audience_path   = flattenCompact(payload.get('audience_path', state.get('audience_path')));
    const persistent_path = flattenCompact(payload.get('persistent_path', state.get('persistent_path')));
    const route_path      = flattenCompact(payload.get('route_path',    state.get('route_path')));
    const experiments     = payload.has('experiments') ? payload.get('experiments') : state.get('experiments');
    const active          = payload.has('active')      ? payload.get('active')      : state.get('active');
    const winners         = payload.has('winners')     ? payload.get('winners')     : state.get('winners');
    const audience        = payload.has('audience')    ? payload.get('audience')    : state.get('audience');
    const route           = payload.has('route')       ? payload.get('route')       : state.get('route');

    const win_action_types = experiments.reduce(
      (map, experiment) => {
        const key = getKey(experiment);
        const types = flattenCompact(experiment.getIn(types_path));
        types.forEach(type => {
          const list = map[type] || [];
          list.push(key);
          map[type] = list;
        });
        return map;
      },
      {}
    );

    return initialState.merge({
      availableExperiments: availableExperiments({
        experiments,
        key_path,
        active,
        persistent_path,
        audience_path,
        audience,
        route,
        route_path,
      }),
      experiments,
      audience,
      route,
      active,
      winners,
      key_path,
      types_path,
      props_path,
      persistent_path,
      audience_path,
      route_path,
      win_action_types
    });
  },

  /**
   * Set the Audience for the experiments
   */
  [SET_AUDIENCE]: (state, { payload }) => {
    const audience       = payload.get('audience');
    return state.merge({
      availableExperiments: availableExperiments({
        experiments:     state.get('experiments'),
        key_path:        state.get('key_path'),
        active:          state.get('active'),
        persistent_path: state.get('persistent_path'),
        audience_path:   state.get('audience_path'),
        route:           state.get('route'),
        route_path:      state.get('route_path'),
        audience,
      }),
      audience,
    });
  },

  /**
   * Intercept the redux-router events, this is specifically for reac-router integration
   */
  ['@@reduxReactRouter/routerDidChange']: (state, { payload }) => {
    const { location = {}, params = {}, routes = [] } = payload;
    const { pathname, search, action, query = {} } = location;
    const path = Immutable.fromJS(routes).map( route => route.get('path') ).filter( path => path ).last(null);
    const route = Immutable.fromJS({ path: (path || pathname), pathName: pathname, search, action, query, params });
    return state.merge({
      route,
      availableExperiments: availableExperiments({
        experiments:   state.get('experiments'),
        audience:      state.get('audience'),
        key_path:      state.get('key_path'),
        audience_path: state.get('audience_path'),
        audience:      state.get('audience'),
        route,
        route_path:    state.get('route_path'),
      }),
    });
  },

  /**
   * Set the Active variations
   */
  [SET_ACTIVE]: (state, { payload }) => {
    return state.set('active', payload.get('active'));
  },

  /**
   * ACTIVATE the experiment
   */
  [ACTIVATE]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const counter = (state.get('running').get(experimentKey) || 0) + 1;
    const running = state.get('running').set(experimentKey, counter);
    return state.set('running', running);
  },

  /**
   * DEACTIVATE the experiment
   */
  [DEACTIVATE]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const counter = (state.get('running').get(experimentKey) || 0) - 1;
    const running = (counter <= 0) ? state.get('running').delete(experimentKey) : state.get('running').set(experimentKey, counter);
    return state.set('running', running);
  },

  /**
   * A user saw an experiment
   * @payload { experiment:ExperimentType, variation:VariationType }
   */
  [PLAY]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const variationKey  = getKey(payload.get('variation'));
    const active        = state.get('active').set(experimentKey, variationKey);
    cacheStore.removeItem(experimentKey);
    return state.set('active', active);
  },

  /**
   * A user interacted with the variation
   * @payload { experiment:ExperimentType, variation:VariationType }
   */
  [WIN]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const variationKey  = getKey(payload.get('variation'));
    const winners       = state.get('winners').set(experimentKey, variationKey);
    return state.set('winners', winners);
  },
};

export default handleActions(reducers, initialState);
<commit_msg>fix: fixed missing argument<commit_after>/** @flow */
import React                             from "react"; // eslint-disable-line no-unused-vars
import Immutable                         from 'immutable';
import _flattenDeep                      from 'lodash/flattenDeep';
import _compact                          from 'lodash/compact';
import { createAction, handleActions }   from 'redux-actions';
import { cacheStore }                    from '../utils/create-cache-store';
import availableExperiments              from '../utils/available-experiments';
import getKey                            from '../utils/get-key';
import generateWinActions                from '../utils/generate-win-actions';
import { immutableExperiment, immutableExperimentVariation } from '../utils/wraps-immutable';

//
// Redux Action Types
//
export const RESET          = 'redux-ab-test/RESET';
export const LOAD           = 'redux-ab-test/LOAD';
export const SET_AUDIENCE   = 'redux-ab-test/SET_AUDIENCE';
export const SET_ACTIVE     = 'redux-ab-test/SET_ACTIVE';
export const ACTIVATE       = 'redux-ab-test/ACTIVATE';
export const DEACTIVATE     = 'redux-ab-test/DEACTIVATE';
export const PLAY           = 'redux-ab-test/PLAY';
export const WIN            = 'redux-ab-test/WIN';

//
// Redux Action Creators:
//
export const reset         = createAction(RESET,            ()              => Immutable.fromJS({}));
export const load          = createAction(LOAD,             (opts = {})     => Immutable.fromJS(opts) );
export const setAudience   = createAction(SET_AUDIENCE,     (audience = {}) => Immutable.fromJS({audience}));
export const setActive     = createAction(SET_ACTIVE,       (active = {})   => Immutable.fromJS({active}));
export const activate      = createAction(ACTIVATE,         immutableExperiment );
export const deactivate    = createAction(DEACTIVATE,       immutableExperiment );
export const play          = createAction(PLAY,             immutableExperimentVariation );
export const win           = createAction(WIN,              ({experiment, variation, actionType, actionPayload}) => Immutable.fromJS({experiment, variation, actionType, actionPayload}) );

export const initialState = Immutable.fromJS({
  experiments:          [ /** Array of "experiment objects" */                        ],
  availableExperiments: { /** Hash of  "experiment key" => "experiment objects" */    },
  running:              { /** "experiment id" => counter  */                          },
  active:               { /** "experiment id" => "variation id" */                    },
  winners:              { /** "experiment id" => "variation id" */                    },
  audience:             { /** Any props you want to use for user/session targeting */ },
  route:                { pathName: null, search: null, action: null, query: {}, params: {} },
  key_path:             ['key'],
  types_path:           ['win_action_types'],
  props_path:           ['componentProps'],
  audience_path:        ['audienceProps'],
  persistent_path:      ['persistentExperience'],
  route_path:           ['routeProps'],
  win_action_types:     { /** Array of Redux Action Types */ },
});

export const middleware = (store:Object) => (next:Function) => (action:Object) => {
  // Process the input action
  const output = next(action);

  // Multi-plex the action output if we are listening for any wins
  const reduxAbTest = store.getState().reduxAbTest;
  if (reduxAbTest) {
    const actions = generateWinActions({
      reduxAbTest,
      win,
      actionType:    action.type,
      actionPayload: action.payload
    });
    actions.forEach( action => next(action) );
  }

  // Return the original action's output
  return output;
};

export const flattenCompact = (list) => Immutable.List( _compact(_flattenDeep(Immutable.fromJS([list]).toJS())) );

const reducers = {
  /**
   * RESET the experiments state.
   */
  [RESET]: (state, { }) => {
    cacheStore.clear();
    return initialState;
  },

  /**
   * LOAD the available experiments. and reset the state of the server
   */
  [LOAD]: (state, { payload }) => {
    const key_path        = flattenCompact(payload.get('key_path',      state.get('key_path')));
    const types_path      = flattenCompact(payload.get('types_path',    state.get('types_path')));
    const props_path      = flattenCompact(payload.get('props_path',    state.get('props_path')));
    const audience_path   = flattenCompact(payload.get('audience_path', state.get('audience_path')));
    const persistent_path = flattenCompact(payload.get('persistent_path', state.get('persistent_path')));
    const route_path      = flattenCompact(payload.get('route_path',    state.get('route_path')));
    const experiments     = payload.has('experiments') ? payload.get('experiments') : state.get('experiments');
    const active          = payload.has('active')      ? payload.get('active')      : state.get('active');
    const winners         = payload.has('winners')     ? payload.get('winners')     : state.get('winners');
    const audience        = payload.has('audience')    ? payload.get('audience')    : state.get('audience');
    const route           = payload.has('route')       ? payload.get('route')       : state.get('route');

    const win_action_types = experiments.reduce(
      (map, experiment) => {
        const key = getKey(experiment);
        const types = flattenCompact(experiment.getIn(types_path));
        types.forEach(type => {
          const list = map[type] || [];
          list.push(key);
          map[type] = list;
        });
        return map;
      },
      {}
    );

    return initialState.merge({
      availableExperiments: availableExperiments({
        experiments,
        key_path,
        active,
        persistent_path,
        audience_path,
        audience,
        route,
        route_path,
      }),
      experiments,
      audience,
      route,
      active,
      winners,
      key_path,
      types_path,
      props_path,
      persistent_path,
      audience_path,
      route_path,
      win_action_types
    });
  },

  /**
   * Set the Audience for the experiments
   */
  [SET_AUDIENCE]: (state, { payload }) => {
    const audience       = payload.get('audience');
    return state.merge({
      availableExperiments: availableExperiments({
        experiments:     state.get('experiments'),
        key_path:        state.get('key_path'),
        active:          state.get('active'),
        persistent_path: state.get('persistent_path'),
        audience_path:   state.get('audience_path'),
        route:           state.get('route'),
        route_path:      state.get('route_path'),
        audience,
      }),
      audience,
    });
  },

  /**
   * Intercept the redux-router events, this is specifically for reac-router integration
   */
  ['@@reduxReactRouter/routerDidChange']: (state, { payload }) => {
    const { location = {}, params = {}, routes = [] } = payload;
    const { pathname, search, action, query = {} } = location;
    const path = Immutable.fromJS(routes).map( route => route.get('path') ).filter( path => path ).last(null);
    const route = Immutable.fromJS({ path: (path || pathname), pathName: pathname, search, action, query, params });
    return state.merge({
      route,
      availableExperiments: availableExperiments({
        experiments:   state.get('experiments'),
        active:        state.get('active'),
        key_path:      state.get('key_path'),
        audience_path: state.get('audience_path'),
        audience:      state.get('audience'),
        route,
        route_path:    state.get('route_path'),
      }),
    });
  },

  /**
   * Set the Active variations
   */
  [SET_ACTIVE]: (state, { payload }) => {
    return state.set('active', payload.get('active'));
  },

  /**
   * ACTIVATE the experiment
   */
  [ACTIVATE]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const counter = (state.get('running').get(experimentKey) || 0) + 1;
    const running = state.get('running').set(experimentKey, counter);
    return state.set('running', running);
  },

  /**
   * DEACTIVATE the experiment
   */
  [DEACTIVATE]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const counter = (state.get('running').get(experimentKey) || 0) - 1;
    const running = (counter <= 0) ? state.get('running').delete(experimentKey) : state.get('running').set(experimentKey, counter);
    return state.set('running', running);
  },

  /**
   * A user saw an experiment
   * @payload { experiment:ExperimentType, variation:VariationType }
   */
  [PLAY]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const variationKey  = getKey(payload.get('variation'));
    const active        = state.get('active').set(experimentKey, variationKey);
    cacheStore.removeItem(experimentKey);
    return state.set('active', active);
  },

  /**
   * A user interacted with the variation
   * @payload { experiment:ExperimentType, variation:VariationType }
   */
  [WIN]: (state, { payload }) => {
    const experimentKey = getKey(payload.get('experiment'));
    const variationKey  = getKey(payload.get('variation'));
    const winners       = state.get('winners').set(experimentKey, variationKey);
    return state.set('winners', winners);
  },
};

export default handleActions(reducers, initialState);
<|endoftext|>


<commit_before>(function(window, _, Backbone){
    'use strict';

    var Monaco = window.Monaco = (window.Monaco || {});

    var View = Monaco.View;
    Monaco.View = View.extend({
        // application namespace
        namespace : 'views',

        // subviews associated with this view with options to help define
        // and render each subview
        views : {},

        // override constructor function
        constructor : function(options) {
            options = options || {};
            // call the original constructor method
            View.prototype.constructor.apply(this, arguments);

            // assigns a template to the view
            if (options.template) {
                this.template = options.template;
            }

            // children view instances
            this.children = {};

            // instanciate each available subview
            _.each(this.views, this._subviewConstructor, this);

            // wrap the render method to work with subviews
            var render = this.render;
            this.render = _.bind(function() {
                // render the master view first
                render.apply(this, arguments);
                // then render subviews
                this._subviewsRender.apply(this, arguments);

                this.trigger('rendered', this);
                return this;
            }, this);

            // wrap the remove method to work with subviews
            var remove = this.remove || function() {};
            this.remove = _.bind(function() {
                // remove the subviews first
                this._subviewsRemove.apply(this, arguments);
                // then remove the master view
                remove.apply(this, arguments);

                this.trigger('removed', this);
                return this;
            }, this);
        },

        // Default render method that renders the template by appending it to the views's element
        render : function(data) {
            // only renders view's that have a template
            if ( this.template ) {
                // data will be a mixture of data parameters with either the view's collection or model
                data = _.extend(data, (this.collection ? this.collection.toJSON() : (this.model ? this.model.toJSON() : {})));

                // render the template using the data, appending the result into the view's element
                this.$el.append(this.template(data));
            }

            // return the current view after rendering to allow chainable calls
            return this;
        },

        // creates the necessary subview instance(s), storing their reference
        _subviewConstructor : function(options, selector) {
            var ViewClass,
                params = {};

            options = options || {};
            ViewClass = (options.ViewClass || Monaco.View);

            var collection = options.collection || this.collection;

            // sets the collection parameter if available
            if (collection && !options.collectionItem) {
                params.collection = collection;
            }
            // sets the model parameter if available
            if (options.model && this.model) {
                params.model = this.model;
            }
            // sets the template parameter if available
            if (options.template) {
                params.template = options.template;
            }

            // if collectionItem then creates one subview for each model in the collection
            if (collection && options.collectionItem) {
                this._subviewPerModelConstructor(selector, collection, options, params, ViewClass);
            }
            // creates only one subview
            else {
                var view = new ViewClass(params);
                view.parent = this;
                this.children[selector] = view;
            }
        },

        // creates one subview per model in the collection, it also sets the master view
        // to handle events coming from the collection
        _subviewPerModelConstructor: function(selector, collection, options, params, ViewClass) {
            // unique namespace per view child to append methods specifically to these views
            var suffix = this.views[selector].suffix = options.suffix = (options.suffix || _.uniqueId('sfx'));
            this[suffix] = {};

            // wrap the generic addOne to inject the itemView and listWrapper properties
            this[suffix].addOne = function() {
                var args = Array.prototype.slice.call(arguments, 0);
                args.push({itemView: ViewClass, listWrapper: selector});
                return Monaco.ViewForModels.addOne.apply(this, args);
            };

            // wrap the generic addAll to inject the itemView and listWrapper properties
            this[suffix].addAll = function() {
                var args = Array.prototype.slice.call(arguments, 0);
                args.push({itemView: ViewClass, listWrapper: selector});
                return Monaco.ViewForModels.addAll.apply(this, args);
            };

            // set the proper events so this view will be listening to the collection events
            // and acting acordingly
            this.listenTo(collection, ('add.' + suffix), this[suffix].addOne);
            this.listenTo(collection, ('reset.' + suffix), this[suffix].addAll);

            // for each model in the collection creates a new view with the passed parameters
            collection.each(function(model) {
                viewParams = _.clone(params);
                viewParams.model = model;
                var view = new ViewClass(viewParams);
                view.parent = this;
                this.children[selector] = this.children[selector] || [];
                this.children[selector].push(view);
            }, this);
        },

        // Render each subview after the main view has been rendered. Your can
        // override this by passing `autoRender: false` as an option for the subview
        _subviewsRender : function() {
            var _arguments = arguments;
            _.each(this.children, function(view, selector) {
                var options = this.views[selector];
                // check if the view should be rendered
                if (!_.has(options, 'autoRender') || options.autoRender === true) {

                    // render one view per collection model by using the `addAll` method
                    if ((options.collection || this.collection) && options.collectionItem) {
                        this[options.suffix].addAll((options.collection || this.collection), {}, {
                            itemView: options.ViewClass || Monaco.ViewClass,
                            listWrapper: selector
                        });
                    }
                    // render a single view
                    else {
                        view.setElement(selector);
                        view.render.apply(view, _arguments);
                    }
                }
            }, this);
        },

        // Remove (DOM + Events) each subview before removing the main view
        _subviewsRemove : function() {
            var _arguments = arguments;
            _.each(this.children, function(view, selector) {

                // get original view options
                var options = this.views[selector];

                // remove each collection's model views
                if (_.isArray(view)) {
                    // remove each subview
                    for(var i = 0, l = view.length; i < l; i++) {
                        view[i].remove.apply(view, _arguments);
                    }

                    // removed event listeners
                    this.stopListening((options.collection || this.collection), 'add.' + options.suffix);
                    this.stopListening((options.collection || this.collection), 'reset.' + options.suffix);

                    // remove the suffix namespace
                    delete this[options.suffix];
                }
                // remove single view
                else {
                    view.remove.apply(view, _arguments);
                }
            }, this);

            // reseting the this.childrent to an empty object
            this.children = {};
        }

    });

    Monaco.ViewForModels = Monaco.View.extend({
        // adds one element to the set of elements by rendering the content of a model
        addOne : function(model, options, info, fromAddAll) {
            var ViewClass = info.ItemView || this.ItemView,
                view = new ViewClass( { model : model } ),
                content = view.render( model.toJSON() ).el;
            if (fromAddAll === true) {
                return content;
            }
            $( info.listWrapper || this.listWrapper ).append( content );
        },

        // adds one element per model based on a collection instance
        addAll : function(collection, options, info) {
            var result = [];
            options = options || {};
            if ( !options.keepValues ) {
                $( info.listWrapper || this.listWrapper ).html(''); //clear list
            }
            collection.each( function( obj ) {
                result.push(this.addOne( obj, options, info, true ));
            }, this );

            $( info.listWrapper || this.listWrapper ).append(result);
        }
    });
}(window, window._, window.Backbone));<commit_msg>added method to allow subviews to be added after the main view has been instanciated or even rendered<commit_after>(function(window, _, Backbone){
    'use strict';

    var Monaco = window.Monaco = (window.Monaco || {});

    var View = Monaco.View;
    Monaco.View = View.extend({
        // application namespace
        namespace : 'views',

        // subviews associated with this view with options to help define
        // and render each subview
        views : {},

        // override constructor function
        constructor : function(options) {
            options = options || {};
            // call the original constructor method
            View.prototype.constructor.apply(this, arguments);

            // assigns a template to the view
            if (options.template) {
                this.template = options.template;
            }

            // children view instances
            this.children = {};

            // instanciate each available subview
            _.each(this.views, this._subviewConstructor, this);

            // wrap the render method to work with subviews
            var render = this.render;
            this.render = _.bind(function() {
                // render the master view first
                render.apply(this, arguments);
                // then render subviews
                this._subviewsRender.apply(this, arguments);

                this.trigger('rendered', this);
                return this;
            }, this);

            // wrap the remove method to work with subviews
            var remove = this.remove || function() {};
            this.remove = _.bind(function() {
                // remove the subviews first
                this._subviewsRemove.apply(this, arguments);
                // then remove the master view
                remove.apply(this, arguments);

                this.trigger('removed', this);
                return this;
            }, this);
        },

        // Default render method that renders the template by appending it to the views's element
        render : function(data) {
            // only renders view's that have a template
            if ( this.template ) {
                // data will be a mixture of data parameters with either the view's collection or model
                data = _.extend(data, (this.collection ? this.collection.toJSON() : (this.model ? this.model.toJSON() : {})));

                // render the template using the data, appending the result into the view's element
                this.$el.append(this.template(data));
            }

            // return the current view after rendering to allow chainable calls
            return this;
        },

        // adds a subview after this view has been instanciated
        // the parameter view should be on the same format you would include your views in a template
        add : function(view) {
            var keys = _.keys(view),
                selector = keys[0],
                options = view[selector];

            if (!selector || !options) {
                throw new Error('Invalid subview parameters');
            }
            this._subviewConstructor(options, selector);
        },

        // creates the necessary subview instance(s), storing their reference
        _subviewConstructor : function(options, selector) {
            var ViewClass,
                params = {};

            options = options || {};
            ViewClass = (options.ViewClass || Monaco.View);

            var collection = options.collection || this.collection;

            // sets the collection parameter if available
            if (collection && !options.collectionItem) {
                params.collection = collection;
            }
            // sets the model parameter if available
            if (options.model && this.model) {
                params.model = this.model;
            }
            // sets the template parameter if available
            if (options.template) {
                params.template = options.template;
            }

            // if collectionItem then creates one subview for each model in the collection
            if (collection && options.collectionItem) {
                this._subviewPerModelConstructor(selector, collection, options, params, ViewClass);
            }
            // creates only one subview
            else {
                var view = new ViewClass(params);
                view.parent = this;
                this.children[selector] = view;
            }
        },

        // creates one subview per model in the collection, it also sets the master view
        // to handle events coming from the collection
        _subviewPerModelConstructor: function(selector, collection, options, params, ViewClass) {
            // unique namespace per view child to append methods specifically to these views
            var suffix = this.views[selector].suffix = options.suffix = (options.suffix || _.uniqueId('sfx'));
            this[suffix] = {};

            // wrap the generic addOne to inject the itemView and listWrapper properties
            this[suffix].addOne = function() {
                var args = Array.prototype.slice.call(arguments, 0);
                args.push({itemView: ViewClass, listWrapper: selector});
                return Monaco.ViewForModels.addOne.apply(this, args);
            };

            // wrap the generic addAll to inject the itemView and listWrapper properties
            this[suffix].addAll = function() {
                var args = Array.prototype.slice.call(arguments, 0);
                args.push({itemView: ViewClass, listWrapper: selector});
                return Monaco.ViewForModels.addAll.apply(this, args);
            };

            // set the proper events so this view will be listening to the collection events
            // and acting acordingly
            this.listenTo(collection, ('add.' + suffix), this[suffix].addOne);
            this.listenTo(collection, ('reset.' + suffix), this[suffix].addAll);

            // for each model in the collection creates a new view with the passed parameters
            collection.each(function(model) {
                viewParams = _.clone(params);
                viewParams.model = model;
                var view = new ViewClass(viewParams);
                view.parent = this;
                this.children[selector] = this.children[selector] || [];
                this.children[selector].push(view);
            }, this);
        },

        // Render each subview after the main view has been rendered. Your can
        // override this by passing `autoRender: false` as an option for the subview
        _subviewsRender : function() {
            var _arguments = arguments;
            _.each(this.children, function(view, selector) {
                var options = this.views[selector];
                // check if the view should be rendered
                if (!_.has(options, 'autoRender') || options.autoRender === true) {

                    // render one view per collection model by using the `addAll` method
                    if ((options.collection || this.collection) && options.collectionItem) {
                        this[options.suffix].addAll((options.collection || this.collection), {}, {
                            itemView: options.ViewClass || Monaco.ViewClass,
                            listWrapper: selector
                        });
                    }
                    // render a single view
                    else {
                        view.setElement(selector);
                        view.render.apply(view, _arguments);
                    }
                }
            }, this);
        },

        // Remove (DOM + Events) each subview before removing the main view
        _subviewsRemove : function() {
            var _arguments = arguments;
            _.each(this.children, function(view, selector) {

                // get original view options
                var options = this.views[selector];

                // remove each collection's model views
                if (_.isArray(view)) {
                    // remove each subview
                    for(var i = 0, l = view.length; i < l; i++) {
                        view[i].remove.apply(view, _arguments);
                    }

                    // removed event listeners
                    this.stopListening((options.collection || this.collection), 'add.' + options.suffix);
                    this.stopListening((options.collection || this.collection), 'reset.' + options.suffix);

                    // remove the suffix namespace
                    delete this[options.suffix];
                }
                // remove single view
                else {
                    view.remove.apply(view, _arguments);
                }
            }, this);

            // reseting the this.childrent to an empty object
            this.children = {};
        }

    });

    Monaco.ViewForModels = Monaco.View.extend({
        // adds one element to the set of elements by rendering the content of a model
        addOne : function(model, options, info, fromAddAll) {
            var ViewClass = info.ItemView || this.ItemView,
                view = new ViewClass( { model : model } ),
                content = view.render( model.toJSON() ).el;
            if (fromAddAll === true) {
                return content;
            }
            $( info.listWrapper || this.listWrapper ).append( content );
        },

        // adds one element per model based on a collection instance
        addAll : function(collection, options, info) {
            var result = [];
            options = options || {};
            if ( !options.keepValues ) {
                $( info.listWrapper || this.listWrapper ).html(''); //clear list
            }
            collection.each( function( obj ) {
                result.push(this.addOne( obj, options, info, true ));
            }, this );

            $( info.listWrapper || this.listWrapper ).append(result);
        }
    });
}(window, window._, window.Backbone));<|endoftext|>


<commit_before>var fs = require('fs');
var util = require('util');
var soap = require('soap');
var sprintf = require('sprintf-js').sprintf;
var config = require('./trntxtconfig.js');

var stations = getStations('resources/station_codes.csv');

var soapUrl = 'https://lite.realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2014-02-20';
var altSoapUrl = 'http://lite.realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2014-02-20';
var soapHeader = util.format('<AccessToken><TokenValue>%s</TokenValue></AccessToken>', config.apiKey
	|| console.error("No API key provided. Received: "+config.apiKey));
var oErrorStation = {stationName: "error", stationCode: "XXX"};

// Check HTTPS is available. If it throws an error, use HTTP instead.
soap.createClient(soapUrl, function(err, client) {
	if (err && err.code === 'CERT_HAS_EXPIRED') {
		console.error('Darwin HTTPS certificate expired. Using HTTP instead');
		soapUrl = altSoapUrl;
	}
});

/**
 * Returns an array of station objects, each with stationName and stationCode
 */
function getStations(stationCsv) {
	var i;
	var result = fs.readFileSync(stationCsv, 'utf8').split('\n');
	result = result.slice(1, result.length);
	var output = [];
	for (i = 0; i < result.length; i += 1) {
		if (result[i].split(',').length === 2) output.push({stationName: result[i].split(',')[0].trim(), stationCode: result[i].split(',')[1].trim()});
	}
	return output;
}

function fnFindStation(input) {
	var i;
	var bestMatch = {
		stationNumber: -1,
		matchIndex: -1
	}
	var position;

	// console.log("Finding station for input " + input);
	if (input.length == 3) {
		// console.log("Checking station codes");
		for(i = 0; i < stations.length; i++) {
			if (stations[i].stationCode.toUpperCase() === input.toUpperCase()) {
				// console.log(util.format("Found station %d: %s (%s)", i, stations[i].stationName, stations[i].stationCode));
				return stations[i];
			}
		}
	}

	// console.log("Checking station name equality");
	for(i = 0; i < stations.length; i++) {
		if (sanitise(stations[i].stationName) === sanitise(input)) {
			// console.log(util.format("Found station %d: %s (%s)", i, stations[i].stationName, stations[i].stationCode));
			return stations[i];
		}
	}

	// console.log("Checking station name matches");
	for(i = 0; i < stations.length; i++) {
		position = sanitise(stations[i].stationName).indexOf(sanitise(input));
		if (position != -1) {
			// console.log(util.format("Found station %d: %s (%s) at position %d", i, stations[i].stationName, stations[i].stationCode, position));
			if (bestMatch.stationNumber === -1 || bestMatch.matchIndex > position) {
				// console.log('new best match!');
				bestMatch.stationNumber = i;
				bestMatch.matchIndex = position;
			}
		}
	}
	if (bestMatch.stationNumber !== -1) return stations[bestMatch.stationNumber];

	console.error("No match found");
	return errorStation;
}

function sanitise(input) {
	return input.toUpperCase().replace(/[^A-Z]/g,'');
}

function fnGetDepartures(stations, callback) {
	if (config.apiKey === undefined) {
		console.error('No API key set!');
		var cb = {pageTitle:'trntxt: ERROR', content:'
Error: No API key set.
'};
		callback(cb);
		return;
	}

	getDepartureObject(stations, function(departureObject) {
		// console.log(stations);
		var jadeResponse = {
			content: generateDepartureHtml(departureObject),
			pageTitle: 'trntxt: ' + departureObject.fromStation.stationCode,
			fromStation: departureObject.fromStation.stationCode
		};
		if (departureObject.toStation !== undefined) {
			jadeResponse.pageTitle += ' > ' + departureObject.toStation.stationCode;
			jadeResponse.toStation = departureObject.toStation.stationCode;
		}
		callback(jadeResponse);
	});
}

function getDepartureObject(stations, callback) {
	var output = {};
	output.fromStation = stations.fromStation;
	if (stations.toStation !== undefined) output.toStation = stations.toStation;
	output.trainServices = [];
	// TODO output.busServices = [];

	var options = {
		numRows: 10,
		crs: stations.fromStation.stationCode
	};
	if (stations.toStation !== undefined) {
		options.filterCrs = stations.toStation.stationCode;
	}

	soap.createClient(soapUrl, function(err, client) {
		if (err) return console.error(err);
		client.addSoapHeader(soapHeader);
		return client.GetDepartureBoard(options, function(err, result) {
			fs.writeFile('public/lastrequest.txt', JSON.stringify(result), function(err) {
				if (err) return console.error(err);
			});

			var oTrainServices = result.GetStationBoardResult.trainServices;
			var aServices = oTrainServices === undefined ? [] : oTrainServices.service;
			var i;
			var aPromises = [];
			for (i = 0; i < aServices.length; i += 1) {
				output.trainServices[i] = {};
				output.trainServices[i].originStation = {
					stationName: aServices[i].origin.location[0].locationName
				};
				output.trainServices[i].destinationStation = {
					stationName: aServices[i].destination.location[0].locationName
				};
				output.trainServices[i].std = aServices[i].std;
				output.trainServices[i].etd = aServices[i].etd;
				if (aServices[i].platform !== undefined) {
					output.trainServices[i].platform = aServices[i].platform;
				} else {
					output.trainServices[i].platform = null;
				}
				output.trainServices[i].serviceID = aServices[i].serviceID;
				if (output.toStation !== undefined) aPromises.push(makePromiseForService(output.trainServices[i].serviceID));
			}
			Promise.all(aPromises).then(function(detailedServices) {
				// console.log("All details received");
				for (var i = 0; i < detailedServices.length; i++) {
					var arrival = getArrivalTimeForService(detailedServices[i], output.toStation);
					output.trainServices[i].sta = arrival.sta;
					output.trainServices[i].eta = arrival.eta;
				}
				return callback(output);
			}, function(error) {
				console.error("WAAAAAAH", error);
				throw error;
			});
		});
	});
}

function generateDepartureHtml(oDepartures) {
	var output = util.format('Departure board for %s (%s)',
		oDepartures.fromStation.stationName, oDepartures.fromStation.stationCode);
	if (oDepartures.toStation !== undefined) {
		output += util.format(' calling at %s (%s)',
			oDepartures.toStation.stationName, oDepartures.toStation.stationCode);
	}
	output += '

';
	if (oDepartures.trainServices.length === 0) {
		output += 'No services found. Trntxt currently does not display information for replacement bus services or routes requiring changes.';
	} else {
		oDepartures.trainServices.forEach(function(service) {
			output += '----

';
			output += service.originStation.stationName + ' > ';
			output += service.destinationStation.stationName + '
';
			output += 'Departs ' + oDepartures.fromStation.stationName + ' at ';
			// If the train is delayed, strike out the original departure time
			if (service.etd !== 'On time') {
				output += '';
				output += service.std;
				output += '';
			} else {
				output += service.std;
			}
			// Show the estimated departure time: if train is on time it will say 'On time'
			output += ' (' + service.etd + '), ';
			if (service.platform !== null) {
				output += 'platform ' + service.platform + '
';
			} else {
				output += 'no platform information available
';
			}
			if (oDepartures.toStation !== undefined) {
				output += 'Arriving at ' + oDepartures.toStation.stationName + ' at ';
				if (service.eta !== 'On time') {
					output += '' + service.sta + '';
				} else {
					output += service.sta;
				}
				output += ' (' + service.eta + ')';
				output += '
';
			}
			output += '
';
		});
	}
	return output;
}

function getArrivalTimeForService(service, toStation) {
	var output = {};
	var callingPointArray = service.GetServiceDetailsResult.subsequentCallingPoints.callingPointList[0].callingPoint;
	for (var i = 0; i < callingPointArray.length; i++) {
		if (callingPointArray[i].crs === toStation.stationCode) {
			output.sta = callingPointArray[i].st;
			output.eta = callingPointArray[i].et;
			break;
		}
	}
	return output;
}

function makePromiseForService(serviceId) {
	var options = { serviceID: serviceId };
	return new Promise(function(resolve, reject) {
		soap.createClient(soapUrl, function(err, client) {
			client.addSoapHeader(soapHeader);
			client.GetServiceDetails(options, function(err, result) {
				if (err) return reject(err);
				return resolve(result);
			});
		});
	});
}

function fnGetServiceDetails(serviceId, callback) {
	return soap.createClient(soapUrl, function(err, client) {
		client.addSoapHeader(soapHeader);
		if (err) throw err;
		var options = {serviceID: serviceId};
		return client.GetServiceDetails(options, function(result) {
			return callback(result);
		});
	});
}

exports.findStation   = fnFindStation;           // function findStation()
exports.getDepartures = fnGetDepartures;         // function getDepartures(response, fromStation, toStation)
exports.errorStation  = oErrorStation;           // A station object which can be used when an error occurrs.
exports.getServiceDetails = fnGetServiceDetails; // function getServiceDetails(serviceId, callback)
<commit_msg>Attempt at fixing path name<commit_after>var fs = require('fs');
var path = require('path');
var util = require('util');
var soap = require('soap');
var sprintf = require('sprintf-js').sprintf;
var config = require('./trntxtconfig.js');

var stations = getStations('../resources/station_codes.csv');

var soapUrl = 'https://lite.realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2014-02-20';
var altSoapUrl = 'http://lite.realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2014-02-20';
var soapHeader = util.format('<AccessToken><TokenValue>%s</TokenValue></AccessToken>', config.apiKey
	|| console.error("No API key provided. Received: "+config.apiKey));
var oErrorStation = {stationName: "error", stationCode: "XXX"};

// Check HTTPS is available. If it throws an error, use HTTP instead.
soap.createClient(soapUrl, function(err, client) {
	if (err && err.code === 'CERT_HAS_EXPIRED') {
		console.error('Darwin HTTPS certificate expired. Using HTTP instead');
		soapUrl = altSoapUrl;
	}
});

/**
 * Returns an array of station objects, each with stationName and stationCode
 */
function getStations(stationCsv) {
	var i;
	var result = fs.readFileSync(path.join(__dirname, stationCsv), 'utf8').split('\n');
	result = result.slice(1, result.length);
	var output = [];
	for (i = 0; i < result.length; i += 1) {
		if (result[i].split(',').length === 2) output.push({stationName: result[i].split(',')[0].trim(), stationCode: result[i].split(',')[1].trim()});
	}
	return output;
}

function fnFindStation(input) {
	var i;
	var bestMatch = {
		stationNumber: -1,
		matchIndex: -1
	}
	var position;

	// console.log("Finding station for input " + input);
	if (input.length == 3) {
		// console.log("Checking station codes");
		for(i = 0; i < stations.length; i++) {
			if (stations[i].stationCode.toUpperCase() === input.toUpperCase()) {
				// console.log(util.format("Found station %d: %s (%s)", i, stations[i].stationName, stations[i].stationCode));
				return stations[i];
			}
		}
	}

	// console.log("Checking station name equality");
	for(i = 0; i < stations.length; i++) {
		if (sanitise(stations[i].stationName) === sanitise(input)) {
			// console.log(util.format("Found station %d: %s (%s)", i, stations[i].stationName, stations[i].stationCode));
			return stations[i];
		}
	}

	// console.log("Checking station name matches");
	for(i = 0; i < stations.length; i++) {
		position = sanitise(stations[i].stationName).indexOf(sanitise(input));
		if (position != -1) {
			// console.log(util.format("Found station %d: %s (%s) at position %d", i, stations[i].stationName, stations[i].stationCode, position));
			if (bestMatch.stationNumber === -1 || bestMatch.matchIndex > position) {
				// console.log('new best match!');
				bestMatch.stationNumber = i;
				bestMatch.matchIndex = position;
			}
		}
	}
	if (bestMatch.stationNumber !== -1) return stations[bestMatch.stationNumber];

	console.error("No match found");
	return errorStation;
}

function sanitise(input) {
	return input.toUpperCase().replace(/[^A-Z]/g,'');
}

function fnGetDepartures(stations, callback) {
	if (config.apiKey === undefined) {
		console.error('No API key set!');
		var cb = {pageTitle:'trntxt: ERROR', content:'
Error: No API key set.
'};
		callback(cb);
		return;
	}

	getDepartureObject(stations, function(departureObject) {
		// console.log(stations);
		var jadeResponse = {
			content: generateDepartureHtml(departureObject),
			pageTitle: 'trntxt: ' + departureObject.fromStation.stationCode,
			fromStation: departureObject.fromStation.stationCode
		};
		if (departureObject.toStation !== undefined) {
			jadeResponse.pageTitle += ' > ' + departureObject.toStation.stationCode;
			jadeResponse.toStation = departureObject.toStation.stationCode;
		}
		callback(jadeResponse);
	});
}

function getDepartureObject(stations, callback) {
	var output = {};
	output.fromStation = stations.fromStation;
	if (stations.toStation !== undefined) output.toStation = stations.toStation;
	output.trainServices = [];
	// TODO output.busServices = [];

	var options = {
		numRows: 10,
		crs: stations.fromStation.stationCode
	};
	if (stations.toStation !== undefined) {
		options.filterCrs = stations.toStation.stationCode;
	}

	soap.createClient(soapUrl, function(err, client) {
		if (err) return console.error(err);
		client.addSoapHeader(soapHeader);
		return client.GetDepartureBoard(options, function(err, result) {
			fs.writeFile('public/lastrequest.txt', JSON.stringify(result), function(err) {
				if (err) return console.error(err);
			});

			var oTrainServices = result.GetStationBoardResult.trainServices;
			var aServices = oTrainServices === undefined ? [] : oTrainServices.service;
			var i;
			var aPromises = [];
			for (i = 0; i < aServices.length; i += 1) {
				output.trainServices[i] = {};
				output.trainServices[i].originStation = {
					stationName: aServices[i].origin.location[0].locationName
				};
				output.trainServices[i].destinationStation = {
					stationName: aServices[i].destination.location[0].locationName
				};
				output.trainServices[i].std = aServices[i].std;
				output.trainServices[i].etd = aServices[i].etd;
				if (aServices[i].platform !== undefined) {
					output.trainServices[i].platform = aServices[i].platform;
				} else {
					output.trainServices[i].platform = null;
				}
				output.trainServices[i].serviceID = aServices[i].serviceID;
				if (output.toStation !== undefined) aPromises.push(makePromiseForService(output.trainServices[i].serviceID));
			}
			Promise.all(aPromises).then(function(detailedServices) {
				// console.log("All details received");
				for (var i = 0; i < detailedServices.length; i++) {
					var arrival = getArrivalTimeForService(detailedServices[i], output.toStation);
					output.trainServices[i].sta = arrival.sta;
					output.trainServices[i].eta = arrival.eta;
				}
				return callback(output);
			}, function(error) {
				console.error("WAAAAAAH", error);
				throw error;
			});
		});
	});
}

function generateDepartureHtml(oDepartures) {
	var output = util.format('Departure board for %s (%s)',
		oDepartures.fromStation.stationName, oDepartures.fromStation.stationCode);
	if (oDepartures.toStation !== undefined) {
		output += util.format(' calling at %s (%s)',
			oDepartures.toStation.stationName, oDepartures.toStation.stationCode);
	}
	output += '

';
	if (oDepartures.trainServices.length === 0) {
		output += 'No services found. Trntxt currently does not display information for replacement bus services or routes requiring changes.';
	} else {
		oDepartures.trainServices.forEach(function(service) {
			output += '----

';
			output += service.originStation.stationName + ' > ';
			output += service.destinationStation.stationName + '
';
			output += 'Departs ' + oDepartures.fromStation.stationName + ' at ';
			// If the train is delayed, strike out the original departure time
			if (service.etd !== 'On time') {
				output += '';
				output += service.std;
				output += '';
			} else {
				output += service.std;
			}
			// Show the estimated departure time: if train is on time it will say 'On time'
			output += ' (' + service.etd + '), ';
			if (service.platform !== null) {
				output += 'platform ' + service.platform + '
';
			} else {
				output += 'no platform information available
';
			}
			if (oDepartures.toStation !== undefined) {
				output += 'Arriving at ' + oDepartures.toStation.stationName + ' at ';
				if (service.eta !== 'On time') {
					output += '' + service.sta + '';
				} else {
					output += service.sta;
				}
				output += ' (' + service.eta + ')';
				output += '
';
			}
			output += '
';
		});
	}
	return output;
}

function getArrivalTimeForService(service, toStation) {
	var output = {};
	var callingPointArray = service.GetServiceDetailsResult.subsequentCallingPoints.callingPointList[0].callingPoint;
	for (var i = 0; i < callingPointArray.length; i++) {
		if (callingPointArray[i].crs === toStation.stationCode) {
			output.sta = callingPointArray[i].st;
			output.eta = callingPointArray[i].et;
			break;
		}
	}
	return output;
}

function makePromiseForService(serviceId) {
	var options = { serviceID: serviceId };
	return new Promise(function(resolve, reject) {
		soap.createClient(soapUrl, function(err, client) {
			client.addSoapHeader(soapHeader);
			client.GetServiceDetails(options, function(err, result) {
				if (err) return reject(err);
				return resolve(result);
			});
		});
	});
}

function fnGetServiceDetails(serviceId, callback) {
	return soap.createClient(soapUrl, function(err, client) {
		client.addSoapHeader(soapHeader);
		if (err) throw err;
		var options = {serviceID: serviceId};
		return client.GetServiceDetails(options, function(result) {
			return callback(result);
		});
	});
}

exports.findStation   = fnFindStation;           // function findStation()
exports.getDepartures = fnGetDepartures;         // function getDepartures(response, fromStation, toStation)
exports.errorStation  = oErrorStation;           // A station object which can be used when an error occurrs.
exports.getServiceDetails = fnGetServiceDetails; // function getServiceDetails(serviceId, callback)
<|endoftext|>


<commit_before>module.exports = {
  config: {
    description: 'Path to config file',
    type: 'string',
    default: 'aw.config.js',
  },
  glob: {
    description: 'Glob pattern',
    default: ['test/**/*.spec.js'],
    type: 'array',
  },
  require: {
    description: 'Require path',
    default: [],
    type: 'array',
  },
  watch: {
    description: 'Watch changes',
    default: false,
    type: 'boolean',
  },
  coverage: {
    description: 'Generate coverage',
    default: false,
    type: 'boolean',
  },
  'nyc.require': {
    description: 'Require path',
    default: [],
    type: 'array',
  },
  'nyc.include': {
    description: 'Include glob',
    default: [],
    type: 'array',
  },
  'nyc.exclude': {
    description: 'Exclude glob',
    default: ['**/coverage/**'],
    type: 'array',
  },
  'nyc.sourceMap': {
    description: 'Should nyc detect and handle source maps?',
    default: true,
    type: 'boolean',
  },
  'nyc.babel': {
    description: 'Sets up a preferred babel test environment e.g add `babel-register` to `nyc.require` `nyc.sourceMap=false` and `nyc.instrument=./lib/instrumenters/noop`',
    default: true,
    type: 'boolean',
  },
  'nyc.tempDirectory': {
    description: 'Directory to output raw coverage information to',
    default: './coverage/.nyc_output',
    type: 'string',
  },
  'nyc.reporter': {
    description: 'Coverage reporter(s) to use',
    default: ['text', 'lcov', 'text-summary'],
    type: 'array',
  },
  'nyc.reporterDir': {
    description: 'Directory to output coverage reports in',
    default: 'coverage',
    type: 'string',
  },
};
<commit_msg>Fix `nyc` option<commit_after>module.exports = {
  config: {
    description: 'Path to config file',
    type: 'string',
    default: 'aw.config.js',
  },
  glob: {
    description: 'Glob pattern',
    default: ['test/**/*.spec.js'],
    type: 'array',
  },
  require: {
    description: 'Require path',
    default: [],
    type: 'array',
  },
  watch: {
    description: 'Watch changes',
    default: false,
    type: 'boolean',
  },
  coverage: {
    description: 'Generate coverage',
    default: false,
    type: 'boolean',
  },
  'nyc.require': {
    description: 'Require path',
    default: [],
    type: 'array',
  },
  'nyc.include': {
    description: 'Include glob',
    default: [],
    type: 'array',
  },
  'nyc.exclude': {
    description: 'Exclude glob',
    default: ['**/coverage/**'],
    type: 'array',
  },
  'nyc.sourceMap': {
    description: 'Should nyc detect and handle source maps?',
    default: false,
    type: 'boolean',
  },
  'nyc.babel': {
    description: 'Sets up a preferred babel test environment e.g add `babel-register` to `nyc.require` `nyc.sourceMap=false` and `nyc.instrument=./lib/instrumenters/noop`',
    default: true,
    type: 'boolean',
  },
  'nyc.tempDirectory': {
    description: 'Directory to output raw coverage information to',
    default: './coverage/.nyc_output',
    type: 'string',
  },
  'nyc.reporter': {
    description: 'Coverage reporter(s) to use',
    default: ['ßlcov', 'text-summary'],
    type: 'array',
  },
  'nyc.reportDir': {
    description: 'Directory to output coverage reports in',
    default: 'coverage',
    type: 'string',
  },
};
<|endoftext|>


<commit_before>/**
 * @constructor
 */
webfont.FontWatcher = function(domHelper, eventDispatcher, fontSizer,
    asyncCall, getTime) {
  this.domHelper_ = domHelper;
  this.eventDispatcher_ = eventDispatcher;
  this.fontSizer_ = fontSizer;
  this.asyncCall_ = asyncCall;
  this.getTime_ = getTime;
  this.currentlyWatched_ = 0;
  this.last_ = false;
  this.success_ = false;
  this.nameHelper_ = new webfont.CssFontFamilyName();
  this.fvd_ = new webfont.FontVariationDescription();
};

/**
 * A set of sans-serif fonts and a generic family that cover most platforms:
 * Windows - arial - 99.71%
 * Mac - arial - 97.67%
 * Linux - 97.67%
 * (Based on http://www.codestyle.org/css/font-family/sampler-CombinedResults.shtml)
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_FONTS_A = 'arial,"URW Gothic L",sans-serif';

/**
 * A set of serif fonts and a generic family that cover most platforms. We
 * want each of these fonts to have a different width when rendering the test
 * string than each of the fonts in DEFAULT_FONTS_A:
 * Windows - Georgia - 98.98%
 * Mac - Georgia - 95.60%
 * Linux - Century Schoolbook L - 97.97%
 * (Based on http://www.codestyle.org/css/font-family/sampler-CombinedResults.shtml)
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_FONTS_B = 'Georgia,"Century Schoolbook L",serif';

/**
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_VARIATION = 'n4';

/**
 * Watches a set of font families.
 * @param {Array.<string>} fontFamilies The font family names to watch.
 * @param {Object.<string, Array.<string>>} fontDescriptions The font variations
 *     of each family to watch. Described with FVD.
 * @param {Object.<string, string>} fontTestStrings The font test strings for
 *     each family.
 * @param {boolean} last True if this is the last set of families to watch.
 */
webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
    fontTestStrings, last) {
  var length = fontFamilies.length;

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    if (!fontDescriptions[fontFamily]) {
      fontDescriptions[fontFamily] = [webfont.FontWatcher.DEFAULT_VARIATION];
    }
    this.currentlyWatched_ += fontDescriptions[fontFamily].length;
  }

  if (last) {
    this.last_ = last;
  }

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    var descriptions = fontDescriptions[fontFamily];
    var fontTestString  = fontTestStrings[fontFamily] || 'Mm';

    for (var j = 0, len = descriptions.length; j < len; j++) {
      var fontDescription = descriptions[j];

      var defaultFontsA = webfont.FontWatcher.DEFAULT_FONTS_A;
      var originalSizeA = this.getDefaultFontSize_(defaultFontsA,
          fontDescription, fontTestString);

      var defaultFontsB = webfont.FontWatcher.DEFAULT_FONTS_B;
      var originalSizeB = this.getDefaultFontSize_(defaultFontsB,
          fontDescription, fontTestString);

      this.watch_(fontFamily, fontDescription, fontTestString,
          originalSizeA, defaultFontsA, originalSizeB, defaultFontsB);
    }
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.watch_ = function(fontFamily, fontDescription,
    fontTestString, originalSizeA, defaultFontsA, originalSizeB, defaultFontsB) {
  this.eventDispatcher_.dispatchFontLoading(fontFamily, fontDescription);
  var requestedFontA = this.createHiddenElementWithFont_(this.nameHelper_.quote(fontFamily),
      defaultFontsA, fontDescription, fontTestString);
  var sizeA = this.fontSizer_.getWidth(requestedFontA);

  var requestedFontB = this.createHiddenElementWithFont_(this.nameHelper_.quote(fontFamily),
      defaultFontsB, fontDescription, fontTestString);
  var sizeB = this.fontSizer_.getWidth(requestedFontB);

  if (originalSizeA != sizeA || originalSizeB != sizeB) {
    this.domHelper_.removeElement(requestedFontA);
    this.domHelper_.removeElement(requestedFontB);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else {
    this.asyncCheck_(this.getTime_(), fontFamily, fontDescription,
        originalSizeA, requestedFontA, originalSizeB, requestedFontB);
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
  if (--this.currentlyWatched_ == 0 && this.last_) {
    if (this.success_) {
      this.eventDispatcher_.dispatchActive();
    } else {
      this.eventDispatcher_.dispatchInactive();
    }
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.check_ = function(started, fontFamily,
    fontDescription, originalSizeA, requestedFontA, originalSizeB, requestedFontB) {
  var sizeA = this.fontSizer_.getWidth(requestedFontA);
  var sizeB = this.fontSizer_.getWidth(requestedFontB);

  if (originalSizeA != sizeA || originalSizeB != sizeB) {
    this.domHelper_.removeElement(requestedFontA);
    this.domHelper_.removeElement(requestedFontB);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else if ((this.getTime_() - started) < 5000) {
    this.asyncCheck_(started, fontFamily, fontDescription, originalSizeA,
        requestedFontA, originalSizeB, requestedFontB);
  } else {
    this.domHelper_.removeElement(requestedFontA);
    this.domHelper_.removeElement(requestedFontB);
    this.eventDispatcher_.dispatchFontInactive(fontFamily, fontDescription);
    this.decreaseCurrentlyWatched_();
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.asyncCheck_ = function(started, fontFamily,
    fontDescription, originalSizeA, requestedFontA, originalSizeB, requestedFontB) {
  this.asyncCall_(function(context, func) {
    return function() {
      func.call(context, started, fontFamily, fontDescription, originalSizeA,
          requestedFontA, originalSizeB, requestedFontB);
    }
  }(this, this.check_), 50);
};

/**
 * @private
 */
webfont.FontWatcher.prototype.getDefaultFontSize_ = function(defaultFonts,
    fontDescription, fontTestString) {
  var defaultFont = this.createHiddenElementWithFont_(
      null, defaultFonts, fontDescription, fontTestString);
  var size = this.fontSizer_.getWidth(defaultFont);

  this.domHelper_.removeElement(defaultFont);
  return size;
};

/**
 * @private
 */
webfont.FontWatcher.prototype.createHiddenElementWithFont_ = function(
    fontFamily, defaultFonts, fontDescription, fontTestString) {
  fontFamily = fontFamily ? fontFamily + "," : "";
  var variationCss = this.fvd_.expand(fontDescription);
  var styleString = "position:absolute;top:-999px;font-size:300px;font-family:" +
      fontFamily + defaultFonts + ";" + variationCss;
  var span = this.domHelper_.createElement('span', { 'style': styleString },
      fontTestString);

  this.domHelper_.insertInto('body', span);
  return span;
};
<commit_msg>Moves default test string to a constant and changes it based on suggestions from Tim Ahrens.<commit_after>/**
 * @constructor
 */
webfont.FontWatcher = function(domHelper, eventDispatcher, fontSizer,
    asyncCall, getTime) {
  this.domHelper_ = domHelper;
  this.eventDispatcher_ = eventDispatcher;
  this.fontSizer_ = fontSizer;
  this.asyncCall_ = asyncCall;
  this.getTime_ = getTime;
  this.currentlyWatched_ = 0;
  this.last_ = false;
  this.success_ = false;
  this.nameHelper_ = new webfont.CssFontFamilyName();
  this.fvd_ = new webfont.FontVariationDescription();
};

/**
 * A set of sans-serif fonts and a generic family that cover most platforms:
 * Windows - arial - 99.71%
 * Mac - arial - 97.67%
 * Linux - 97.67%
 * (Based on http://www.codestyle.org/css/font-family/sampler-CombinedResults.shtml)
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_FONTS_A = 'arial,"URW Gothic L",sans-serif';

/**
 * A set of serif fonts and a generic family that cover most platforms. We
 * want each of these fonts to have a different width when rendering the test
 * string than each of the fonts in DEFAULT_FONTS_A:
 * Windows - Georgia - 98.98%
 * Mac - Georgia - 95.60%
 * Linux - Century Schoolbook L - 97.97%
 * (Based on http://www.codestyle.org/css/font-family/sampler-CombinedResults.shtml)
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_FONTS_B = 'Georgia,"Century Schoolbook L",serif';

/**
 * Default test string. Characters are chosen so that their widths vary a lot
 * between the fonts in the default stacks. We want each fallback stack
 * to always start out at a different width than the other.
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_TEST_STRING = 'BESs';

/**
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_VARIATION = 'n4';

/**
 * Watches a set of font families.
 * @param {Array.<string>} fontFamilies The font family names to watch.
 * @param {Object.<string, Array.<string>>} fontDescriptions The font variations
 *     of each family to watch. Described with FVD.
 * @param {Object.<string, string>} fontTestStrings The font test strings for
 *     each family.
 * @param {boolean} last True if this is the last set of families to watch.
 */
webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
    fontTestStrings, last) {
  var length = fontFamilies.length;

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    if (!fontDescriptions[fontFamily]) {
      fontDescriptions[fontFamily] = [webfont.FontWatcher.DEFAULT_VARIATION];
    }
    this.currentlyWatched_ += fontDescriptions[fontFamily].length;
  }

  if (last) {
    this.last_ = last;
  }

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    var descriptions = fontDescriptions[fontFamily];
    var fontTestString  = fontTestStrings[fontFamily] || webfont.FontWatcher.DEFAULT_TEST_STRING;

    for (var j = 0, len = descriptions.length; j < len; j++) {
      var fontDescription = descriptions[j];

      var defaultFontsA = webfont.FontWatcher.DEFAULT_FONTS_A;
      var originalSizeA = this.getDefaultFontSize_(defaultFontsA,
          fontDescription, fontTestString);

      var defaultFontsB = webfont.FontWatcher.DEFAULT_FONTS_B;
      var originalSizeB = this.getDefaultFontSize_(defaultFontsB,
          fontDescription, fontTestString);

      this.watch_(fontFamily, fontDescription, fontTestString,
          originalSizeA, defaultFontsA, originalSizeB, defaultFontsB);
    }
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.watch_ = function(fontFamily, fontDescription,
    fontTestString, originalSizeA, defaultFontsA, originalSizeB, defaultFontsB) {
  this.eventDispatcher_.dispatchFontLoading(fontFamily, fontDescription);
  var requestedFontA = this.createHiddenElementWithFont_(this.nameHelper_.quote(fontFamily),
      defaultFontsA, fontDescription, fontTestString);
  var sizeA = this.fontSizer_.getWidth(requestedFontA);

  var requestedFontB = this.createHiddenElementWithFont_(this.nameHelper_.quote(fontFamily),
      defaultFontsB, fontDescription, fontTestString);
  var sizeB = this.fontSizer_.getWidth(requestedFontB);

  if (originalSizeA != sizeA || originalSizeB != sizeB) {
    this.domHelper_.removeElement(requestedFontA);
    this.domHelper_.removeElement(requestedFontB);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else {
    this.asyncCheck_(this.getTime_(), fontFamily, fontDescription,
        originalSizeA, requestedFontA, originalSizeB, requestedFontB);
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
  if (--this.currentlyWatched_ == 0 && this.last_) {
    if (this.success_) {
      this.eventDispatcher_.dispatchActive();
    } else {
      this.eventDispatcher_.dispatchInactive();
    }
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.check_ = function(started, fontFamily,
    fontDescription, originalSizeA, requestedFontA, originalSizeB, requestedFontB) {
  var sizeA = this.fontSizer_.getWidth(requestedFontA);
  var sizeB = this.fontSizer_.getWidth(requestedFontB);

  if (originalSizeA != sizeA || originalSizeB != sizeB) {
    this.domHelper_.removeElement(requestedFontA);
    this.domHelper_.removeElement(requestedFontB);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else if ((this.getTime_() - started) < 5000) {
    this.asyncCheck_(started, fontFamily, fontDescription, originalSizeA,
        requestedFontA, originalSizeB, requestedFontB);
  } else {
    this.domHelper_.removeElement(requestedFontA);
    this.domHelper_.removeElement(requestedFontB);
    this.eventDispatcher_.dispatchFontInactive(fontFamily, fontDescription);
    this.decreaseCurrentlyWatched_();
  }
};

/**
 * @private
 */
webfont.FontWatcher.prototype.asyncCheck_ = function(started, fontFamily,
    fontDescription, originalSizeA, requestedFontA, originalSizeB, requestedFontB) {
  this.asyncCall_(function(context, func) {
    return function() {
      func.call(context, started, fontFamily, fontDescription, originalSizeA,
          requestedFontA, originalSizeB, requestedFontB);
    }
  }(this, this.check_), 50);
};

/**
 * @private
 */
webfont.FontWatcher.prototype.getDefaultFontSize_ = function(defaultFonts,
    fontDescription, fontTestString) {
  var defaultFont = this.createHiddenElementWithFont_(
      null, defaultFonts, fontDescription, fontTestString);
  var size = this.fontSizer_.getWidth(defaultFont);

  this.domHelper_.removeElement(defaultFont);
  return size;
};

/**
 * @private
 */
webfont.FontWatcher.prototype.createHiddenElementWithFont_ = function(
    fontFamily, defaultFonts, fontDescription, fontTestString) {
  fontFamily = fontFamily ? fontFamily + "," : "";
  var variationCss = this.fvd_.expand(fontDescription);
  var styleString = "position:absolute;top:-999px;font-size:300px;font-family:" +
      fontFamily + defaultFonts + ";" + variationCss;
  var span = this.domHelper_.createElement('span', { 'style': styleString },
      fontTestString);

  this.domHelper_.insertInto('body', span);
  return span;
};
<|endoftext|>


<commit_before>webfont.FontWatcher = function(domHelper, eventDispatcher, fontSizer,
    asyncCall, getTime) {
  this.domHelper_ = domHelper;
  this.eventDispatcher_ = eventDispatcher;
  this.fontSizer_ = fontSizer;
  this.asyncCall_ = asyncCall;
  this.getTime_ = getTime;
  this.currentlyWatched_ = 0;
  this.last_ = false;
  this.success_ = false;
  this.nameHelper_ = new webfont.CssFontFamilyName();
  this.fvd_ = new webfont.FontVariationDescription();
};

webfont.FontWatcher.DEFAULT_FONT = '_,arial,helvetica';
webfont.FontWatcher.DEFAULT_VARIATION = 'n4';

webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions, last) {
  var length = fontFamilies.length;

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    if (!fontDescriptions[fontFamily]) {
      fontDescriptions[fontFamily] = [webfont.FontWatcher.DEFAULT_VARIATION];
    }
    this.currentlyWatched_ += fontDescriptions[fontFamily].length;
  }

  if (last) {
    this.last_ = last;
  }

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    var descriptions = fontDescriptions[fontFamily];

    for (var j = 0, len = descriptions.length; j < len; j++) {
      var fontDescription = descriptions[j];
      var originalSize = this.getDefaultFontSize_(fontDescription);

      this.watch_(fontFamily, fontDescription, originalSize);
    }
  }
};

webfont.FontWatcher.prototype.watch_ = function(fontFamily, fontDescription, originalSize) {
  this.eventDispatcher_.dispatchFontLoading(fontFamily, fontDescription);
  var requestedFont = this.createHiddenElementWithFont_(this.nameHelper_.quote(fontFamily),
      fontDescription);
  var size = this.fontSizer_.getWidth(requestedFont);

  if (originalSize != size) {
    this.domHelper_.removeElement(requestedFont);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else {
    this.asyncCheck_(this.getTime_(), originalSize, requestedFont,
        fontFamily, fontDescription);
  }
};

webfont.FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
  if (--this.currentlyWatched_ == 0 && this.last_) {
    if (this.success_) {
      this.eventDispatcher_.dispatchActive();
    } else {
      this.eventDispatcher_.dispatchInactive();
    }
  }
};

webfont.FontWatcher.prototype.check_ = function(started, originalSize,
    requestedFont, fontFamily, fontDescription) {
  var size = this.fontSizer_.getWidth(requestedFont);

  if (originalSize != size) {
    this.domHelper_.removeElement(requestedFont);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else if ((this.getTime_() - started) < 5000) {
    this.asyncCheck_(started, originalSize, requestedFont, fontFamily, fontDescription);
  } else {
    this.domHelper_.removeElement(requestedFont);
    this.eventDispatcher_.dispatchFontInactive(fontFamily, fontDescription);
    this.decreaseCurrentlyWatched_();
  }
};

webfont.FontWatcher.prototype.asyncCheck_ = function(started, originalSize,
    requestedFont, fontFamily, fontDescription) {
  this.asyncCall_(function(context, func) {
    return function() {
      func.call(context, started, originalSize, requestedFont, fontFamily, fontDescription);
    }
  }(this, this.check_), 50);
};

webfont.FontWatcher.prototype.getDefaultFontSize_ = function(fontDescription) {
  var defaultFont = this.createHiddenElementWithFont_(
      webfont.FontWatcher.DEFAULT_FONT, fontDescription);
  var size = this.fontSizer_.getWidth(defaultFont);

  this.domHelper_.removeElement(defaultFont);
  return size;
};

webfont.FontWatcher.prototype.createHiddenElementWithFont_ = function(
    fontFamily, fontDescription) {
  var variationCss = this.fvd_.expand(fontDescription);
  var styleString = "position:absolute;top:-999px;font-size:300px;font-family:" +
      fontFamily +  ";" + variationCss;
  var span = this.domHelper_.createElement('span', { 'style': styleString }, 'Mm');

  this.domHelper_.insertInto('body', span);
  return span;
};
<commit_msg>Adds back the default font in the stack as it seems that it did not have the expected effect on chrome, and could potentially be a problem for IE.<commit_after>webfont.FontWatcher = function(domHelper, eventDispatcher, fontSizer,
    asyncCall, getTime) {
  this.domHelper_ = domHelper;
  this.eventDispatcher_ = eventDispatcher;
  this.fontSizer_ = fontSizer;
  this.asyncCall_ = asyncCall;
  this.getTime_ = getTime;
  this.currentlyWatched_ = 0;
  this.last_ = false;
  this.success_ = false;
  this.nameHelper_ = new webfont.CssFontFamilyName();
  this.fvd_ = new webfont.FontVariationDescription();
};

webfont.FontWatcher.DEFAULT_FONT = '_,arial,helvetica';
webfont.FontWatcher.DEFAULT_VARIATION = 'n4';

webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions, last) {
  var length = fontFamilies.length;

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    if (!fontDescriptions[fontFamily]) {
      fontDescriptions[fontFamily] = [webfont.FontWatcher.DEFAULT_VARIATION];
    }
    this.currentlyWatched_ += fontDescriptions[fontFamily].length;
  }

  if (last) {
    this.last_ = last;
  }

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    var descriptions = fontDescriptions[fontFamily];

    for (var j = 0, len = descriptions.length; j < len; j++) {
      var fontDescription = descriptions[j];
      var originalSize = this.getDefaultFontSize_(fontDescription);

      this.watch_(fontFamily, fontDescription, originalSize);
    }
  }
};

webfont.FontWatcher.prototype.watch_ = function(fontFamily, fontDescription, originalSize) {
  this.eventDispatcher_.dispatchFontLoading(fontFamily, fontDescription);
  var requestedFont = this.createHiddenElementWithFont_(this.nameHelper_.quote(fontFamily),
      fontDescription);
  var size = this.fontSizer_.getWidth(requestedFont);

  if (originalSize != size) {
    this.domHelper_.removeElement(requestedFont);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else {
    this.asyncCheck_(this.getTime_(), originalSize, requestedFont,
        fontFamily, fontDescription);
  }
};

webfont.FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
  if (--this.currentlyWatched_ == 0 && this.last_) {
    if (this.success_) {
      this.eventDispatcher_.dispatchActive();
    } else {
      this.eventDispatcher_.dispatchInactive();
    }
  }
};

webfont.FontWatcher.prototype.check_ = function(started, originalSize,
    requestedFont, fontFamily, fontDescription) {
  var size = this.fontSizer_.getWidth(requestedFont);

  if (originalSize != size) {
    this.domHelper_.removeElement(requestedFont);
    this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
    this.success_ = true;
    this.decreaseCurrentlyWatched_();
  } else if ((this.getTime_() - started) < 5000) {
    this.asyncCheck_(started, originalSize, requestedFont, fontFamily, fontDescription);
  } else {
    this.domHelper_.removeElement(requestedFont);
    this.eventDispatcher_.dispatchFontInactive(fontFamily, fontDescription);
    this.decreaseCurrentlyWatched_();
  }
};

webfont.FontWatcher.prototype.asyncCheck_ = function(started, originalSize,
    requestedFont, fontFamily, fontDescription) {
  this.asyncCall_(function(context, func) {
    return function() {
      func.call(context, started, originalSize, requestedFont, fontFamily, fontDescription);
    }
  }(this, this.check_), 50);
};

webfont.FontWatcher.prototype.getDefaultFontSize_ = function(fontDescription) {
  var defaultFont = this.createHiddenElementWithFont_(
      webfont.FontWatcher.DEFAULT_FONT, fontDescription);
  var size = this.fontSizer_.getWidth(defaultFont);

  this.domHelper_.removeElement(defaultFont);
  return size;
};

webfont.FontWatcher.prototype.createHiddenElementWithFont_ = function(
    fontFamily, fontDescription) {
  var variationCss = this.fvd_.expand(fontDescription);
  var styleString = "position:absolute;top:-999px;font-size:300px;font-family:" +
      fontFamily + "," + webfont.FontWatcher.DEFAULT_FONT + ";" + variationCss;
  var span = this.domHelper_.createElement('span', { 'style': styleString }, 'Mm');

  this.domHelper_.insertInto('body', span);
  return span;
};
<|endoftext|>