<commit_before>import React, { Component,  PropTypes } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Image,
  TextInput,
  Text,
  TouchableWithoutFeedback,
  View
} from 'react-native';

import Flags from './resources/flags'
import PhoneNumber from './phoneNumber'
import styles from './styles';
import CountryPicker from './countryPicker'

export default class PhoneInput extends Component {

    constructor(props, context){
        super(props, context)

        this.onChangePhoneNumber = this.onChangePhoneNumber.bind(this)
        this.onPressFlag = this.onPressFlag.bind(this)
        this.selectCountry = this.selectCountry.bind(this)
        this.getFlag = this.getFlag.bind(this)

        var iso2 = this.props.initialCountry || 'us'
        var countryData = PhoneNumber.getCountryDataByCode(iso2)
        this.state = {
            iso2,
            formattedNumber: countryData?`+${countryData.dialCode}`:'',
            value: null
        }
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.value != this.state.value){
            this.setState({value: nextProps.value})
            this.updateFlagAndFormatNumber(nextProps.value)
        }
    }

    componentWillMount(){
        if(this.props.value)
            this.updateFlagAndFormatNumber(this.props.value)
    }

    updateFlagAndFormatNumber(number, actionAfterSetState = null){
        var iso2 = this.props.initialCountry || 'us'
        var phoneNumber = number
        if(number){
            if(phoneNumber[0] != '+')
                phoneNumber = `+${phoneNumber}`
            iso2 = PhoneNumber.getCountryCodeOfNumber(phoneNumber)
        }
        this.setState({iso2, formattedNumber: phoneNumber}, actionAfterSetState)
    }

    onChangePhoneNumber(number){
        var actionAfterSetState = this.props.onChangePhoneNumber?() => {this.props.onChangePhoneNumber(number)}:null
        this.updateFlagAndFormatNumber(number, actionAfterSetState)
    }

    isValidNumber(){
        return PhoneNumber.isValidNumber(this.state.formattedNumber, this.state.iso2)
    }

    getNumberType(){
        return PhoneNumber.getNumberType(this.state.formattedNumber, this.state.iso2)
    }

    getValue(){
        return this.state.formattedNumber
    }

    getFlag(iso2){
        return Flags.get(iso2)
    }

    getAllCountries(){
        return PhoneNumber.getAllCountries()
    }

    getPickerData(){
        return PhoneNumber.getAllCountries().map((country,index) => {return {
            key: index,
            image: Flags.get(country.iso2),
            label: country.name,
            dialCode: `+${country.dialCode}`,
            iso2: country.iso2
        }})
    }

    selectCountry(iso2){
        if(this.state.iso2 != iso2){
            var countryData = PhoneNumber.getCountryDataByCode(iso2)
            if(countryData){
                this.setState({
                    iso2,
                    formattedNumber: `+${countryData.dialCode}`
                }, ()=>{
                    if(this.props.onSelectCountry)
                        this.props.onSelectCountry(iso2)
                })
            }
        }
    }

    onPressFlag(){
        if(this.props.onPressFlag)
            this.props.onPressFlag()
        else{
            if(this.state.iso2)
                this.refs.picker.selectCountry(this.state.iso2)
            this.refs.picker.show()
        }
    }

    render(){
        const TextComponent = this.props.textComponent || TextInput;
        return (
            <View style={[styles.container, this.props.style]}>
                <TouchableWithoutFeedback onPress={this.onPressFlag}>
                    <Image source={Flags.get(this.state.iso2)}
                        resizeMode='stretch'
                        style={[styles.flag, this.props.flagStyle]}
                        onPress={this.onPressFlag}
                    />
                </TouchableWithoutFeedback>
                <View style={{flex:1, marginLeft:this.props.offset || 10}}>
                    <TextComponent
                            autoCorrect={false}
                            style={[styles.text, this.props.textStyle]}
                            onChangeText={(text) => this.onChangePhoneNumber(text)}
                            keyboardType='phone-pad'
                            underlineColorAndroid='rgba(0,0,0,0)'
                            value={this.state.formattedNumber}
                            {...this.props.textProps}
                        />
                </View>

                <CountryPicker
                    ref={'picker'}
                    selectedCountry={this.state.iso2}
                    onSubmit={this.selectCountry}
                    buttonColor={this.props.pickerButtonColor}
                    buttonTextStyle={this.props.pickerButtonTextStyle}
                    itemStyle={this.props.itemStyle}
                    cancelText={this.props.cancelText}
                    cancelTextStyle={this.props.cancelTextStyle}
                    confirmText={this.props.confirmText}
                    confirmTextStyle={this.props.confirmTextStyle}
                    pickerBackgroundColor={this.props.pickerBackgroundColor}
                    itemStyle={this.props.pickerItemStyle}
                />
            </View>
        )
    }
}

PhoneInput.propTypes = {
  textComponent: PropTypes.func,
  initialCountry: PropTypes.string,
  onChangePhoneNumber: PropTypes.func,
  value: PropTypes.string,
  style: PropTypes.object,
  flagStyle: PropTypes.object,
  textStyle: PropTypes.object,
  offset: PropTypes.number,
  textProps: PropTypes.object,
  onSelectCountry: PropTypes.func,
  pickerButtonColor: PropTypes.string,
  pickerBackgroundColor: PropTypes.string,
  pickerItemStyle: PropTypes.object,
  cancelText: PropTypes.string,
  cancelTextStyle: PropTypes.object,
  confirmText: PropTypes.string,
  confirmTextTextStyle: PropTypes.object,
}
<commit_msg>Remove 0 after country code.<commit_after>import React, { Component,  PropTypes } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Image,
  TextInput,
  Text,
  TouchableWithoutFeedback,
  View
} from 'react-native';

import Flags from './resources/flags'
import PhoneNumber from './phoneNumber'
import styles from './styles';
import CountryPicker from './countryPicker'

export default class PhoneInput extends Component {

    constructor(props, context){
        super(props, context)

        this.onChangePhoneNumber = this.onChangePhoneNumber.bind(this)
        this.onPressFlag = this.onPressFlag.bind(this)
        this.selectCountry = this.selectCountry.bind(this)
        this.getFlag = this.getFlag.bind(this)

        var iso2 = this.props.initialCountry || 'us'
        var countryData = PhoneNumber.getCountryDataByCode(iso2)
        this.state = {
            iso2,
            formattedNumber: countryData?`+${countryData.dialCode}`:'',
            value: null
        }
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.value != this.state.value){
            this.setState({value: nextProps.value})
            this.updateFlagAndFormatNumber(nextProps.value)
        }
    }

    componentWillMount(){
        if(this.props.value)
            this.updateFlagAndFormatNumber(this.props.value)
    }

    possiblyEliminateZeroAfterCountryCode(number) {
        const number = this.getValue();
        const dialCode = PhoneNumber.getDialCode(number);
        return number.startsWith(dialCode + 0)
          ? dialCode + number.substr(dialCode.length + 1)
          : number;
    }

    updateFlagAndFormatNumber(number, actionAfterSetState = null){
        var iso2 = this.props.initialCountry || 'us'
        var phoneNumber = number
        if(number){
            if(phoneNumber[0] != '+')
                phoneNumber = `+${phoneNumber}`
            phoneNumber = this.possiblyEliminateZeroAfterCountryCode(phoneNumber);
            iso2 = PhoneNumber.getCountryCodeOfNumber(phoneNumber)
        }
        this.setState({iso2, formattedNumber: phoneNumber}, actionAfterSetState)
    }

    onChangePhoneNumber(number){
        var actionAfterSetState = this.props.onChangePhoneNumber?() => {this.props.onChangePhoneNumber(number)}:null
        this.updateFlagAndFormatNumber(number, actionAfterSetState)
    }

    isValidNumber(){
        return PhoneNumber.isValidNumber(this.state.formattedNumber, this.state.iso2)
    }

    getNumberType(){
        return PhoneNumber.getNumberType(this.state.formattedNumber, this.state.iso2)
    }

    getValue(){
        return this.state.formattedNumber
    }

    getFlag(iso2){
        return Flags.get(iso2)
    }

    getAllCountries(){
        return PhoneNumber.getAllCountries()
    }

    getPickerData(){
        return PhoneNumber.getAllCountries().map((country,index) => {return {
            key: index,
            image: Flags.get(country.iso2),
            label: country.name,
            dialCode: `+${country.dialCode}`,
            iso2: country.iso2
        }})
    }

    selectCountry(iso2){
        if(this.state.iso2 != iso2){
            var countryData = PhoneNumber.getCountryDataByCode(iso2)
            if(countryData){
                this.setState({
                    iso2,
                    formattedNumber: `+${countryData.dialCode}`
                }, ()=>{
                    if(this.props.onSelectCountry)
                        this.props.onSelectCountry(iso2)
                })
            }
        }
    }

    onPressFlag(){
        if(this.props.onPressFlag)
            this.props.onPressFlag()
        else{
            if(this.state.iso2)
                this.refs.picker.selectCountry(this.state.iso2)
            this.refs.picker.show()
        }
    }

    render(){
        const TextComponent = this.props.textComponent || TextInput;
        return (
            <View style={[styles.container, this.props.style]}>
                <TouchableWithoutFeedback onPress={this.onPressFlag}>
                    <Image source={Flags.get(this.state.iso2)}
                        resizeMode='stretch'
                        style={[styles.flag, this.props.flagStyle]}
                        onPress={this.onPressFlag}
                    />
                </TouchableWithoutFeedback>
                <View style={{flex:1, marginLeft:this.props.offset || 10}}>
                    <TextComponent
                            autoCorrect={false}
                            style={[styles.text, this.props.textStyle]}
                            onChangeText={(text) => this.onChangePhoneNumber(text)}
                            keyboardType='phone-pad'
                            underlineColorAndroid='rgba(0,0,0,0)'
                            value={this.state.formattedNumber}
                            {...this.props.textProps}
                        />
                </View>

                <CountryPicker
                    ref={'picker'}
                    selectedCountry={this.state.iso2}
                    onSubmit={this.selectCountry}
                    buttonColor={this.props.pickerButtonColor}
                    buttonTextStyle={this.props.pickerButtonTextStyle}
                    itemStyle={this.props.itemStyle}
                    cancelText={this.props.cancelText}
                    cancelTextStyle={this.props.cancelTextStyle}
                    confirmText={this.props.confirmText}
                    confirmTextStyle={this.props.confirmTextStyle}
                    pickerBackgroundColor={this.props.pickerBackgroundColor}
                    itemStyle={this.props.pickerItemStyle}
                />
            </View>
        )
    }
}

PhoneInput.propTypes = {
  textComponent: PropTypes.func,
  initialCountry: PropTypes.string,
  onChangePhoneNumber: PropTypes.func,
  value: PropTypes.string,
  style: PropTypes.object,
  flagStyle: PropTypes.object,
  textStyle: PropTypes.object,
  offset: PropTypes.number,
  textProps: PropTypes.object,
  onSelectCountry: PropTypes.func,
  pickerButtonColor: PropTypes.string,
  pickerBackgroundColor: PropTypes.string,
  pickerItemStyle: PropTypes.object,
  cancelText: PropTypes.string,
  cancelTextStyle: PropTypes.object,
  confirmText: PropTypes.string,
  confirmTextTextStyle: PropTypes.object,
}
<|endoftext|>


<commit_before>'use babel';
'use strict';

const {EventEmitter}     = require ('events');
const {Client}           = require ('_debugger');
const {spawn}            = require ('child_process');
const watt               = require ('watt');
const escapeStringRegexp = require ('escape-string-regexp');
const path               = require ('path');
const fs                 = require ('fs');
const tmp                = require ('tmp');

const Extractor = require ('./extractor.js');

class Wannabe extends EventEmitter {
  /**
   * Construct the Wannabe object.
   *
   * @param {string} script - Test script.
   */
  constructor (script) {
    super ();

    this._frames  = {};
    this._script  = script;
    this._client  = new Client ();
    this._mocha   = {
      kill: () => {}
    };

    watt.wrapAll (this);
  }

  /**
   * Spawn mocha with the node debugger.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  _spawn (pattern, next) {
    const regex = pattern instanceof RegExp ?
                  pattern.toString ().replace (/^\/(.*)\/.?$/, '$1') :
                  escapeStringRegexp (pattern);

    this._mocha = spawn ('node', [
      '--debug',
      '--debug-brk',
      path.join (__dirname, './mocha.js'),
      this._script,
      regex
    ], {
      detached: false,
      stdio: ['ignore', 'ignore', 'pipe']
    });

    this._mocha.unref ();

    this._mocha.stderr.once ('data', () => next ());

    this._mocha.on ('close', () => {});
    this._mocha.on ('error', (err) => {
      console.error (err);
    });
  }

  /**
   * Inspect the frame on the current breakpoint.
   *
   * It populates this._frames with the local variables.
   * The key of this._frames is the line number.
   *
   * NOTE: setBreakpoint is slow!
   */
  * _inspect (next) {
    const {frames} = yield this._client.reqBacktrace (next);
    const line = `${frames[0].line}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    const frame = {
      locals: [],
      arguments: [],
      returnValue: null
    };

    frames[0].locals.forEach ((local) => {
      /* Local variables */
      frame.locals.push ({
        name:  local.name,
        type:  local.value.type,
        value: local.value.value || local.value.className
      });
    });

    frames[0].arguments.forEach ((argument) => {
      /* Arguments */
      frame.arguments.push ({
        name:  argument.name,
        type:  argument.value.type,
        value: argument.value.value
      });
    });

    /* Return value */
    if (frames[0].returnValue && frames[0].returnValue.type !== 'undefined') {
      frame.returnValue = {
        type: frames[0].returnValue.type,
        value: frames[0].returnValue.value
      };
    }

    this._frames[line].push (frame);
  }

  _exception (ex, next) {
    const line = `${ex.body.sourceLine + 1}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    this._frames[line].push ({
      exception: {
        type: ex.body.exception.type,
        text: ex.body.exception.text
      }
    });

    next ();
  }

  /**
   * Initialize the client connection.
   *
   * It waits on the first breakpoint because the child is executed with
   * the --debug-brk argument.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  * init (pattern, next) {
    yield this._spawn (pattern, next);

    let first = true;
    this._client.on ('break', () => {
      if (first) {
        this.emit ('ready');
        first = false;
        return;
      }

      this._inspect (() => this._client.reqContinue (() => {}));
    });

    this._client.on ('exception', (ex) => {
      this._exception (ex, () => this._client.reqContinue (() => {}));
    });

    this._client.once ('connect', next.parallel ());
    this._client.connect (5858);
    yield next.sync ();

    yield this.once ('ready', next); // wait for the first breakpoint (--debug-brk)
    yield this._client.reqSetExceptionBreak ('all', next);
  }

  /**
   * Set one or more breakpoints according to the current script.
   *
   * @param {number[]} lines - Lines where put the breakpoints.
   */
  * setBreakpoints (lines, next) {
    for (const line of lines) {
      this._client.setBreakpoint ({
        type:   'script',
        target: this._script,
        line:   line
      }, next.parallel ());
    }

    yield next.sync ();
  }

  /**
   * Run Wannabe.
   *
   * When a new breakpoint is reached, the continue request is automatically
   * sent until that the child is terminated.
   *
   * @returns all frames.
   */
  * run (next) {
    const promise = new Promise ((resolve) => {
      this._client.once ('error', (err) => {
        resolve (this._frames);
      });

      this._client.once ('close', () => {
        resolve (this._frames);
      });
    });

    yield this._client.reqContinue (next);
    return yield promise;
  }

  dispose () {
    this._mocha.kill ('SIGKILL');
  }
}

const extract = watt (function * (script, data, extractor, next) {
  let file = null;
  if (data) {
    /* We must create a new script in the same location that {script}.
     * The reason is that we can not pass a buffer to mocha; only files
     * can be passed to the runner.
     */
    file = yield tmp.tmpName ({
      template: path.join (script, '../.wannabe-XXXXXX')
    }, next);
    yield fs.writeFile (file, data, next);
  } else {
    file = script;
  }

  let _exThrown = false;
  let frames = null;
  const wannabe = new Wannabe (file);

  try {
    const {lines, pattern} = extractor (file);
    if (!lines.length) {
      return {};
    }

    yield wannabe.init (pattern);

    yield wannabe.setBreakpoints (lines);
    frames = yield wannabe.run ();
  } catch (ex) {
    _exThrown = true;
    throw ex;
  } finally {
    wannabe.dispose ();
    if (data) {
      yield fs.unlink (file, next);
    }
    // FIXME: see watt bug: https://github.com/mappum/watt/issues/16
    if (!_exThrown) {
      return frames;
    }
  }
});

exports.byPattern = watt (function * (script, data, funcName, pattern, next) {
  return yield extract (script, data, (file) => {
    const lines = data ?
      Extractor.byFuncInData (data, funcName, pattern) :
      Extractor.byFuncInFile (file, funcName, pattern);
    return {
      lines,
      pattern
    }
  }, next);
});

exports.byLine = watt (function * (script, data, funcName, line, next) {
  return yield extract (script, data, (file) => {
    return data ?
      Extractor.byLineInData (data, funcName, line) :
      Extractor.byLineInFile (file, funcName, line);
  }, next);
});
<commit_msg>Cosmetic<commit_after>'use babel';
'use strict';

const {EventEmitter}     = require ('events');
const {Client}           = require ('_debugger');
const {spawn}            = require ('child_process');
const watt               = require ('watt');
const escapeStringRegexp = require ('escape-string-regexp');
const path               = require ('path');
const fs                 = require ('fs');
const tmp                = require ('tmp');

const Extractor = require ('./extractor.js');

class Wannabe extends EventEmitter {
  /**
   * Construct the Wannabe object.
   *
   * @param {string} script - Test script.
   */
  constructor (script) {
    super ();

    this._frames = {};
    this._script = script;
    this._client = new Client ();
    this._mocha  = {
      kill: () => {}
    };

    watt.wrapAll (this);
  }

  /**
   * Spawn mocha with the node debugger.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  _spawn (pattern, next) {
    const regex = pattern instanceof RegExp ?
                  pattern.toString ().replace (/^\/(.*)\/.?$/, '$1') :
                  escapeStringRegexp (pattern);

    this._mocha = spawn ('node', [
      '--debug',
      '--debug-brk',
      path.join (__dirname, './mocha.js'),
      this._script,
      regex
    ], {
      detached: false,
      stdio: ['ignore', 'ignore', 'pipe']
    });

    this._mocha.unref ();

    this._mocha.stderr.once ('data', () => next ());

    this._mocha.on ('close', () => {});
    this._mocha.on ('error', (err) => {
      console.error (err);
    });
  }

  /**
   * Inspect the frame on the current breakpoint.
   *
   * It populates this._frames with the local variables.
   * The key of this._frames is the line number.
   *
   * NOTE: setBreakpoint is slow!
   */
  * _inspect (next) {
    const {frames} = yield this._client.reqBacktrace (next);
    const line = `${frames[0].line}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    const frame = {
      locals: [],
      arguments: [],
      returnValue: null
    };

    frames[0].locals.forEach ((local) => {
      /* Local variables */
      frame.locals.push ({
        name:  local.name,
        type:  local.value.type,
        value: local.value.value || local.value.className
      });
    });

    frames[0].arguments.forEach ((argument) => {
      /* Arguments */
      frame.arguments.push ({
        name:  argument.name,
        type:  argument.value.type,
        value: argument.value.value
      });
    });

    /* Return value */
    if (frames[0].returnValue && frames[0].returnValue.type !== 'undefined') {
      frame.returnValue = {
        type: frames[0].returnValue.type,
        value: frames[0].returnValue.value
      };
    }

    this._frames[line].push (frame);
  }

  _exception (ex, next) {
    const line = `${ex.body.sourceLine + 1}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    this._frames[line].push ({
      exception: {
        type: ex.body.exception.type,
        text: ex.body.exception.text
      }
    });

    next ();
  }

  /**
   * Initialize the client connection.
   *
   * It waits on the first breakpoint because the child is executed with
   * the --debug-brk argument.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  * init (pattern, next) {
    yield this._spawn (pattern, next);

    let first = true;
    this._client.on ('break', () => {
      if (first) {
        this.emit ('ready');
        first = false;
        return;
      }

      this._inspect (() => this._client.reqContinue (() => {}));
    });

    this._client.on ('exception', (ex) => {
      this._exception (ex, () => this._client.reqContinue (() => {}));
    });

    this._client.once ('connect', next.parallel ());
    this._client.connect (5858);
    yield next.sync ();

    yield this.once ('ready', next); // wait for the first breakpoint (--debug-brk)
    yield this._client.reqSetExceptionBreak ('all', next);
  }

  /**
   * Set one or more breakpoints according to the current script.
   *
   * @param {number[]} lines - Lines where put the breakpoints.
   */
  * setBreakpoints (lines, next) {
    for (const line of lines) {
      this._client.setBreakpoint ({
        type:   'script',
        target: this._script,
        line:   line
      }, next.parallel ());
    }

    yield next.sync ();
  }

  /**
   * Run Wannabe.
   *
   * When a new breakpoint is reached, the continue request is automatically
   * sent until that the child is terminated.
   *
   * @returns all frames.
   */
  * run (next) {
    const promise = new Promise ((resolve) => {
      this._client.once ('error', (err) => {
        resolve (this._frames);
      });

      this._client.once ('close', () => {
        resolve (this._frames);
      });
    });

    yield this._client.reqContinue (next);
    return yield promise;
  }

  dispose () {
    this._mocha.kill ('SIGKILL');
  }
}

const extract = watt (function * (script, data, extractor, next) {
  let file = null;
  if (data) {
    /* We must create a new script in the same location that {script}.
     * The reason is that we can not pass a buffer to mocha; only files
     * can be passed to the runner.
     */
    file = yield tmp.tmpName ({
      template: path.join (script, '../.wannabe-XXXXXX')
    }, next);
    yield fs.writeFile (file, data, next);
  } else {
    file = script;
  }

  let _exThrown = false;
  let frames = null;
  const wannabe = new Wannabe (file);

  try {
    const {lines, pattern} = extractor (file);
    if (!lines.length) {
      return {};
    }

    yield wannabe.init (pattern);

    yield wannabe.setBreakpoints (lines);
    frames = yield wannabe.run ();
  } catch (ex) {
    _exThrown = true;
    throw ex;
  } finally {
    wannabe.dispose ();
    if (data) {
      yield fs.unlink (file, next);
    }
    // FIXME: see watt bug: https://github.com/mappum/watt/issues/16
    if (!_exThrown) {
      return frames;
    }
  }
});

exports.byPattern = watt (function * (script, data, funcName, pattern, next) {
  return yield extract (script, data, (file) => {
    const lines = data ?
      Extractor.byFuncInData (data, funcName, pattern) :
      Extractor.byFuncInFile (file, funcName, pattern);
    return {
      lines,
      pattern
    }
  }, next);
});

exports.byLine = watt (function * (script, data, funcName, line, next) {
  return yield extract (script, data, (file) => {
    return data ?
      Extractor.byLineInData (data, funcName, line) :
      Extractor.byLineInFile (file, funcName, line);
  }, next);
});
<|endoftext|>


<commit_before>var _ = require('lodash');
var async = require('async');
var defaultValidations = require('./defaultValidations');
var Model = require('racer/lib/Model');

module.exports = Validator;

/**
 * Creates a validator object and initiates it
 * @param {Model} model - A scoped model to the location on where to put the validator's data
 * @param {Model|string|Object} [origin|defaultObject] - Either a scoped model to the location where the original data is stored. Or, a path (as string) to the location where to find the original data. If not passed, the fields parameter is mandatory. Can also be passed in as a defaultObject. If passed in as default objet, the fields parameter is also mandatory.
 * @param {Collection.<string, Object>} [fields] - A collection of fields to use. If not passed, the origin parameter is mandatory. The key for each object is the field name, and the value is the field object (specified below).
   * @param {*} [fields.<fieldName>.default] - A default value for this specific field.
   * @param {Object[]} [fields.<fieldName>.validations] - A list of validations to apply.
     * @param {String|Function|RegEx} [fields.<fieldName>.validations.<index>.rule] - The rule to use when validating this rule. Either, this can be a string matching the name of a default rule (specified in default checks or passed in as an option), OR a function which will get run with the value to be validated as input, which should return true|false reflecting the validity, OR a RegEx, which will get run similar to if you pass in a function.
     * @param {String} [fields.<fieldName>.validations.<index>.message] - The error message to show if validation fails
 * @param {Object} [options] - An options object
   * @param {Collection.<string, Function|RegEx>} [options.rules] - A collection of default rules to add to this instance. Each key is the name of the rule. The value is the rule, either a function or regular expression similar to field specific functions/regular expressions passed in the fields parameter
 * @returns {Validator} - Instance of Validator
 */
function Validator(model, origin, fields, options) {
  // Parse out arguments
  var self = this;
  var args = Array.prototype.slice.call(arguments);

  if(!args.length) throw new Error('You must specify a model!');
  if(args.length == 1) throw new Error('You must either specify an origin or a fields object');

  this.model = args.shift();
  var arg = args.shift();

  // Origin cases
  if(arg instanceof Model) {
    this.origin = arg;

    arg = args.shift();
  } else if(typeof arg === 'string') {
    this.origin = this.model.scope(arg);

    arg = args.shift();
  }

  // If only a collection was passed, assume we want to add to a new doc
  if(this.origin) {
    var segments = this.origin._splitPath();

    if(segments.length === 1) {
      // Generate a new id
      this.origin = this.origin.at(model.id());
    }
  }

  if(arg) {
    // Case where we've passed in a default object
    if(!Array.isArray(arg) && typeof args[0] !== 'undefined') {
      var defaultObject = arg;

      arg = args.shift();
    }

    // Fields argument
    if(Array.isArray(arg)) {
      // Support passing in an array of fields
      this.fields = _.zipObject(arg, _.fill(Array(arg.length), {}));
    } else {
      this.fields = arg;
    }

    if(defaultObject) {
      _.each(defaultObject, function (val, key) {
        if(!self.fields[key]) self.fields[key] = {};

        self.fields[key]['default'] = val;
      });
    }
  }

  this.options = _.assign({
    rules: {},
    messages: {}
  }, args.shift());

  // Setup
  this._setup();
}

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.getValues = function () {
  var values = {};
  _.each(this.model.get(), function (field, fieldName) {
    values[fieldName] = field.value;
  });
  return values;
};

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.validate = function (fieldname, callback) {
  this._validate(fieldname, callback);
};

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.validateAll = function() {
  var valid = true;
  var self = this;

  _.each(this.model.get(), function (field, fieldName) {
    var fieldIsValid = self._validate(fieldName);

    if(valid) valid = fieldIsValid;
  });

  return valid;
};

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.setInvalid = function (fieldName, message) {
  this._setValidity(fieldName, false);
  this.model.push(fieldName + '.messages', message || defaultValidations['default'].message);
};

/*
 * @public
 * Reset values to origin.
 */
Validator.prototype.reset = function () {
  this._setup();
};

/*
 * @public
 * Commit values to model. Call after client- and serverside validation.
 *
 * @param {Boolean} force - Whether to force commit despite validation not passing
 */
Validator.prototype.commit = function (force) {
  if(!this.origin) return;

  if(!force && !this.validateAll()) return;

  var values = {};

  _.each(this.model.get(), function (field, fieldName) {
    if(fieldName !== 'id' && field && field.hasOwnProperty('value')) values[fieldName] = field.value;
  });

  if(this.origin.get('id')) {
    this.origin.setEach(values);
  } else {
    // REVIEW: If we haven fetched/subscribed to a doc which we have the id for (i.e. we try to add a doc which already exists in DB), we get random errors that we don't handle (but doesn't seem to really affect anything)
    var id = this.origin.leaf();
    values.id = id;
    this.origin.parent().add(values);
  }

  return true;
};

Validator.prototype._setup = function () {
  var self = this;

  if (this.fields) {
    _.each(this.fields, function (field, fieldName) {
      self._addFieldProperties(fieldName, field);
    });
  }

  if(this.origin) {
    _.each(this.origin.get(), function (value, fieldName) {
      self.model.set(fieldName + '.value', value);
    });
  }

  this.model.on('change', '*.value', function (field, value) {
    if (self.origin) {
      if (value !== self.origin.get(field)) {
        self.model.set(field + '.hasChanged', true);
      } else {
        self.model.set(field + '.hasChanged', false);
      }

      self._setChangedState();
    }
  });
};

Validator.prototype._addFieldProperties = function (fieldName, field) {
  var self = this;
  var data = {value: null};

  if(field) {
    data = {value: field.default};

    if(field.validations) {
      _.assign(data, {
        validations: this._assignValidations(field.validations),
        isValid: false,
        isInvalid: false,
        validate: function (callback) {
          return self._validate(fieldName, callback);
        }
      });
    }
  }

  this.model.setEach(fieldName, data);
};

Validator.prototype._validate = function (fieldName, callback) {
  this.model.del(fieldName + '.messages');

  var field = this.model.get(fieldName);
  if(!field || !field.hasOwnProperty('validations')) return true;

  var validating = [];
  for (var i = 0; i < field.validations.length; i++) {
    validating.push(this._check(fieldName, field.value, field.validations[i], this));
  }

  async.parallel(validating, function (err, results) {
    if (callback) callback(err ? false : true);
  });
};

Validator.prototype._assignValidations = function (validations) {
  for (var i = 0; i < validations.length; i++) {
    var validation = validations[i];

    if (!validation.message) validation.message = this._getMessage(validation.rule);

    if (typeof validation.rule === 'string') validation.rule = this._getRule(validation.rule);

    if (typeof validation.rule !== 'function' && !_.isRegExp(validation.rule)) throw new Error('This rule is not (or does not point to) a RegEx or a function.');
  }

  return validations;
};

Validator.prototype._getRule = function (ruleName) {
  if(this.options.rules[ruleName]) return this.options.rules[ruleName];

  if(defaultValidations.hasOwnProperty(ruleName)) return defaultValidations[ruleName].rule;

  throw new Error('Rule: "' + ruleName + '" is not available. You need to add it as an option.');
};

Validator.prototype._getMessage = function (ruleName) {
  return this.options.messages[ruleName] || ((defaultValidations.hasOwnProperty(ruleName)) ? defaultValidations[ruleName].message : defaultValidations['default'].message);
};

Validator.prototype._check = function (fieldName, value, validation, context) {
  return function (callback) {
    var setValidity = function (valid) {
      // TODO: condition validation so we don't overwrite a more recent validation.
      if (!valid) context.model.push(fieldName + '.messages', validation.message);
      context._setValidity(fieldName, valid);

      callback(!valid, valid);
    };

    if (_.isRegExp(validation.rule)) setValidity(validation.rule.test(value));

    if (typeof validation.rule === 'function') validation.rule(value, setValidity);
  };
};

Validator.prototype._setValidity = function (fieldName, validity) {
  this.model.setEach(fieldName, { isValid: validity, isInvalid: !validity });
  this._setState();
};

Validator.prototype._setState = function() {
  this.model.set('hasInvalidFields', !!_.result(_.find(this.model.get(), {'isInvalid': true}), 'isInvalid'));
};

Validator.prototype._setChangedState = function() {
  this.model.set('hasChangedFields', !!_.result(_.find(this.model.get(), {'hasChanged': true}), 'hasChanged'));
};
<commit_msg>Add async to validate all and commit<commit_after>var _ = require('lodash');
var async = require('async');
var defaultValidations = require('./defaultValidations');
var Model = require('racer/lib/Model');

module.exports = Validator;

/**
 * Creates a validator object and initiates it
 * @param {Model} model - A scoped model to the location on where to put the validator's data
 * @param {Model|string|Object} [origin|defaultObject] - Either a scoped model to the location where the original data is stored. Or, a path (as string) to the location where to find the original data. If not passed, the fields parameter is mandatory. Can also be passed in as a defaultObject. If passed in as default objet, the fields parameter is also mandatory.
 * @param {Collection.<string, Object>} [fields] - A collection of fields to use. If not passed, the origin parameter is mandatory. The key for each object is the field name, and the value is the field object (specified below).
   * @param {*} [fields.<fieldName>.default] - A default value for this specific field.
   * @param {Object[]} [fields.<fieldName>.validations] - A list of validations to apply.
     * @param {String|Function|RegEx} [fields.<fieldName>.validations.<index>.rule] - The rule to use when validating this rule. Either, this can be a string matching the name of a default rule (specified in default checks or passed in as an option), OR a function which will get run with the value to be validated as input and a callback to return the validity with, OR a RegEx to test against.
     * @param {String} [fields.<fieldName>.validations.<index>.message] - The error message to show if validation fails
 * @param {Object} [options] - An options object
   * @param {Collection.<string, Function|RegEx>} [options.rules] - A collection of default rules to add to this instance. Each key is the name of the rule. The value is the rule, either a function or regular expression similar to field specific functions/regular expressions passed in the fields parameter
 * @returns {Validator} - Instance of Validator
 */
function Validator(model, origin, fields, options) {
  // Parse out arguments
  var self = this;
  var args = Array.prototype.slice.call(arguments);

  if(!args.length) throw new Error('You must specify a model!');
  if(args.length == 1) throw new Error('You must either specify an origin or a fields object');

  this.model = args.shift();
  var arg = args.shift();

  // Origin cases
  if(arg instanceof Model) {
    this.origin = arg;

    arg = args.shift();
  } else if(typeof arg === 'string') {
    this.origin = this.model.scope(arg);

    arg = args.shift();
  }

  // If only a collection was passed, assume we want to add to a new doc
  if(this.origin) {
    var segments = this.origin._splitPath();

    if(segments.length === 1) {
      // Generate a new id
      this.origin = this.origin.at(model.id());
    }
  }

  if(arg) {
    // Case where we've passed in a default object
    if(!Array.isArray(arg) && typeof args[0] !== 'undefined') {
      var defaultObject = arg;

      arg = args.shift();
    }

    // Fields argument
    if(Array.isArray(arg)) {
      // Support passing in an array of fields
      this.fields = _.zipObject(arg, _.fill(Array(arg.length), {}));
    } else {
      this.fields = arg;
    }

    if(defaultObject) {
      _.each(defaultObject, function (val, key) {
        if(!self.fields[key]) self.fields[key] = {};

        self.fields[key]['default'] = val;
      });
    }
  }

  this.options = _.assign({
    rules: {},
    messages: {}
  }, args.shift());

  // Setup
  this._setup();
}

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.getValues = function () {
  var values = {};
  _.each(this.model.get(), function (field, fieldName) {
    values[fieldName] = field.value;
  });
  return values;
};

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.validate = function (fieldname, callback) {
  this._validate(fieldname, callback);
};

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.validateAll = function(callback) {
  var self = this;

  var validating = [];

  _.each(this.model.get(), function (field, fieldName) {
    validating.push(function (asyncCallback) {
      self._validate(fieldName, function (valid) {
        asyncCallback(!valid, valid);
      });
    });
  });

  async.parallel(validating, function (err, res) {
    if (callback) callback(err ? false : true, res);
  });
};

// TODO: Add proper docs for external facing methods, such as this one
Validator.prototype.setInvalid = function (fieldName, message) {
  this._setValidity(fieldName, false);
  this.model.push(fieldName + '.messages', message || defaultValidations['default'].message);
};

/*
 * @public
 * Reset values to origin.
 */
Validator.prototype.reset = function () {
  this._setup();
};

/*
 * @public
 * Validates and commits to model.
 *
 * @param {Boolean} [force] - Whether to force commit without validating.
 * @param {Function} [callback] - A function that is called after validation. Passes valid state.
 */
Validator.prototype.commit = function (force, callback) {
  if (!this.origin) return;

  if (arguments.length === 1 && typeof arguments[0] !== 'boolean')  {
    callback = arguments[0];
    force = false;
  }

  if (force) {
    this._commitToModel();

    return;
  }

  var self = this;
  this.validateAll(function (valid) {
    if (valid) self._commitToModel();

    if (callback) callback(valid);
  });
};

Validator.prototype._commitToModel = function () {
  var values = {};

  _.each(this.model.get(), function (field, fieldName) {
    if(fieldName !== 'id' && field && field.hasOwnProperty('value')) values[fieldName] = field.value;
  });

  if(this.origin.get('id')) {
    this.origin.setEach(values);
  } else {
    // REVIEW: If we haven fetched/subscribed to a doc which we have the id for (i.e. we try to add a doc which already exists in DB), we get random errors that we don't handle (but doesn't seem to really affect anything)
    var id = this.origin.leaf();
    values.id = id;
    this.origin.parent().add(values);
  }
};

Validator.prototype._setup = function () {
  var self = this;

  if (this.fields) {
    _.each(this.fields, function (field, fieldName) {
      self._addFieldProperties(fieldName, field);
    });
  }

  if(this.origin) {
    _.each(this.origin.get(), function (value, fieldName) {
      self.model.set(fieldName + '.value', value);
    });
  }

  this.model.on('change', '*.value', function (field, value) {
    if (self.origin) {
      if (value !== self.origin.get(field)) {
        self.model.set(field + '.hasChanged', true);
      } else {
        self.model.set(field + '.hasChanged', false);
      }

      self._setChangedState();
    }
  });
};

Validator.prototype._addFieldProperties = function (fieldName, field) {
  var self = this;
  var data = {value: null};

  if(field) {
    data = {value: field.default};

    if(field.validations) {
      _.assign(data, {
        validations: this._assignValidations(field.validations),
        isValid: false,
        isInvalid: false,
        validate: function (callback) {
          return self._validate(fieldName, callback);
        }
      });
    }
  }

  this.model.setEach(fieldName, data);
};

Validator.prototype._validate = function (fieldName, callback) {
  this.model.del(fieldName + '.messages');

  var field = this.model.get(fieldName);
  if(!field || !field.hasOwnProperty('validations')) return true;

  var validating = [];
  for (var i = 0; i < field.validations.length; i++) {
    validating.push(this._check(fieldName, field.value, field.validations[i], this));
  }

  async.parallel(validating, function (err, results) {
    if (callback) callback(err ? false : true);
  });
};

Validator.prototype._assignValidations = function (validations) {
  for (var i = 0; i < validations.length; i++) {
    var validation = validations[i];

    if (!validation.message) validation.message = this._getMessage(validation.rule);

    if (typeof validation.rule === 'string') validation.rule = this._getRule(validation.rule);

    if (typeof validation.rule !== 'function' && !_.isRegExp(validation.rule)) throw new Error('This rule is not (or does not point to) a RegEx or a function.');
  }

  return validations;
};

Validator.prototype._getRule = function (ruleName) {
  if(this.options.rules[ruleName]) return this.options.rules[ruleName];

  if(defaultValidations.hasOwnProperty(ruleName)) return defaultValidations[ruleName].rule;

  throw new Error('Rule: "' + ruleName + '" is not available. You need to add it as an option.');
};

Validator.prototype._getMessage = function (ruleName) {
  return this.options.messages[ruleName] || ((defaultValidations.hasOwnProperty(ruleName)) ? defaultValidations[ruleName].message : defaultValidations['default'].message);
};

Validator.prototype._check = function (fieldName, value, validation, context) {
  return function (callback) {
    var setValidity = function (valid) {
      // TODO: condition validation so we don't overwrite a more recent validation.
      if (!valid) context.model.push(fieldName + '.messages', validation.message);
      context._setValidity(fieldName, valid);

      callback(!valid, valid);
    };

    if (_.isRegExp(validation.rule)) setValidity(validation.rule.test(value));

    if (typeof validation.rule === 'function') validation.rule(value, setValidity);
  };
};

Validator.prototype._setValidity = function (fieldName, validity) {
  this.model.setEach(fieldName, { isValid: validity, isInvalid: !validity });
  this._setState();
};

Validator.prototype._setState = function() {
  this.model.set('hasInvalidFields', !!_.result(_.find(this.model.get(), {'isInvalid': true}), 'isInvalid'));
};

Validator.prototype._setChangedState = function() {
  this.model.set('hasChangedFields', !!_.result(_.find(this.model.get(), {'hasChanged': true}), 'hasChanged'));
};
<|endoftext|>


<commit_before>var url         = require('url');
var util        = require('util');
var async       = require('async');
var io          = require('socket.io-client');
var debug       = require('debug')('apianalytics');

var helpers     = require('./helpers');
var pkg         = require('../package.json');

module.exports = function Agent (serviceToken, options) {
  // Ensure agent key exists
  if (!serviceToken) {
    throw new Error('a service token is required, visit: https://www.apianalytics.com/ to obtain one');
  }

  // Ensure instance type
  if (!(this instanceof Agent)) {
    return new Agent(serviceToken, options);
  }

  // this alias
  var self = this;

  // Setup options with defaults
  self.opts = Object.create({
    host: 'socket.apianalytics.com:80',
    logger: debug,
    sendBody: false,
    reqByteLimit: 1e10,
    entriesPerHar: 1
  });

  // assign new values or keep old ones
  Object.keys(options).map(function (key) {
    key in self.opts && (self.opts[key] = options[key]);
  });

  // Setup event queue
  // TODO specify worker pool
  // TODO use msgpack + gzip?
  this.queue = async.queue(function (entry, done) {
    self.har.entries.push(entry);

    if (self.har.entries.length >= self.opts.entriesPerHar) {
      // TODO benchmark this
      self.socket.send(JSON.parse(JSON.stringify(self.har)));

      // reset entries object
      self.har.entries = [];

      self.opts.logger(util.format.apply(null, [
        'Recorded %s %s request with a response of %s %s',
        entry.request.method,
        entry.request.url,
        entry.response.status,
        entry.response.statusText
      ]));
    }

    done();
  });

  // init HAR object
  this.har = {
    version: '1.2',
    serviceToken: serviceToken,
    creator: {
      name: pkg.name,
      version: pkg.version
    },

    entries: []
  };

  // Pause event queue until connected to Analytics server
  this.queue.pause();

  // Connect to Analytics server
  self.opts.logger(util.format('starting socket connection to %s using token: %s', this.opts.host, serviceToken));

  // TODO set reconnectionAttempts count
  this.socket = io(util.format('ws://%s', this.opts.host));

  this.socket.on('connect', function () {
    self.opts.logger(util.format('Connected to %s using token: %s', self.opts.host, serviceToken));
    self.queue.resume();
  });

  this.socket.on('reconnect', function () {
    self.opts.logger(util.format('Reconnected to %s using token: %s', self.opts.host, serviceToken));
    self.queue.resume();
  });

  this.socket.on('reconnecting', function (number) {
    self.opts.logger(util.format('Reconnect attemp #%d to %s using token: %s', number, self.opts.host, serviceToken));
    self.queue.resume();
  });

  this.socket.on('disconnect', function () {
    self.opts.logger('Disconnected from %s', self.opts.host);
    self.queue.pause();
  });

  this.socket.on('error', function (err) {
    self.opts.logger(util.format('Error connecting to %s using token: %s, details: %s', self.opts.host, serviceToken, err));
    self.queue.pause();
  });

  // API Recorder Middleware
  // TODO use tamper or tamper-esque method to get raw body
  //      to determine raw content size to get infer compression size
  return function (req, res, next) {
    var agentResStartTime = new Date();

    // body container
    var bytes = 0;

    var bodies = {
      req: {
        size: -1,
        base64: null
      },

      res: {
        size: -1,
        base64: null
      }
    };

    // buffer container
    var chunked = {
      req: [],
      res: []
    };

    // store original methods for later use
    var func = {
      end: res.end,
      write: res.write
    };

    // grab the request body
    req.on('data', function (chunk) {
      bytes += chunk.length;

      if (bytes <= self.opts.reqByteLimit) {
        chunked.req.push(chunk);
      }
    });

    // construct the request body
    req.on('end', function () {
      var body = Buffer.concat(chunked.req);

      bodies.req.size = body.length;
      bodies.req.base64 = body.toString('utf8');
    });

    // override node's http.ServerResponse.write method
    res.write = function (chunk, encoding) {
      // call the original http.ServerResponse.write method
      func.write.call(res, chunk, encoding);

      chunked.res.push(chunk);
    };

    // override node's http.ServerResponse.end method
    res.end = function (data, encoding) {
      // call the original http.ServerResponse.end method
      func.end.call(res, data, encoding);

      if (chunked.res.length) {
        data = Buffer.concat(chunked.res);
      }

      // construct body
      bodies.res.size = data ? data.length : -1;
      bodies.res.base64 = data ? data.toString('utf8') : null;

      var reqReceived = new Date();
      var reqHeadersArr = helpers.objectToArray(req.headers);

      var resHeaders = helpers.parseResponseHeaderString(res._header);

      var resContentLength = parseInt(helpers.getHeaderValue(resHeaders.headersArr, 'content-length', -1));
      var resBodySize = ~[0, -1].indexOf(resContentLength) && bodies.res.size !== -1 ? bodies.res.size : resContentLength;

      var reqContentLength = parseInt(helpers.getHeaderValue(reqHeadersArr, 'content-length', -1));
      var reqBodySize = ~[0, -1].indexOf(reqContentLength) && bodies.req.size !== -1 ? bodies.req.size : reqContentLength;

      var waitTime = agentResStartTime.getTime() - reqReceived.getTime();
      var protocol = req.connection.encrypted ? 'https' : 'http';

      var entry = {
        serverIPAddress: helpers.getServerAddress(),
        startedDateTime: agentResStartTime.toISOString(),

        request: {
          method: req.method,
          url: util.format('%s://%s%s', protocol, req.headers.host, req.url),
          httpVersion: 'HTTP/' + req.httpVersion,
          queryString: helpers.objectToArray(url.parse(req.url, true).query),
          headers: reqHeadersArr,
          headersSize: helpers.getReqHeaderSize(req),
          bodySize: reqBodySize,
          content: {
            size: reqBodySize,
            mimeType: helpers.getHeaderValue(reqHeadersArr, 'content-type', 'application/octet-stream'),
            text: self.opts.sendBody ? bodies.req.base64 : null
          }
        },

        response: {
          status: res.statusCode,
          statusText: resHeaders.statusText,
          httpVersion: resHeaders.version,
          headers: resHeaders.headersArr,
          headersSize: res._header ? new Buffer(res._header).length : -1,
          bodySize: resBodySize,
          content: {
            // TODO measure before compression, if any
            size: resBodySize,
            mimeType: helpers.getHeaderValue(resHeaders.headersArr, 'content-type', 'application/octet-stream'),
            text: self.opts.sendBody ? bodies.res.base64 : null
          }
        },

        timings: {
          send: 0, // TODO
          wait: waitTime,
          receive: 0  // TODO
        }
      };

      // log some info
      self.opts.logger(util.format.apply(null, [
        'Detected "finish" with %s response on request, %s %s',
        res.statusCode,
        entry.request.method,
        entry.request.url
      ]));

      // send to queue
      self.queue.push(entry);
    };

    if (typeof next === 'function') {
      next();
    }
  };
};
<commit_msg>removing -1 rule<commit_after>var url         = require('url');
var util        = require('util');
var async       = require('async');
var io          = require('socket.io-client');
var debug       = require('debug')('apianalytics');

var helpers     = require('./helpers');
var pkg         = require('../package.json');

module.exports = function Agent (serviceToken, options) {
  // Ensure agent key exists
  if (!serviceToken) {
    throw new Error('a service token is required, visit: https://www.apianalytics.com/ to obtain one');
  }

  // Ensure instance type
  if (!(this instanceof Agent)) {
    return new Agent(serviceToken, options);
  }

  // this alias
  var self = this;

  // Setup options with defaults
  self.opts = Object.create({
    host: 'socket.apianalytics.com:80',
    logger: debug,
    sendBody: false,
    reqByteLimit: 1e10,
    entriesPerHar: 1
  });

  // assign new values or keep old ones
  Object.keys(options).map(function (key) {
    key in self.opts && (self.opts[key] = options[key]);
  });

  // Setup event queue
  // TODO specify worker pool
  // TODO use msgpack + gzip?
  this.queue = async.queue(function (entry, done) {
    self.har.entries.push(entry);

    if (self.har.entries.length >= self.opts.entriesPerHar) {
      // TODO benchmark this
      self.socket.send(JSON.parse(JSON.stringify(self.har)));

      // reset entries object
      self.har.entries = [];

      self.opts.logger(util.format.apply(null, [
        'Recorded %s %s request with a response of %s %s',
        entry.request.method,
        entry.request.url,
        entry.response.status,
        entry.response.statusText
      ]));
    }

    done();
  });

  // init HAR object
  this.har = {
    version: '1.2',
    serviceToken: serviceToken,
    creator: {
      name: pkg.name,
      version: pkg.version
    },

    entries: []
  };

  // Pause event queue until connected to Analytics server
  this.queue.pause();

  // Connect to Analytics server
  self.opts.logger(util.format('starting socket connection to %s using token: %s', this.opts.host, serviceToken));

  // TODO set reconnectionAttempts count
  this.socket = io(util.format('ws://%s', this.opts.host));

  this.socket.on('connect', function () {
    self.opts.logger(util.format('Connected to %s using token: %s', self.opts.host, serviceToken));
    self.queue.resume();
  });

  this.socket.on('reconnect', function () {
    self.opts.logger(util.format('Reconnected to %s using token: %s', self.opts.host, serviceToken));
    self.queue.resume();
  });

  this.socket.on('reconnecting', function (number) {
    self.opts.logger(util.format('Reconnect attemp #%d to %s using token: %s', number, self.opts.host, serviceToken));
    self.queue.resume();
  });

  this.socket.on('disconnect', function () {
    self.opts.logger('Disconnected from %s', self.opts.host);
    self.queue.pause();
  });

  this.socket.on('error', function (err) {
    self.opts.logger(util.format('Error connecting to %s using token: %s, details: %s', self.opts.host, serviceToken, err));
    self.queue.pause();
  });

  // API Recorder Middleware
  // TODO use tamper or tamper-esque method to get raw body
  //      to determine raw content size to get infer compression size
  return function (req, res, next) {
    var agentResStartTime = new Date();

    // body container
    var bytes = 0;

    var bodies = {
      req: {
        size: 0,
        base64: null
      },

      res: {
        size: 0,
        base64: null
      }
    };

    // buffer container
    var chunked = {
      req: [],
      res: []
    };

    // store original methods for later use
    var func = {
      end: res.end,
      write: res.write
    };

    // grab the request body
    req.on('data', function (chunk) {
      bytes += chunk.length;

      if (bytes <= self.opts.reqByteLimit) {
        chunked.req.push(chunk);
      }
    });

    // construct the request body
    req.on('end', function () {
      var body = Buffer.concat(chunked.req);

      bodies.req.size = body.length;
      bodies.req.base64 = body.toString('utf8');
    });

    // override node's http.ServerResponse.write method
    res.write = function (chunk, encoding) {
      // call the original http.ServerResponse.write method
      func.write.call(res, chunk, encoding);

      chunked.res.push(chunk);
    };

    // override node's http.ServerResponse.end method
    res.end = function (data, encoding) {
      // call the original http.ServerResponse.end method
      func.end.call(res, data, encoding);

      if (chunked.res.length) {
        data = Buffer.concat(chunked.res);
      }

      // construct body
      bodies.res.size = data ? data.length : 0;
      bodies.res.base64 = data ? data.toString('utf8') : null;

      var reqReceived = new Date();
      var reqHeadersArr = helpers.objectToArray(req.headers);

      var resHeaders = helpers.parseResponseHeaderString(res._header);

      var resContentLength = parseInt(helpers.getHeaderValue(resHeaders.headersArr, 'content-length', 0));
      var resBodySize = resContentLength === 0 && bodies.res.size > 0 ? bodies.res.size : resContentLength;

      var reqContentLength = parseInt(helpers.getHeaderValue(reqHeadersArr, 'content-length', 0));
      var reqBodySize = reqContentLength === 0 && bodies.req.size > 0 ? bodies.req.size : reqContentLength;

      var waitTime = agentResStartTime.getTime() - reqReceived.getTime();
      var protocol = req.connection.encrypted ? 'https' : 'http';

      var entry = {
        serverIPAddress: helpers.getServerAddress(),
        startedDateTime: agentResStartTime.toISOString(),

        request: {
          method: req.method,
          url: util.format('%s://%s%s', protocol, req.headers.host, req.url),
          httpVersion: 'HTTP/' + req.httpVersion,
          queryString: helpers.objectToArray(url.parse(req.url, true).query),
          headers: reqHeadersArr,
          headersSize: helpers.getReqHeaderSize(req),
          bodySize: reqBodySize,
          content: {
            size: reqBodySize,
            mimeType: helpers.getHeaderValue(reqHeadersArr, 'content-type', 'application/octet-stream'),
            text: self.opts.sendBody ? bodies.req.base64 : null
          }
        },

        response: {
          status: res.statusCode,
          statusText: resHeaders.statusText,
          httpVersion: resHeaders.version,
          headers: resHeaders.headersArr,
          headersSize: res._header ? new Buffer(res._header).length : 0,
          bodySize: resBodySize,
          content: {
            // TODO measure before compression, if any
            size: resBodySize,
            mimeType: helpers.getHeaderValue(resHeaders.headersArr, 'content-type', 'application/octet-stream'),
            text: self.opts.sendBody ? bodies.res.base64 : null
          }
        },

        timings: {
          send: 0, // TODO
          wait: waitTime,
          receive: 0  // TODO
        }
      };

      // log some info
      self.opts.logger(util.format.apply(null, [
        'Detected "finish" with %s response on request, %s %s',
        res.statusCode,
        entry.request.method,
        entry.request.url
      ]));

      // send to queue
      self.queue.push(entry);
    };

    if (typeof next === 'function') {
      next();
    }
  };
};
<|endoftext|>


<commit_before>'use strict'

import readCache from './read-cache'
import serialize from './serialize'

function cache (config = {}) {
  if (!config.store) {
    throw new Error('Cache middleware need to be provided a store.')
  }

  const store = config.store
  const key = config.key || cache.key
  const read = config.readCache || readCache
  const serialize = config.serialize || serialize

  return (req, next, service) => {
    const useCache = !service.use || (service.use && (service.use.cache !== false))

    if (!useCache) {
      return next()
    }

    return store.getItem(key(req))
      .then(read(req))
      .catch((err) => {
        // cache miss
        const promise = next()
        promise.then((req, res) => {
          store.setItem(key(req), serialize(req, res))
        })

        return promise
      })
  }
}

cache.readCache = readCache
cache.serialize = serialize

cache.key = function (req) {
  return req.url
}

export default cache
<commit_msg>feat(cache): intercept the cache error to trigger a network fetch<commit_after>'use strict'

import readCache from './read-cache'
import serialize from './serialize'

function cache (config = {}) {
  if (!config.store) {
    throw new Error('Cache middleware need to be provided a store.')
  }

  const store = config.store
  const key = config.key || cache.key
  const read = config.readCache || readCache
  const serialize = config.serialize || serialize

  return (req, next, service) => {
    const useCache = !service.use || (service.use && (service.use.cache !== false))

    if (!useCache) {
      return next()
    }

    return new Promise((resolve, reject) => {
      store.getItem(key(req))
        .then((value) => {
          read(req)(value)
            .then(resolve)
            .catch((err) => {
              // cache miss
              next()
                .then((res) => {
                  resolve(res)
                  store.setItem(key(req), serialize(req, res))
                })
                .catch(reject)
            })
        })
        .catch(reject)
    })
  }
}

cache.readCache = readCache
cache.serialize = serialize

cache.key = function (req) {
  return req.url
}

export default cache
<|endoftext|>


<commit_before>(function (root, factory) {
	if (typeof exports === 'object') {
		// For nodejs
		module.exports = factory();
	} else {
		// For browsers
		root.opening_hours = factory();
	}
}(this, function () {
	return function(value) {
		//======================================================================
		// Constants
		//======================================================================
		var months = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
		var weekdays = { Su: 0, Mo: 1, Tu: 2, We: 3, Th: 4, Fr: 5, Sa: 6 };

		//======================================================================
		// Constructor - entry to parsing code
		//======================================================================
		// Terminology:
		//
		// Mo-Fr 10:00-11:00; Th 10:00-12:00
		// \_____block_____/  \____block___/
		//
		// Mo-Fr Jan 10:00-11:00
		// \__/  \_/ \_________/
		// selectors (left to right: weekday, month, time)
		//
		// Logic:
		// - Split blocks; foreach block:
		// - Tokenize
		// - Run toplevel (block) parser
		//   - Which calls subparser for specific selector types
		//     - Which produce selectors
		var blocks = value.split(/\s*;\s*/);

		for (var block = 0; block < blocks.length; block++) {
			var tokens = tokenize(blocks[block]);

			var selectors = {
				time: [],
				weekday: [],

				meaning: true,
			};

			parseGroup(tokens, 0, selectors);

			blocks[block] = selectors;
		}

		// Tokenization function: splits string into parts
		// output: array of pairs [content, type]
		function tokenize(value) {
			var tokens = new Array();

			while (value != '') {
				var tmp;
				if (tmp = value.match(/^(?:week|24\/7|off)/)) {
					// reserved word
					tokens.push([tmp[0], tmp[0]]);
					value = value.substr(tmp[0].length)
				} else if (tmp = value.match(/^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/)) {
					// month name
					tokens.push([months[tmp[0]], 'month']);
					value = value.substr(3);
				} else if (tmp = value.match(/^(?:Mo|Tu|We|Th|Fr|Sa|Su)/)) {
					// weekday name
					tokens.push([weekdays[tmp[0]], 'weekday']);
					value = value.substr(2);
				} else if (tmp = value.match(/^\d+/)) {
					// number
					tokens.push([tmp[0], 'number']);
					value = value.substr(tmp[0].length);
				} else if (value.match(/^\s/)) {
					// whitespace are fixed
					value = value.substr(1)
				} else {
					// other single-character tokens
					tokens.push([value[0], value[0]]);
					value = value.substr(1)
				}
			}

			return tokens;
		}

		// Function to check token array for specific pattern
		function matchTokens(tokens, at, match) {
			for (var i = 0; i < match.length; i++) {
				if (typeof tokens[at + i] === 'undefined')
					return false;
				if (tokens[at + i][1] !== match[i])
					return false;
			}

			return true;
		}

		//======================================================================
		// Top-level parser
		//======================================================================
		function parseGroup(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday') {
					at = parseWeekdayRange(tokens, at, selectors);
				} else if (tokens[at][1] == 'month' && tokens[at+1][1] == 'number') {
					at = parseMonthDayRange(tokens, at);
				} else if (tokens[at][1] == 'month') {
					at = parseMonthRange(tokens, at);
				} else if (tokens[at][0] == 'week') {
					at = parseWeekRange(tokens, at);
				} else if (tokens[at][1] == 'number') {
					at = parseTimeRange(tokens, at, selectors);
				} else if (tokens[at][0] == 'off') {
					selectors.meaning = false;
					at++;
				} else if (tokens[at][0] == '24/7') {
					selectors.time.push(function(date) { return [true]; });
					at++;
				} else {
					throw 'Unexpected token: "' + tokens[at] + '"';
				}
			}

			return tokens;
		}

		//======================================================================
		// Time range parser (10:00-12:00,14:00-16:00)
		//======================================================================
		function parseTimeRange(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == ':' && tokens[at+2][1] == 'number' && tokens[at+3][0] == '-' && tokens[at+4][1] == 'number' && tokens[at+5][0] == ':' && tokens[at+6][1] == 'number') {
					// Time range
					selectors.time.push(function(tokens, at) { return function(date) {
						var ourminutes = date.getHours() * 60 + date.getMinutes();
						var minutes_from = tokens[at][0] * 60 + tokens[at+2][0] * 1;
						var minutes_to = tokens[at+4][0] * 60 + tokens[at+6][0] * 1;

						if (ourminutes < minutes_from) {
							var eventdate = new Date(date);
							eventdate.setHours(0, minutes_from, 0, 0);
							return [false, eventdate];
						} else if (ourminutes < minutes_to) {
							var eventdate = new Date(date);
							eventdate.setHours(0, minutes_to, 0, 0);
							return [true, eventdate];
						} else {
							var eventdate = new Date(date);
							eventdate.setHours(0, minutes_from, 0, 0);
							eventdate.setDate(eventdate.getDate()+1);
							return [false, eventdate];
						}
					}}(tokens, at));

					at += 7;
				} else {
					throw 'Unexpected token in time range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;

			}

			return at;
		}

		// for given date, returns date moved to the start of specified day minute
		function dateAtNextDayMinutes(date, minutes) {
			var tmpdate = new Date(date);
			tmpdate.setHours(0, minutes, 0, 0);
			return tmpdate;
		}

		//======================================================================
		// Weekday range parser (Mo,We-Fr,Sa[1-2,-1])
		//======================================================================
		function parseWeekdayRange(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday' && tokens[at+1][0] == '-' && tokens[at+2][1] == 'weekday') {
					// Weekday range (Mo-Fr)
					selectors.weekday.push(function(tokens, at) { return function(date) {
						var ourweekday = date.getDay();
						var weekday_from = tokens[at][0];
						var weekday_to = tokens[at+2][0];

						if (weekday_from <= weekday_to) {
							if (ourweekday < weekday_from || ourweekday > weekday_to) {
								return [false, dateAtNextWeekday(date, weekday_from)];
							} else {
								return [true, dateAtNextWeekday(date, weekday_to + 1)];
							}
						} else {
							if (ourweekday <= weekday_to || ourweekday >= weekday_from) {
								return [true, dateAtNextWeekday(date, weekday_to + 1)];
							} else {
								return [false, dateAtNextWeekday(date, weekday_from)];
							}
						}
					}}(tokens, at));

					at += 3;
				} else if (tokens[at][1] == 'weekday' && tokens[at+1][0] == '[') {
					// Conditional weekday (Mo[3])
					at = parseNumRange(tokens, at+2);
				} else if (tokens[at][1] == 'weekday') {
					// Single weekday (Mo)
					selectors.weekday.push(function(tokens, at) { return function(date) {
						var ourweekday = date.getDay();
						var weekday_at = tokens[at][0];

						if (ourweekday == weekday_at) {
							return [true, dateAtNextWeekday(date, weekday_at + 1)];
						} else {
							return [false, dateAtNextWeekday(date, weekday_at)];
						}
					}}(tokens, at));

					at++;
				} else {
					throw 'Unexpected token in weekday range: "' + tokens[at] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// Numeric list parser (1,2,3-4,-1), used in weekday parser above
		function parseNumRange(tokens, at) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == '-' && tokens[at+2][1] == 'number') {
					// Number range
					at += 3;
				} else if (tokens[at][0] == '-' && tokens[at+1][1] == 'number') {
					// Negative number
					at += 2
				} else if (tokens[at][1] == 'number') {
					// Single number
					at++;
				} else {
					throw 'Unexpected token in number range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',') {
					at++;
				} else if (tokens[at][0] == ']') {
					at++;
					break;
				}
			}

			return at;
		}

		// for given date, returns date moved to the specific day of week
		function dateAtNextWeekday(date, day) {
			var tmpdate = new Date(date);

			if (tmpdate.getDay() < day)
				tmpdate.setDate(tmpdate.getDate() + day - tmpdate.getDay());
			else if (tmpdate.getDay() > day)
				tmpdate.setDate(tmpdate.getDate() + 7 + day - tmpdate.getDay());

			tmpdate.setHours(0, 0, 0, 0);

			return tmpdate;
		}

		//======================================================================
		// Month range parser (Jan,Feb-Mar)
		//======================================================================
		function parseMonthDayRange(tokens, at) {
			return at + 1;
		}

		//======================================================================
		// Main selector traversing function
		//======================================================================
		function getState(date) {
			if (typeof date === 'undefined')
				date = new Date();

			var resultstate = false;
			var changedate;

			var dateseltypes = [ 'weekday' ];
			var date_matching_blocks = [];

			for (var block = 0; block < blocks.length; block++) {
				var has_date_selectors = false;
				var matching_date_block = true;

				// Try each date selector type
				for (var dateseltype = 0; dateseltype < dateseltypes.length; dateseltype++) {
					var dateselectors = blocks[block][dateseltypes[dateseltype]];
					if (dateselectors.length > 0)
						has_date_selectors = true;
					var has_matching_selector = false;
					for (var datesel = 0; datesel < dateselectors.length; datesel++) {
						var res = dateselectors[datesel](date);
						if (res[0])
							has_matching_selector = true;
						if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
							changedate = res[1];
					}

					if (has_date_selectors && !has_matching_selector) {
						matching_date_block = false;
						// XXX: do we need to break here, or we need to adjust time?
					}
				}

				if (matching_date_block) {
					if (has_date_selectors && blocks[block].meaning)
						date_matching_blocks = [];
					date_matching_blocks.push(block);
				}
			}

			for (var nblock = 0; nblock < date_matching_blocks.length; nblock++) {
				var block = date_matching_blocks[nblock];

				for (var timesel = 0; timesel < blocks[block].time.length; timesel++) {
					var res = blocks[block].time[timesel](date);
					if (res[0])
						resultstate = blocks[block].meaning;
					if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
						changedate = res[1];
				}
			}

			return [ resultstate, changedate ];
		}

		//======================================================================
		// Public interface
		//======================================================================

		// check whether facility is `open' on the given date (or now)
		this.isOpen = function(date) {
			return getState(date)[0];
		}

		// returns time of next status change
		this.nextChange = function(date) {
			var state = getState(date);
			var firststate = state;
			var prevstate;
			var maxdelta = 60*60*24*365*1000;
			do {
				prevstate = state;
				state = getState(prevstate[1]);

				// this indicates error in some selector generating code above
				if (state[1].getTime() <= prevstate[1].getTime())
					throw "Fatal: infinite loop in nextChange";

				// this may happen if the facility is always open/closed,
				// we may need a better way of checking for that
				if (state[1].getTime() - firststate[1].getTime() > maxdelta)
					return undefined;
			} while (state[0] === prevstate[0]);
			return prevstate[1];
		}
	}
}));
<commit_msg>Move closing ] after numeric range to upper level where it belongs<commit_after>(function (root, factory) {
	if (typeof exports === 'object') {
		// For nodejs
		module.exports = factory();
	} else {
		// For browsers
		root.opening_hours = factory();
	}
}(this, function () {
	return function(value) {
		//======================================================================
		// Constants
		//======================================================================
		var months = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 };
		var weekdays = { Su: 0, Mo: 1, Tu: 2, We: 3, Th: 4, Fr: 5, Sa: 6 };

		//======================================================================
		// Constructor - entry to parsing code
		//======================================================================
		// Terminology:
		//
		// Mo-Fr 10:00-11:00; Th 10:00-12:00
		// \_____block_____/  \____block___/
		//
		// Mo-Fr Jan 10:00-11:00
		// \__/  \_/ \_________/
		// selectors (left to right: weekday, month, time)
		//
		// Logic:
		// - Split blocks; foreach block:
		// - Tokenize
		// - Run toplevel (block) parser
		//   - Which calls subparser for specific selector types
		//     - Which produce selectors
		var blocks = value.split(/\s*;\s*/);

		for (var block = 0; block < blocks.length; block++) {
			var tokens = tokenize(blocks[block]);

			var selectors = {
				time: [],
				weekday: [],

				meaning: true,
			};

			parseGroup(tokens, 0, selectors);

			blocks[block] = selectors;
		}

		// Tokenization function: splits string into parts
		// output: array of pairs [content, type]
		function tokenize(value) {
			var tokens = new Array();

			while (value != '') {
				var tmp;
				if (tmp = value.match(/^(?:week|24\/7|off)/)) {
					// reserved word
					tokens.push([tmp[0], tmp[0]]);
					value = value.substr(tmp[0].length)
				} else if (tmp = value.match(/^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/)) {
					// month name
					tokens.push([months[tmp[0]], 'month']);
					value = value.substr(3);
				} else if (tmp = value.match(/^(?:Mo|Tu|We|Th|Fr|Sa|Su)/)) {
					// weekday name
					tokens.push([weekdays[tmp[0]], 'weekday']);
					value = value.substr(2);
				} else if (tmp = value.match(/^\d+/)) {
					// number
					tokens.push([tmp[0], 'number']);
					value = value.substr(tmp[0].length);
				} else if (value.match(/^\s/)) {
					// whitespace are fixed
					value = value.substr(1)
				} else {
					// other single-character tokens
					tokens.push([value[0], value[0]]);
					value = value.substr(1)
				}
			}

			return tokens;
		}

		// Function to check token array for specific pattern
		function matchTokens(tokens, at, match) {
			for (var i = 0; i < match.length; i++) {
				if (typeof tokens[at + i] === 'undefined')
					return false;
				if (tokens[at + i][1] !== match[i])
					return false;
			}

			return true;
		}

		//======================================================================
		// Top-level parser
		//======================================================================
		function parseGroup(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday') {
					at = parseWeekdayRange(tokens, at, selectors);
				} else if (tokens[at][1] == 'month' && tokens[at+1][1] == 'number') {
					at = parseMonthDayRange(tokens, at);
				} else if (tokens[at][1] == 'month') {
					at = parseMonthRange(tokens, at);
				} else if (tokens[at][0] == 'week') {
					at = parseWeekRange(tokens, at);
				} else if (tokens[at][1] == 'number') {
					at = parseTimeRange(tokens, at, selectors);
				} else if (tokens[at][0] == 'off') {
					selectors.meaning = false;
					at++;
				} else if (tokens[at][0] == '24/7') {
					selectors.time.push(function(date) { return [true]; });
					at++;
				} else {
					throw 'Unexpected token: "' + tokens[at] + '"';
				}
			}

			return tokens;
		}

		//======================================================================
		// Time range parser (10:00-12:00,14:00-16:00)
		//======================================================================
		function parseTimeRange(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == ':' && tokens[at+2][1] == 'number' && tokens[at+3][0] == '-' && tokens[at+4][1] == 'number' && tokens[at+5][0] == ':' && tokens[at+6][1] == 'number') {
					// Time range
					selectors.time.push(function(tokens, at) { return function(date) {
						var ourminutes = date.getHours() * 60 + date.getMinutes();
						var minutes_from = tokens[at][0] * 60 + tokens[at+2][0] * 1;
						var minutes_to = tokens[at+4][0] * 60 + tokens[at+6][0] * 1;

						if (ourminutes < minutes_from) {
							var eventdate = new Date(date);
							eventdate.setHours(0, minutes_from, 0, 0);
							return [false, eventdate];
						} else if (ourminutes < minutes_to) {
							var eventdate = new Date(date);
							eventdate.setHours(0, minutes_to, 0, 0);
							return [true, eventdate];
						} else {
							var eventdate = new Date(date);
							eventdate.setHours(0, minutes_from, 0, 0);
							eventdate.setDate(eventdate.getDate()+1);
							return [false, eventdate];
						}
					}}(tokens, at));

					at += 7;
				} else {
					throw 'Unexpected token in time range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;

			}

			return at;
		}

		// for given date, returns date moved to the start of specified day minute
		function dateAtNextDayMinutes(date, minutes) {
			var tmpdate = new Date(date);
			tmpdate.setHours(0, minutes, 0, 0);
			return tmpdate;
		}

		//======================================================================
		// Weekday range parser (Mo,We-Fr,Sa[1-2,-1])
		//======================================================================
		function parseWeekdayRange(tokens, at, selectors) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'weekday' && tokens[at+1][0] == '-' && tokens[at+2][1] == 'weekday') {
					// Weekday range (Mo-Fr)
					selectors.weekday.push(function(tokens, at) { return function(date) {
						var ourweekday = date.getDay();
						var weekday_from = tokens[at][0];
						var weekday_to = tokens[at+2][0];

						if (weekday_from <= weekday_to) {
							if (ourweekday < weekday_from || ourweekday > weekday_to) {
								return [false, dateAtNextWeekday(date, weekday_from)];
							} else {
								return [true, dateAtNextWeekday(date, weekday_to + 1)];
							}
						} else {
							if (ourweekday <= weekday_to || ourweekday >= weekday_from) {
								return [true, dateAtNextWeekday(date, weekday_to + 1)];
							} else {
								return [false, dateAtNextWeekday(date, weekday_from)];
							}
						}
					}}(tokens, at));

					at += 3;
				} else if (tokens[at][1] == 'weekday' && tokens[at+1][0] == '[') {
					// Conditional weekday (Mo[3])
					at = parseNumRange(tokens, at+2);
					if (tokens[at][1] !== ']')
						throw '"]" expected';
					at++;
				} else if (tokens[at][1] == 'weekday') {
					// Single weekday (Mo)
					selectors.weekday.push(function(tokens, at) { return function(date) {
						var ourweekday = date.getDay();
						var weekday_at = tokens[at][0];

						if (ourweekday == weekday_at) {
							return [true, dateAtNextWeekday(date, weekday_at + 1)];
						} else {
							return [false, dateAtNextWeekday(date, weekday_at)];
						}
					}}(tokens, at));

					at++;
				} else {
					throw 'Unexpected token in weekday range: "' + tokens[at] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// Numeric list parser (1,2,3-4,-1), used in weekday parser above
		function parseNumRange(tokens, at) {
			while (at < tokens.length) {
				if (tokens[at][1] == 'number' && tokens[at+1][0] == '-' && tokens[at+2][1] == 'number') {
					// Number range
					at += 3;
				} else if (tokens[at][0] == '-' && tokens[at+1][1] == 'number') {
					// Negative number
					at += 2
				} else if (tokens[at][1] == 'number') {
					// Single number
					at++;
				} else {
					throw 'Unexpected token in number range: "' + tokens[at][0] + '"';
				}

				if (at >= tokens.length)
					break;

				if (tokens[at][0] == ',')
					at++;
				else
					break;
			}

			return at;
		}

		// for given date, returns date moved to the specific day of week
		function dateAtNextWeekday(date, day) {
			var tmpdate = new Date(date);

			if (tmpdate.getDay() < day)
				tmpdate.setDate(tmpdate.getDate() + day - tmpdate.getDay());
			else if (tmpdate.getDay() > day)
				tmpdate.setDate(tmpdate.getDate() + 7 + day - tmpdate.getDay());

			tmpdate.setHours(0, 0, 0, 0);

			return tmpdate;
		}

		//======================================================================
		// Month range parser (Jan,Feb-Mar)
		//======================================================================
		function parseMonthDayRange(tokens, at) {
			return at + 1;
		}

		//======================================================================
		// Main selector traversing function
		//======================================================================
		function getState(date) {
			if (typeof date === 'undefined')
				date = new Date();

			var resultstate = false;
			var changedate;

			var dateseltypes = [ 'weekday' ];
			var date_matching_blocks = [];

			for (var block = 0; block < blocks.length; block++) {
				var has_date_selectors = false;
				var matching_date_block = true;

				// Try each date selector type
				for (var dateseltype = 0; dateseltype < dateseltypes.length; dateseltype++) {
					var dateselectors = blocks[block][dateseltypes[dateseltype]];
					if (dateselectors.length > 0)
						has_date_selectors = true;
					var has_matching_selector = false;
					for (var datesel = 0; datesel < dateselectors.length; datesel++) {
						var res = dateselectors[datesel](date);
						if (res[0])
							has_matching_selector = true;
						if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
							changedate = res[1];
					}

					if (has_date_selectors && !has_matching_selector) {
						matching_date_block = false;
						// XXX: do we need to break here, or we need to adjust time?
					}
				}

				if (matching_date_block) {
					if (has_date_selectors && blocks[block].meaning)
						date_matching_blocks = [];
					date_matching_blocks.push(block);
				}
			}

			for (var nblock = 0; nblock < date_matching_blocks.length; nblock++) {
				var block = date_matching_blocks[nblock];

				for (var timesel = 0; timesel < blocks[block].time.length; timesel++) {
					var res = blocks[block].time[timesel](date);
					if (res[0])
						resultstate = blocks[block].meaning;
					if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
						changedate = res[1];
				}
			}

			return [ resultstate, changedate ];
		}

		//======================================================================
		// Public interface
		//======================================================================

		// check whether facility is `open' on the given date (or now)
		this.isOpen = function(date) {
			return getState(date)[0];
		}

		// returns time of next status change
		this.nextChange = function(date) {
			var state = getState(date);
			var firststate = state;
			var prevstate;
			var maxdelta = 60*60*24*365*1000;
			do {
				prevstate = state;
				state = getState(prevstate[1]);

				// this indicates error in some selector generating code above
				if (state[1].getTime() <= prevstate[1].getTime())
					throw "Fatal: infinite loop in nextChange";

				// this may happen if the facility is always open/closed,
				// we may need a better way of checking for that
				if (state[1].getTime() - firststate[1].getTime() > maxdelta)
					return undefined;
			} while (state[0] === prevstate[0]);
			return prevstate[1];
		}
	}
}));
<|endoftext|>


<commit_before>var AmpersandState = require('ampersand-state');
var FieldCollection = require('./models/field-collection');
var debug = require('debug')('mongodb-schema:models:schema');
var es = require('event-stream');
var flatten = require('flatnest').flatten;
var _ = require('lodash');

function getValueType(value) {
  var T;
  if (_.has(value, '_bsonType')) {
    T = value._bsonType;
  } else {
    T = Object.prototype.toString.call(value).replace(/\[object (\w+)\]/, '$1');
  }
  return T;
}

var Schema = AmpersandState.extend({
  collections: {
    fields: FieldCollection
  },
  props: {
    count: {
      type: 'number',
      default: 0
    }
  },
  stream: function() {
    var schema = this;
    return es.through(function(doc) {
      debug('updating based on %j', doc);
      _.each(_.pairs(flatten(doc)), function(d) {
        var _id = d[0];
        var value = d[1];
        var T = getValueType(d[1]);
        var field = schema.fields.get(_id);

        if (!field) {
          debug('`%s` is a new field', _id);
          field = schema.fields.add({
            _id: _id
          });

          field.types.add({
            _id: T,
            count: 1,
            value: value
          });
        } else {
          debug('`%s` is already a known field with %d type(s)', _id, field.types.length);
          var existingType = field.types.get(T);
          if (!existingType) {
            debug('new type `%s` for field `%s`', T, _id);
            field.types.add({
              _id: T,
              count: 1,
              value: value
            });
          } else {
            debug('updating existing type %j', existingType);
            existingType.values.push(value);
            existingType.count += 1;
          }
        }
        this.emit('data', field);
      }, this);
      schema.count += 1;
    }, function() {
      debug('finalized schema is', JSON.stringify(schema, null, 2));
      this.emit('end');
    });
  }
});

module.exports = function() {
  return new Schema();
};
module.exports.extend = Schema.extend;

module.exports.stream = function() {
  return new Schema().stream();
};

module.exports.FieldCollection = require('./models/field-collection');
<commit_msg>fix(main): export Schema class<commit_after>var AmpersandState = require('ampersand-state');
var FieldCollection = require('./models/field-collection');
var debug = require('debug')('mongodb-schema:models:schema');
var es = require('event-stream');
var flatten = require('flatnest').flatten;
var _ = require('lodash');

function getValueType(value) {
  var T;
  if (_.has(value, '_bsonType')) {
    T = value._bsonType;
  } else {
    T = Object.prototype.toString.call(value).replace(/\[object (\w+)\]/, '$1');
  }
  return T;
}

var Schema = AmpersandState.extend({
  collections: {
    fields: FieldCollection
  },
  props: {
    count: {
      type: 'number',
      default: 0
    }
  },
  stream: function() {
    var schema = this;
    return es.through(function(doc) {
      debug('updating based on %j', doc);
      _.each(_.pairs(flatten(doc)), function(d) {
        var _id = d[0];
        var value = d[1];
        var T = getValueType(d[1]);
        var field = schema.fields.get(_id);

        if (!field) {
          debug('`%s` is a new field', _id);
          field = schema.fields.add({
            _id: _id,
            count: 1
          });

          field.types.add({
            _id: T,
            count: 1,
            value: value
          });
        } else {
          debug('`%s` is already a known field with %d type(s)', _id, field.types.length);
          var existingType = field.types.get(T);
          if (!existingType) {
            debug('new type `%s` for field `%s`', T, _id);
            field.types.add({
              _id: T,
              count: 1,
              value: value
            });
          } else {
            debug('updating existing type %j', existingType);
            existingType.values.push(value);
            existingType.count += 1;
          }
        }
        this.emit('data', field);
      }, this);
      schema.count += 1;
    }, function() {
      debug('finalized schema is', JSON.stringify(schema, null, 2));
      this.emit('end');
    });
  }
});

module.exports = function() {
  return new Schema();
};

module.exports.extend = Schema.extend;

module.exports.stream = function() {
  return new Schema().stream();
};
module.exports.Schema = Schema;
module.exports.FieldCollection = require('./models/field-collection');
<|endoftext|>


<commit_before>import DateTimeModal from './components/form/date-time-modal'
import DateTimePreview from './components/form/date-time-preview'
import DateTimeSelector from './components/form/date-time-selector'
import DefaultSearchForm from './components/form/default-search-form'
import ErrorMessage from './components/form/error-message'
import GeneralSettingsPanel from './components/form/general-settings-panel'
import LocationField from './components/form/location-field'
import ModeSelector from './components/form/mode-selector'
import ModesPanel from './components/form/modes-panel'
import PlanTripButton from './components/form/plan-trip-button'
import SettingsPreview from './components/form/settings-preview'
import SettingsSelectorPanel from './components/form/settings-selector-panel'
import SwitchButton from './components/form/switch-button'

import BaseLayers from './components/map/base-layers'
import BaseMap from './components/map/base-map'
import BikeRentalOverlay from './components/map/bike-rental-overlay'
import DefaultMap from './components/map/default-map'
import EndpointsOverlay from './components/map/endpoints-overlay'
import ItineraryOverlay from './components/map/itinerary-overlay'
import Map from './components/map/map'
import ToggleMap from './components/map/toggle-map'
import TransitiveOverlay from './components/map/transitive-overlay'
import StylizedMap from './components/map/stylized-map'
import OsmBaseLayer from './components/map/osm-base-layer'
import StopsOverlay from './components/map/stops-overlay'
import RoutesOverlay from './components/map/routes-overlay'

import ItineraryCarousel from './components/narrative/itinerary-carousel'
import LegDiagramPreview from './components/narrative/leg-diagram-preview'
import NarrativeItineraries from './components/narrative/narrative-itineraries'
import NarrativeProfileOptions from './components/narrative/narrative-profile-options'
import NarrativeItinerary from './components/narrative/narrative-itinerary'
import NarrativeRoutingResults from './components/narrative/narrative-routing-results'
import RealtimeAnnotation from './components/narrative/realtime-annotation'
import TransportationNetworkCompanyLeg from './components/narrative/default/tnc-leg'
import TripDetails from './components/narrative/trip-details'
import TripTools from './components/narrative/trip-tools'

import MobileMain from './components/mobile/main'

import StopViewer from './components/viewers/stop-viewer'
import ViewStopButton from './components/viewers/view-stop-button'
import ViewTripButton from './components/viewers/view-trip-button'
import ViewerContainer from './components/viewers/viewer-container'

import ResponsiveWebapp from './components/app/responsive-webapp'
import AppMenu from './components/app/app-menu'

import { setAutoPlan, setMapCenter } from './actions/config'
import { getCurrentPosition } from './actions/location'
import { setLocationToCurrent, clearLocation } from './actions/map'
import { setUseRealtimeResponse } from './actions/narrative'
import { findNearbyStops } from './actions/api'

import createOtpReducer from './reducers/create-otp-reducer'

import otpUtils from './util'

export {
  // form components
  DateTimeModal,
  DateTimePreview,
  DateTimeSelector,
  DefaultSearchForm,
  ErrorMessage,
  GeneralSettingsPanel,
  LocationField,
  ModeSelector,
  ModesPanel,
  PlanTripButton,
  SettingsPreview,
  SettingsSelectorPanel,
  StylizedMap,
  SwitchButton,

  // map components
  BaseLayers,
  BaseMap,
  BikeRentalOverlay,
  DefaultMap,
  EndpointsOverlay,
  ItineraryCarousel,
  ItineraryOverlay,
  Map,
  OsmBaseLayer,
  StopsOverlay,
  RoutesOverlay,
  ToggleMap,
  TransitiveOverlay,

  // narrative components
  LegDiagramPreview,
  NarrativeItineraries,
  NarrativeItinerary,
  NarrativeProfileOptions,
  NarrativeRoutingResults,
  RealtimeAnnotation,
  TransportationNetworkCompanyLeg,
  TripDetails,
  TripTools,

  // mobile compoments
  MobileMain,

  // viewer components
  StopViewer,
  ViewerContainer,
  ViewStopButton,
  ViewTripButton,

  // app components,
  ResponsiveWebapp,
  AppMenu,

  // actions
  clearLocation,
  findNearbyStops,
  getCurrentPosition,
  setAutoPlan,
  setLocationToCurrent,
  setMapCenter,
  setUseRealtimeResponse,

  // redux utilities
  createOtpReducer,

  // general utilities
  otpUtils
}
<commit_msg>fix(index): Remove ToggleMap from index.js<commit_after>import DateTimeModal from './components/form/date-time-modal'
import DateTimePreview from './components/form/date-time-preview'
import DateTimeSelector from './components/form/date-time-selector'
import DefaultSearchForm from './components/form/default-search-form'
import ErrorMessage from './components/form/error-message'
import GeneralSettingsPanel from './components/form/general-settings-panel'
import LocationField from './components/form/location-field'
import ModeSelector from './components/form/mode-selector'
import ModesPanel from './components/form/modes-panel'
import PlanTripButton from './components/form/plan-trip-button'
import SettingsPreview from './components/form/settings-preview'
import SettingsSelectorPanel from './components/form/settings-selector-panel'
import SwitchButton from './components/form/switch-button'

import BaseLayers from './components/map/base-layers'
import BaseMap from './components/map/base-map'
import BikeRentalOverlay from './components/map/bike-rental-overlay'
import DefaultMap from './components/map/default-map'
import EndpointsOverlay from './components/map/endpoints-overlay'
import ItineraryOverlay from './components/map/itinerary-overlay'
import Map from './components/map/map'
import TransitiveOverlay from './components/map/transitive-overlay'
import StylizedMap from './components/map/stylized-map'
import OsmBaseLayer from './components/map/osm-base-layer'
import StopsOverlay from './components/map/stops-overlay'
import RoutesOverlay from './components/map/routes-overlay'

import ItineraryCarousel from './components/narrative/itinerary-carousel'
import LegDiagramPreview from './components/narrative/leg-diagram-preview'
import NarrativeItineraries from './components/narrative/narrative-itineraries'
import NarrativeProfileOptions from './components/narrative/narrative-profile-options'
import NarrativeItinerary from './components/narrative/narrative-itinerary'
import NarrativeRoutingResults from './components/narrative/narrative-routing-results'
import RealtimeAnnotation from './components/narrative/realtime-annotation'
import TransportationNetworkCompanyLeg from './components/narrative/default/tnc-leg'
import TripDetails from './components/narrative/trip-details'
import TripTools from './components/narrative/trip-tools'

import MobileMain from './components/mobile/main'

import StopViewer from './components/viewers/stop-viewer'
import ViewStopButton from './components/viewers/view-stop-button'
import ViewTripButton from './components/viewers/view-trip-button'
import ViewerContainer from './components/viewers/viewer-container'

import ResponsiveWebapp from './components/app/responsive-webapp'
import AppMenu from './components/app/app-menu'

import { setAutoPlan, setMapCenter } from './actions/config'
import { getCurrentPosition } from './actions/location'
import { setLocationToCurrent, clearLocation } from './actions/map'
import { setUseRealtimeResponse } from './actions/narrative'
import { findNearbyStops } from './actions/api'

import createOtpReducer from './reducers/create-otp-reducer'

import otpUtils from './util'

export {
  // form components
  DateTimeModal,
  DateTimePreview,
  DateTimeSelector,
  DefaultSearchForm,
  ErrorMessage,
  GeneralSettingsPanel,
  LocationField,
  ModeSelector,
  ModesPanel,
  PlanTripButton,
  SettingsPreview,
  SettingsSelectorPanel,
  StylizedMap,
  SwitchButton,

  // map components
  BaseLayers,
  BaseMap,
  BikeRentalOverlay,
  DefaultMap,
  EndpointsOverlay,
  ItineraryCarousel,
  ItineraryOverlay,
  Map,
  OsmBaseLayer,
  StopsOverlay,
  RoutesOverlay,
  TransitiveOverlay,

  // narrative components
  LegDiagramPreview,
  NarrativeItineraries,
  NarrativeItinerary,
  NarrativeProfileOptions,
  NarrativeRoutingResults,
  RealtimeAnnotation,
  TransportationNetworkCompanyLeg,
  TripDetails,
  TripTools,

  // mobile compoments
  MobileMain,

  // viewer components
  StopViewer,
  ViewerContainer,
  ViewStopButton,
  ViewTripButton,

  // app components,
  ResponsiveWebapp,
  AppMenu,

  // actions
  clearLocation,
  findNearbyStops,
  getCurrentPosition,
  setAutoPlan,
  setLocationToCurrent,
  setMapCenter,
  setUseRealtimeResponse,

  // redux utilities
  createOtpReducer,

  // general utilities
  otpUtils
}
<|endoftext|>