<commit_before>
/**
 * Sticky.js
 * Library for sticky elements written in vanilla javascript. With this library you can easily set sticky elements on your website. It's also responsive.
 *
 * @version 1.1.5
 * @author Rafal Galus <biuro@rafalgalus.pl>
 * @website https://rgalus.github.io/sticky-js/
 * @repo https://github.com/rgalus/sticky-js
 * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE
 */

class Sticky {
  /**
   * Sticky instance constructor
   * @constructor
   * @param {string} selector - Selector which we can find elements
   * @param {string} options - Global options for sticky elements (could be overwritten by data-{option}="" attributes)
   */
  constructor(selector = '', options = {}) {
    this.selector = selector;
    this.elements = [];

    this.version = '1.1.5';

    this.vp = this.getViewportSize();
    this.scrollTop = this.getScrollTopPosition();

    this.options = {
      marginTop: options.marginTop || 0,
      stickyFor: options.stickFor || 0,
      stickyClass: options.stickyClass || null,
      stickyContainer: options.stickContainer || 'body',
    };

    this.run();
  }

  /**
   * Function that waits for page to be fully loaded and then renders & activates every sticky element found with specified selector
   * @function
   */
  run() {
    // wait for page to be fully loaded
    const pageLoaded = setInterval(() => {
      if (document.readyState === 'complete') {
        clearInterval(pageLoaded);

        const elements = document.querySelectorAll(this.selector);
        this.forEach(elements, (element) => this.renderElement(element));
      }
    }, 10);
  }

  /**
   * Function that assign needed variables for sticky element, that are used in future for calculations and other
   * @function
   * @param {node} element - Element to be rendered
   */
  renderElement(element) {
    // create container for variables needed in future
    element.sticky = {};

    // set default variables
    element.sticky.active = false;

    element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
    element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
    element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;

    element.sticky.container = this.getStickyContainer(element);
    element.sticky.container.rect = this.getRectangle(element.sticky.container);

    element.sticky.rect = this.getRectangle(element);

    // fix when element is image that has not yet loaded and width, height = 0
    if (element.tagName.toLowerCase === 'img') {
      element.onload = () => element.sticky.rect = this.getRectangle(element);
    }

    // activate rendered element
    this.activate(element);
  }

  /**
   * Function that activates element when specified conditions are met and then initalise events
   * @function
   * @param {node} element - Element to be activated
   */
   activate(element) {
    if (
      ((element.sticky.rect.top + element.sticky.rect.height) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
      && (element.sticky.stickyFor < this.vp.width)
      && !element.sticky.active
    ) {
      element.sticky.active = true;
    }

    if (this.elements.indexOf(element) < 0) {
      this.elements.push(element);
    }

    if (!element.sticky.resizeEvent) {
      this.initResizeEvents(element);
      element.sticky.resizeEvent = true;
    }

    if (!element.sticky.scrollEvent) {
      this.initScrollEvents(element);
      element.sticky.scrollEvent = true;
    }

    this.setPosition(element);
   }

  /**
   * Function which is adding onResizeEvents to window listener and assigns function to element as resizeListener
   * @function
   * @param {node} element - Element for which resize events are initialised
   */
   initResizeEvents(element) {
    element.sticky.resizeListener = () => this.onResizeEvents(element);
    window.addEventListener('resize', element.sticky.resizeListener);
   }

  /**
   * Function which is fired when user resize window. It checks if element should be activated or deactivated and then run setPosition function
   * @function
   * @param {node} element - Element for which event function is fired
   */
   onResizeEvents(element) {
    this.vp = this.getViewportSize();

    element.sticky.rect = this.getRectangle(element);
    element.sticky.container.rect = this.getRectangle(element.sticky.container);

    if (
      ((element.sticky.rect.top + element.sticky.rect.height) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
      && (element.sticky.stickyFor < this.vp.width)
      && !element.sticky.active
    ) {
      element.sticky.active = true;
    } else if (
      ((element.sticky.rect.top + element.sticky.rect.height) >= (element.sticky.container.rect.top + element.sticky.container.rect.height))
      || element.sticky.stickyFor >= this.vp.width
      && element.sticky.active
    ) {
      element.sticky.active = false;
    }

    this.setPosition(element);
   }

  /**
   * Function which is adding onScrollEvents to window listener and assigns function to element as scrollListener
   * @function
   * @param {node} element - Element for which scroll events are initialised
   */
   initScrollEvents(element) {
    element.sticky.scrollListener = () => this.onScrollEvents(element);
    window.addEventListener('scroll', element.sticky.scrollListener);
   }

  /**
   * Function which is fired when user scroll window. If element is active, function is invoking setPosition function
   * @function
   * @param {node} element - Element for which event function is fired
   */
   onScrollEvents(element) {
    this.scrollTop = this.getScrollTopPosition();

    if (element.sticky.active) {
      this.setPosition(element);
    }
   }

  /**
   * Main function for the library. Here are some condition calculations and css appending for sticky element when user scroll window
   * @function
   * @param {node} element - Element that will be positioned if it's active
   */
   setPosition(element) {
    this.css(element, { position: '', width: '', top: '', left: '' });

    if ((this.vp.height < element.sticky.rect.height) || !element.sticky.active) {
      return;
    }

    if (!element.sticky.rect.width) {
      element.sticky.rect = this.getRectangle(element);
    }

    if (
      element.sticky.rect.top === 0
      && element.sticky.container === document.querySelector('body')
    ) {
      this.css(element, {
        position: 'fixed',
        top: element.sticky.rect.top + 'px',
        left: element.sticky.rect.left + 'px',
        width: element.sticky.rect.width + 'px',
      });
    } else if (this.scrollTop > (element.sticky.rect.top - element.sticky.marginTop)) {
      this.css(element, {
        position: 'fixed',
        width: element.sticky.rect.width + 'px',
        left: element.sticky.rect.left + 'px',
      });

      if (
        (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop)
        > (element.sticky.container.rect.top + element.sticky.container.offsetHeight)
      ) {

        if (element.sticky.stickyClass) element.classList.remove(element.sticky.stickyClass);

        this.css(element, {
          top: (element.sticky.container.rect.top + element.sticky.container.offsetHeight) - (this.scrollTop + element.sticky.rect.height) + 'px' }
        );
      } else {
        if (element.sticky.stickyClass) element.classList.add(element.sticky.stickyClass);

        this.css(element, { top: element.sticky.marginTop + 'px' });
      }
    } else {
      if (element.sticky.stickyClass) element.classList.remove(element.sticky.stickyClass);

      this.css(element, { position: '', width: '', top: '', left: '' });
    }
   }

  /**
   * Function that updates element sticky rectangle (with sticky container), then activate or deactivate element, then update position if it's active
   * @function
   */
   update() {
    this.forEach(this.elements, (element) => {
      element.sticky.rect = this.getRectangle(element);
      element.sticky.container.rect = this.getRectangle(element.sticky.container);

      this.activate(element);
      this.setPosition(element);
    });
   }

  /**
   * Function that returns container element in which sticky element is stuck (if is not specified, then it's stuck to body)
   * @function
   * @param {node} element - Element which sticky container are looked for
   * @return {node} element - Sticky container
   */
   getStickyContainer(element) {
    let container = element;

    while (
      !container.hasAttribute('data-sticky-container')
      && !container.parentNode.querySelector(this.options.stickyContainer)
      && container !== document.querySelector('body')
    ) {
      container = container.parentNode;
    }

    return container;
   }

  /**
   * Function that returns element rectangle & position (width, height, top, left)
   * @function
   * @param {node} element - Element which position & rectangle are returned
   * @return {object}
   */
  getRectangle(element) {
    this.css(element, { position: '', width: '', top: '', left: '' });

    const width = Math.max(element.offsetWidth, element.clientWidth, element.scrollWidth);
    const height = Math.max(element.offsetHeight, element.clientHeight, element.scrollHeight);

    let top = 0;
    let left = 0;

    do {
      top += element.offsetTop || 0;
      left += element.offsetLeft || 0;
      element = element.offsetParent;
    } while(element);

    return { top, left, width, height };
  }

  /**
   * Function that returns viewport dimensions
   * @function
   * @return {object}
   */
  getViewportSize() {
    return {
      width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
      height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
    };
  }

  /**
   * Function that returns scroll position offset from top
   * @function
   * @return {number}
   */
  getScrollTopPosition() {
    return (window.pageYOffset || document.scrollTop)  - (document.clientTop || 0) || 0;
  }

  /**
   * Helper function for loops
   * @helper
   * @param {array}
   * @param {function} callback - Callback function (no need for explanation)
   */
  forEach(array, callback) {
    for (let i = 0, len = array.length; i < len; i++) {
      callback(array[i]);
    }
  }

  /**
   * Helper function to add/remove css properties for specified element.
   * @helper
   * @param {node} element - DOM element
   * @param {object} properties - CSS properties that will be added/removed from specified element
   */
  css(element, properties) {
    for (let property in properties) {
      if (properties.hasOwnProperty(property)) {
        element.style[property] = properties[property];
      }
    }
  }
}

/**
 * Export function that supports AMD, CommonJS and Plain Browser.
 */
((root, factory) => {
  if (typeof exports !== 'undefined') {
    module.exports = factory;
  } else if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else {
    root.Sticky = factory;
  }
})(this, Sticky);
<commit_msg>Fix typo<commit_after>
/**
 * Sticky.js
 * Library for sticky elements written in vanilla javascript. With this library you can easily set sticky elements on your website. It's also responsive.
 *
 * @version 1.1.5
 * @author Rafal Galus <biuro@rafalgalus.pl>
 * @website https://rgalus.github.io/sticky-js/
 * @repo https://github.com/rgalus/sticky-js
 * @license https://github.com/rgalus/sticky-js/blob/master/LICENSE
 */

class Sticky {
  /**
   * Sticky instance constructor
   * @constructor
   * @param {string} selector - Selector which we can find elements
   * @param {string} options - Global options for sticky elements (could be overwritten by data-{option}="" attributes)
   */
  constructor(selector = '', options = {}) {
    this.selector = selector;
    this.elements = [];

    this.version = '1.1.5';

    this.vp = this.getViewportSize();
    this.scrollTop = this.getScrollTopPosition();

    this.options = {
      marginTop: options.marginTop || 0,
      stickyFor: options.stickFor || 0,
      stickyClass: options.stickyClass || null,
      stickyContainer: options.stickyContainer || 'body',
    };

    this.run();
  }

  /**
   * Function that waits for page to be fully loaded and then renders & activates every sticky element found with specified selector
   * @function
   */
  run() {
    // wait for page to be fully loaded
    const pageLoaded = setInterval(() => {
      if (document.readyState === 'complete') {
        clearInterval(pageLoaded);

        const elements = document.querySelectorAll(this.selector);
        this.forEach(elements, (element) => this.renderElement(element));
      }
    }, 10);
  }

  /**
   * Function that assign needed variables for sticky element, that are used in future for calculations and other
   * @function
   * @param {node} element - Element to be rendered
   */
  renderElement(element) {
    // create container for variables needed in future
    element.sticky = {};

    // set default variables
    element.sticky.active = false;

    element.sticky.marginTop = parseInt(element.getAttribute('data-margin-top')) || this.options.marginTop;
    element.sticky.stickyFor = parseInt(element.getAttribute('data-sticky-for')) || this.options.stickyFor;
    element.sticky.stickyClass = element.getAttribute('data-sticky-class') || this.options.stickyClass;

    element.sticky.container = this.getStickyContainer(element);
    element.sticky.container.rect = this.getRectangle(element.sticky.container);

    element.sticky.rect = this.getRectangle(element);

    // fix when element is image that has not yet loaded and width, height = 0
    if (element.tagName.toLowerCase === 'img') {
      element.onload = () => element.sticky.rect = this.getRectangle(element);
    }

    // activate rendered element
    this.activate(element);
  }

  /**
   * Function that activates element when specified conditions are met and then initalise events
   * @function
   * @param {node} element - Element to be activated
   */
   activate(element) {
    if (
      ((element.sticky.rect.top + element.sticky.rect.height) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
      && (element.sticky.stickyFor < this.vp.width)
      && !element.sticky.active
    ) {
      element.sticky.active = true;
    }

    if (this.elements.indexOf(element) < 0) {
      this.elements.push(element);
    }

    if (!element.sticky.resizeEvent) {
      this.initResizeEvents(element);
      element.sticky.resizeEvent = true;
    }

    if (!element.sticky.scrollEvent) {
      this.initScrollEvents(element);
      element.sticky.scrollEvent = true;
    }

    this.setPosition(element);
   }

  /**
   * Function which is adding onResizeEvents to window listener and assigns function to element as resizeListener
   * @function
   * @param {node} element - Element for which resize events are initialised
   */
   initResizeEvents(element) {
    element.sticky.resizeListener = () => this.onResizeEvents(element);
    window.addEventListener('resize', element.sticky.resizeListener);
   }

  /**
   * Function which is fired when user resize window. It checks if element should be activated or deactivated and then run setPosition function
   * @function
   * @param {node} element - Element for which event function is fired
   */
   onResizeEvents(element) {
    this.vp = this.getViewportSize();

    element.sticky.rect = this.getRectangle(element);
    element.sticky.container.rect = this.getRectangle(element.sticky.container);

    if (
      ((element.sticky.rect.top + element.sticky.rect.height) < (element.sticky.container.rect.top + element.sticky.container.rect.height))
      && (element.sticky.stickyFor < this.vp.width)
      && !element.sticky.active
    ) {
      element.sticky.active = true;
    } else if (
      ((element.sticky.rect.top + element.sticky.rect.height) >= (element.sticky.container.rect.top + element.sticky.container.rect.height))
      || element.sticky.stickyFor >= this.vp.width
      && element.sticky.active
    ) {
      element.sticky.active = false;
    }

    this.setPosition(element);
   }

  /**
   * Function which is adding onScrollEvents to window listener and assigns function to element as scrollListener
   * @function
   * @param {node} element - Element for which scroll events are initialised
   */
   initScrollEvents(element) {
    element.sticky.scrollListener = () => this.onScrollEvents(element);
    window.addEventListener('scroll', element.sticky.scrollListener);
   }

  /**
   * Function which is fired when user scroll window. If element is active, function is invoking setPosition function
   * @function
   * @param {node} element - Element for which event function is fired
   */
   onScrollEvents(element) {
    this.scrollTop = this.getScrollTopPosition();

    if (element.sticky.active) {
      this.setPosition(element);
    }
   }

  /**
   * Main function for the library. Here are some condition calculations and css appending for sticky element when user scroll window
   * @function
   * @param {node} element - Element that will be positioned if it's active
   */
   setPosition(element) {
    this.css(element, { position: '', width: '', top: '', left: '' });

    if ((this.vp.height < element.sticky.rect.height) || !element.sticky.active) {
      return;
    }

    if (!element.sticky.rect.width) {
      element.sticky.rect = this.getRectangle(element);
    }

    if (
      element.sticky.rect.top === 0
      && element.sticky.container === document.querySelector('body')
    ) {
      this.css(element, {
        position: 'fixed',
        top: element.sticky.rect.top + 'px',
        left: element.sticky.rect.left + 'px',
        width: element.sticky.rect.width + 'px',
      });
    } else if (this.scrollTop > (element.sticky.rect.top - element.sticky.marginTop)) {
      this.css(element, {
        position: 'fixed',
        width: element.sticky.rect.width + 'px',
        left: element.sticky.rect.left + 'px',
      });

      if (
        (this.scrollTop + element.sticky.rect.height + element.sticky.marginTop)
        > (element.sticky.container.rect.top + element.sticky.container.offsetHeight)
      ) {

        if (element.sticky.stickyClass) element.classList.remove(element.sticky.stickyClass);

        this.css(element, {
          top: (element.sticky.container.rect.top + element.sticky.container.offsetHeight) - (this.scrollTop + element.sticky.rect.height) + 'px' }
        );
      } else {
        if (element.sticky.stickyClass) element.classList.add(element.sticky.stickyClass);

        this.css(element, { top: element.sticky.marginTop + 'px' });
      }
    } else {
      if (element.sticky.stickyClass) element.classList.remove(element.sticky.stickyClass);

      this.css(element, { position: '', width: '', top: '', left: '' });
    }
   }

  /**
   * Function that updates element sticky rectangle (with sticky container), then activate or deactivate element, then update position if it's active
   * @function
   */
   update() {
    this.forEach(this.elements, (element) => {
      element.sticky.rect = this.getRectangle(element);
      element.sticky.container.rect = this.getRectangle(element.sticky.container);

      this.activate(element);
      this.setPosition(element);
    });
   }

  /**
   * Function that returns container element in which sticky element is stuck (if is not specified, then it's stuck to body)
   * @function
   * @param {node} element - Element which sticky container are looked for
   * @return {node} element - Sticky container
   */
   getStickyContainer(element) {
    let container = element;

    while (
      !container.hasAttribute('data-sticky-container')
      && !container.parentNode.querySelector(this.options.stickyContainer)
      && container !== document.querySelector('body')
    ) {
      container = container.parentNode;
    }

    return container;
   }

  /**
   * Function that returns element rectangle & position (width, height, top, left)
   * @function
   * @param {node} element - Element which position & rectangle are returned
   * @return {object}
   */
  getRectangle(element) {
    this.css(element, { position: '', width: '', top: '', left: '' });

    const width = Math.max(element.offsetWidth, element.clientWidth, element.scrollWidth);
    const height = Math.max(element.offsetHeight, element.clientHeight, element.scrollHeight);

    let top = 0;
    let left = 0;

    do {
      top += element.offsetTop || 0;
      left += element.offsetLeft || 0;
      element = element.offsetParent;
    } while(element);

    return { top, left, width, height };
  }

  /**
   * Function that returns viewport dimensions
   * @function
   * @return {object}
   */
  getViewportSize() {
    return {
      width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
      height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
    };
  }

  /**
   * Function that returns scroll position offset from top
   * @function
   * @return {number}
   */
  getScrollTopPosition() {
    return (window.pageYOffset || document.scrollTop)  - (document.clientTop || 0) || 0;
  }

  /**
   * Helper function for loops
   * @helper
   * @param {array}
   * @param {function} callback - Callback function (no need for explanation)
   */
  forEach(array, callback) {
    for (let i = 0, len = array.length; i < len; i++) {
      callback(array[i]);
    }
  }

  /**
   * Helper function to add/remove css properties for specified element.
   * @helper
   * @param {node} element - DOM element
   * @param {object} properties - CSS properties that will be added/removed from specified element
   */
  css(element, properties) {
    for (let property in properties) {
      if (properties.hasOwnProperty(property)) {
        element.style[property] = properties[property];
      }
    }
  }
}

/**
 * Export function that supports AMD, CommonJS and Plain Browser.
 */
((root, factory) => {
  if (typeof exports !== 'undefined') {
    module.exports = factory;
  } else if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else {
    root.Sticky = factory;
  }
})(this, Sticky);
<|endoftext|>


<commit_before>'use strict'

var Duplex = require('stream').Duplex
var inherits = require('inherits')
var PassThrough = require('stream').PassThrough

var debug = require('debug')
var debugLog = debug('1tp:transports:streams:proxy')
var errorLog = debug('1tp:transports:streams:proxy:error')

function ProxyStream () {
  if (!(this instanceof ProxyStream)) {
    return new ProxyStream()
  }
  // init duplex
  Duplex.call(this, ProxyStream.DEFAULTS)
  // create inbound and outbound streams
  this._outboundPassThrough = new PassThrough()
  this._inboundPassThrough = new PassThrough()
  // listen to _inboundPassThrough
  var self = this
  this._inboundPassThrough
    .on('readable', function () {
      self._readFromInboundPassThrough()
    })
    .on('end', function () {
      self.push(null) // EOF
    })
  // done
  debugLog('created proxy stream.')
}

ProxyStream.DEFAULTS = {
  allowHalfOpen: true,
  readableObjectMode: false,
  writableObjectMode: false
}

inherits(ProxyStream, Duplex)

ProxyStream.prototype.connectStream = function (stream) {
  this._connectedStream = stream
  this._outboundPassThrough.pipe(this._connectedStream)
  this._connectedStream.pipe(this._inboundPassThrough)
}

ProxyStream.prototype._read = function (size) {
  this._readFromInboundPassThrough(size)
}

ProxyStream.prototype._readFromInboundPassThrough = function (size) {
  var chunk
  while ((chunk = this._inboundPassThrough.read(size)) !== null) {
    // if push returns false, stop writing
    if (!this.push(chunk)) {
      break
    }
  }
}

ProxyStream.prototype._write = function (chunk, encoding, done) {
  if (!this._connectedStream) {
    var noStreamError = 'no connected stream to write data to.'
    errorLog(noStreamError)
    throw new Error(noStreamError)
  }
  this._outboundPassThrough.write(chunk, encoding, done)
}

ProxyStream.prototype.end = function () {
  this._outboundPassThrough.end()
  var self = this
  process.nextTick(function () {
    self._end()
  })
}

ProxyStream.prototype._end = function () {
  // end writestream
  ProxyStream.super_.prototype.end.call(this)
}

module.exports = ProxyStream
<commit_msg>replaced require('inherits') with require('util').inherits<commit_after>'use strict'

var Duplex = require('stream').Duplex
var inherits = require('util').inherits
var PassThrough = require('stream').PassThrough

var debug = require('debug')
var debugLog = debug('1tp:transports:streams:proxy')
var errorLog = debug('1tp:transports:streams:proxy:error')

function ProxyStream () {
  if (!(this instanceof ProxyStream)) {
    return new ProxyStream()
  }
  // init duplex
  Duplex.call(this, ProxyStream.DEFAULTS)
  // create inbound and outbound streams
  this._outboundPassThrough = new PassThrough()
  this._inboundPassThrough = new PassThrough()
  // listen to _inboundPassThrough
  var self = this
  this._inboundPassThrough
    .on('readable', function () {
      self._readFromInboundPassThrough()
    })
    .on('end', function () {
      self.push(null) // EOF
    })
  // done
  debugLog('created proxy stream.')
}

ProxyStream.DEFAULTS = {
  allowHalfOpen: true,
  readableObjectMode: false,
  writableObjectMode: false
}

inherits(ProxyStream, Duplex)

ProxyStream.prototype.connectStream = function (stream) {
  this._connectedStream = stream
  this._outboundPassThrough.pipe(this._connectedStream)
  this._connectedStream.pipe(this._inboundPassThrough)
}

ProxyStream.prototype._read = function (size) {
  this._readFromInboundPassThrough(size)
}

ProxyStream.prototype._readFromInboundPassThrough = function (size) {
  var chunk
  while ((chunk = this._inboundPassThrough.read(size)) !== null) {
    // if push returns false, stop writing
    if (!this.push(chunk)) {
      break
    }
  }
}

ProxyStream.prototype._write = function (chunk, encoding, done) {
  if (!this._connectedStream) {
    var noStreamError = 'no connected stream to write data to.'
    errorLog(noStreamError)
    throw new Error(noStreamError)
  }
  this._outboundPassThrough.write(chunk, encoding, done)
}

ProxyStream.prototype.end = function () {
  this._outboundPassThrough.end()
  var self = this
  process.nextTick(function () {
    self._end()
  })
}

ProxyStream.prototype._end = function () {
  // end writestream
  ProxyStream.super_.prototype.end.call(this)
}

module.exports = ProxyStream
<|endoftext|>


<commit_before>
/**
 * Dependenices
 */

var _ = require('underscore');

/**
 * Namespace AST
 */

var AST = {};

/**
 * AST class representing a sentence
 *
 * @param {String} sentence
 * @constructor
 */

AST.Sentence = function(string) {
  this.string = string;
};

/**
 * AST class representing a variable
 *
 * @param {String} variable
 * @constructor
 */

AST.Variable = function(name) {
  this.name = name;
};

/**
 * AST class representing an ICU NumberFormat
 *
 * @param {String} variable
 * @param {String} argument
 * @constructor
 */

AST.NumberFormat = function(variable, argument) {
  this.variable = variable;
  this.argument = argument;
  this.format = {
    positive: null,
    negative: null
  };
  this._parseArgument(argument);
};

/**
 * NumberFormat syntaxes
 *
 * @type {Enum}
 */

AST.NumberFormat.prototype.Syntaxes = {
  NUMBER_SIMPLE_ARGUMENTS: /^(integer|currency|percent)$/,
  NUMBER_CHARACTER: /[#0-9\.E@\,\+\-;]/,
  SIGNIFICANT_PATTERN: /^(#*)(@+)(#*)$/,
  EXPONENT_PATTERN: /^E(\+?)(0+)$/,
  FRACTION_PATTERN: /^(0*)(#*)$/,
  INTEGER_PATTERN: /^(#*)(0+)$/,
  GROUP_SIZE_PATTERN: /(\,[0#]+)?\,([0#]+)(\.[0#]+)?$/
};

/**
 * Create format object representing a NumberFormat. A base class for
 * FloatingnumberFormat and SignificantNumberFormat.
 *
 * @class FloatingNumberFormat
 * @constructor
 * @api private
 */

AST.NumberFormat._NumberFormat = function(attributes) {
  this.prefix = attributes.prefix;
  this.suffix = attributes.suffix;
  this.paddingCharacter = typeof attributes.paddingCharacter !== 'undefined' ? attributes.paddingCharacter : null;
  this.groupSize = typeof attributes.groupSize !== 'undefined' ? attributes.groupSize : null;
  this.percentage = typeof attributes.percentage !== 'undefined' ? attributes.percentage : null;
  this.permille = typeof attributes.permille !== 'undefined' ? attributes.permille : null;
  this.currency = typeof attributes.currency !== 'undefined' ? attributes.currency : null;
  this.formatLength = attributes.formatLength;
};

/**
 * Create format object representing a FloatingNumberFormat.
 *
 * @class FloatingNumberFormat
 * @constructor
 * @api private
 */

AST.NumberFormat._FloatingNumberFormat = function(attributes) {
  AST.NumberFormat._NumberFormat.apply(this, arguments);

  this.exponent = typeof attributes.exponent !== 'undefined' ? attributes.exponent : null;
  this.fraction = typeof attributes.fraction !== 'undefined' ? attributes.fraction : null;
  this.integer = attributes.integer;
};

AST.NumberFormat._FloatingNumberFormat.prototype = Object.create(AST.NumberFormat._NumberFormat.prototype);

/**
 * Create format object representing a SignificantFormat.
 *
 * @class SignificantNumberFormat
 * @constructor
 * @api private
 */

AST.NumberFormat._SignificantNumberFormat = function(attributes) {
  AST.NumberFormat._NumberFormat.apply(this, arguments);

  this.leftAbsentNumbers = attributes.leftAbsentNumbers;
  this.nonAbsentNumbers = attributes.nonAbsentNumbers;
  this.rightAbsentNumbers = attributes.rightAbsentNumbers;
};

AST.NumberFormat._SignificantNumberFormat.prototype = Object.create(AST.NumberFormat._NumberFormat.prototype);

/**
 * Parse argument and sets format
 *
 * @param {String} argument
 * @return{void}
 * @api private
 */

AST.NumberFormat.prototype._parseArgument = function(argument) {
  var _this = this
    , numberPatterns = argument;

  if(this.Syntaxes.NUMBER_SIMPLE_ARGUMENTS.test(numberPatterns)) {
    // Valid pattern
    return;
  }
  else {
    var positive = true;
    numberPatterns.split(';').forEach(function(numberPattern) {
      var attributes = {};

      numberPattern = _this._setPrefixesAndSuffixAttributes(numberPattern, attributes);

      if(_this.Syntaxes.SIGNIFICANT_PATTERN.test(numberPattern)) {
        _this._setSignificantNumberFormat(numberPattern, positive);
        positive = false;

        // Valid pattern
        return;
      }

      var floatAndExponentPattern = numberPattern.split('E');
      if(floatAndExponentPattern.length <= 2) {
        if(floatAndExponentPattern.length === 2) {
          attributes.exponent = _this._getExponentAttributes('E' + floatAndExponentPattern[1]);
        }

        var integerAndFractionPattern = floatAndExponentPattern[0].split('.');
        if(integerAndFractionPattern.length <= 2) {
          if(integerAndFractionPattern.length === 2) {
            attributes.fraction = _this._getFractionAttributes(integerAndFractionPattern[1]);
          }

          attributes.integer = _this._getIntegerAttributes(integerAndFractionPattern[0]);

          _this._setFloatingNumberFormat(attributes, positive);

          positive = false;

          // Valid pattern
          return;
        }
        else {
          throw new TypeError('Expected only one \'.\' in your number pattern in your NumberFormat argument, got ' + (integerAndFractionPattern.length - 1) + ' \'.\':s in ' + numberPattern);
        }
      }
      else {
        throw new TypeError('Expected only one \'E\' in your exponent pattern in your NumberFormat argument, got ' + (floatAndExponentPattern.length - 1)+ ' \'E\':s in ' + numberPattern);
      }
    });
  }
};

/**
 * Get integer attributes from an integerAndFractionPattern
 *
 * @param {String} integerAndFractionPattern
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._getIntegerAttributes = function(integerAndFractionPattern) {
  if(!this.Syntaxes.INTEGER_PATTERN.test(integerAndFractionPattern)) {
    throw new TypeError('Expected a valid integer pattern (/^#*0+$/) in your NumberFormat argument, got (' + integerAndFractionPattern[0] + ') in '  + numberPattern);
  }

  var pattern = this.Syntaxes.INTEGER_PATTERN.exec(integerAndFractionPattern);
  return {
    leftAbsentNumbers: pattern[1].length,
    nonAbsentNumbers: pattern[2].length
  };
};

/**
 * Get fraction attributes from an integerAndFractionPattern
 *
 * @param {String} integerAndFractionPattern
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._getFractionAttributes = function(integerAndFractionPattern) {
  if(!this.Syntaxes.FRACTION_PATTERN.test(integerAndFractionPattern)) {
    throw new TypeError('Expected a valid fraction pattern (/^0*#*$/) in your NumberFormat argument, got (' + integerAndFractionPattern[1] + ') in ' + numberPattern);
  }

  var pattern = this.Syntaxes.FRACTION_PATTERN.exec(integerAndFractionPattern);
  return {
    nonAbsentNumbers: pattern[1].length,
    rightAbsentNumbers: pattern[2].length
  };
};

/**
 * Get exponent attributes from a exponent pattern string
 *
 * @param {String} exponentPatter
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._getExponentAttributes = function(exponentPattern) {
  if(!this.Syntaxes.EXPONENT_PATTERN.test(exponentPattern)) {
    throw new TypeError('Expected a valid exponent pattern (/^E\\+?[0-9]+$/) in your NumberFormat argument, got (' + floatAndExponentPattern[1] + ') in ' + numberPattern);
  }

  var pattern = this.Syntaxes.EXPONENT_PATTERN.exec(exponentPattern);
  return {
    nonAbsentNumbers: pattern[2].length,
    showPositiveCharacter: !!pattern[1].length
  };
};

/**
 * Set floating number format on NumberFormat's property format.positive
 * or format.negative depending on if it is positive or not.
 *
 * @param {String} attributes
 * @param {Boolean} positive
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._setFloatingNumberFormat = function(attributes, positive) {
  var format = new AST.NumberFormat._FloatingNumberFormat(attributes);

  if(positive) {
    this.format.positive = format;
  }
  else {
    this.format.negative = format;
  }
};

/**
 * Set signifcant number format on NumberFormat's property format.positive
 * or format.negative depending on if it is positive or not.
 *
 * @param {String} numberPattern
 * @param {Boolean} positive
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._setSignificantNumberFormat = function(numberPattern, positive) {
  var pattern = this.Syntaxes.SIGNIFICANT_PATTERN.exec(numberPattern);
  var format = new AST.NumberFormat._SignificantNumberFormat({
    leftAbsentNumbers: pattern[1].length,
    nonAbsentNumbers: pattern[2].length,
    rightAbsentNumbers: pattern[2].length
  });

  if(positive) {
    this.format.positive = format;
  }
  else {
    this.format.negative = format;
  }
};

/**
 * Set prefixes and suffix attributes
 *
 * @info http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html
 * @param {String} numberPattern
 * @param {Object} attributes (passed by reference)
 * @return {String} the number string excluding prefix and suffix
 * @api private
 */

AST.NumberFormat.prototype._setPrefixesAndSuffixAttributes = function(numberPattern, attributes) {
  var result = ''
    , prefix = ''
    , suffix = ''
    , hasEncounterdNumberCharacters = false
    , currencyCharacterCounter = 0
    , index = 0
    , inQuote = false
    , setPaddingCharacter = false;

  for(var index = 0; index < numberPattern.length; index++) {
    // Check quote for escaping special characters
    if(numberPattern[index] === '\'') {
      if(inQuote) {
        inQuote = false;
        continue;
      }
      var hasEndingQuote =
        typeof numberPattern[index + 2] !== 'undefined' &&
        numberPattern[index + 2] === '\'';

      if(!inQuote && !hasEndingQuote) {
        throw new TypeError('Expected ending quote (\') in ' + numberPattern);
      }
      inQuote = true;
      continue;
    }

    // Padding character deferred setting
    if(setPaddingCharacter) {
      attributes.paddingCharacter = numberPattern[index];
      setPaddingCharacter = false;
      continue;
    }

    switch(numberPattern[index]) {
      case '*':
        // Defer padding character set
        setPaddingCharacter = true;
        continue;
      case '%':
        if(attributes.percentage) {
          throw new TypeError('Can not set double percentage character(%) in ' + numberPattern);
        }
        if(attributes.permille || attributes.currency) {
          throw new TypeError('Can not set percentage whenever permille or currency are set in ' + numberPattern);
        }
        if(!hasEncounterdNumberCharacters) {
          attributes.percentage = {
            position: 'prefix'
          };
        }
        else {
          attributes.percentage = {
            position: 'suffix'
          };
        }
        continue;
      case '‰':
        if(attributes.permille) {
          throw new TypeError('Can not set double permille character(‰) in ' + numberPattern);
        }
        if(attributes.percentage || attributes.currency) {
          throw new TypeError('Can not set permille whenever percentage or currency are set in ' + numberPattern);
        }
        if(!hasEncounterdNumberCharacters) {
          attributes.permille = {
            position: 'prefix'
          };
        }
        else {
          attributes.permille = {
            position: 'suffix'
          };
        }
        continue;
      case '¤':
        if(attributes.percentage || attributes.permille) {
          throw new TypeError('Can not set currency whenever percentage and permille are set in ' + numberPattern);
        }
        if(!hasEncounterdNumberCharacters) {
          currencyCharacterCounter++;
          attributes.currency = {
            position: 'prefix',
            characterLength: currencyCharacterCounter
          };
        }
        else {
          if(typeof attributes.currency !== 'undefined' &&
             typeof attributes.currency.position === 'prefix') {
            throw new TypeError('Can not set both a currency prefix and suffix in ' + numberPattern);
          }
          currencyCharacterCounter++;
          attributes.currency = {
            position: 'suffix',
            characterLength: currencyCharacterCounter
          };
        }
        continue;
    }

    if(this.Syntaxes.NUMBER_CHARACTER.test(numberPattern[index])) {
      hasEncounterdNumberCharacters = true;
      result += numberPattern[index];
      continue;
    }

    if(!hasEncounterdNumberCharacters) {
      prefix += numberPattern[index];
      continue;
    }
    else {
      suffix += numberPattern[index];
    }
  }

  // Set prefix and suffix
  attributes.prefix = prefix;
  attributes.suffix = suffix;

  // Calculate group size
  if(this.Syntaxes.GROUP_SIZE_PATTERN.test(result)) {
    var pattern = this.Syntaxes.GROUP_SIZE_PATTERN.exec(result);
    attributes.groupSize = {
      primary: pattern[1].length,
      // We subtract by one to remove one length unit caused by comma
      secondary: (pattern[0].length - 1) || pattern[1].length
    };
  }

  // Format length
  attributes.formatLength = prefix.length + result.length + suffix.length;

  return result;
};

/**
 * AST class representing an ICU SelectFormat
 *
 * @param {String} variable
 * @constructor
 */

AST.ChoiceFormat = function(variable, values) {
  this.variable = variable;
  this.values = values;
};

/**
 * AST class representing an ICU SelectFormat
 *
 * @param {String} variable
 * @constructor
 */

AST.SelectFormat = function(variable, values) {
  this.variable = variable;
  this.values = values;
};

/**
 * AST class representing an ICU PluralFormat
 *
 * @param {String} variable
 * @constructor
 */

AST.PluralFormat = function(variable, values, offset) {
  this.variable = variable;
  this.values = values;
  this.offset = offset;
};

/**
 * AST class representing an ICU PluralFormat count '#'
 *
 * @param {String} variable
 * @constructor
 */

AST.PluralRemaining = function() {};

module.exports = AST;
<commit_msg>Refactored AST.NumberFormat._parseArgument()<commit_after>
/**
 * Dependenices
 */

var _ = require('underscore');

/**
 * Namespace AST
 */

var AST = {};

/**
 * AST class representing a sentence
 *
 * @param {String} sentence
 * @constructor
 */

AST.Sentence = function(string) {
  this.string = string;
};

/**
 * AST class representing a variable
 *
 * @param {String} variable
 * @constructor
 */

AST.Variable = function(name) {
  this.name = name;
};

/**
 * AST class representing an ICU NumberFormat
 *
 * @param {String} variable
 * @param {String} argument
 * @constructor
 */

AST.NumberFormat = function(variable, argument) {
  this.variable = variable;
  this.argument = argument;
  this.format = {
    positive: null,
    negative: null
  };
  this._parseArgument(argument);
};

/**
 * NumberFormat syntaxes
 *
 * @type {Enum}
 */

AST.NumberFormat.prototype.Syntaxes = {
  NUMBER_SIMPLE_ARGUMENTS: /^(integer|currency|percent)$/,
  NUMBER_CHARACTER: /[#0-9\.E@\,\+\-;]/,
  SIGNIFICANT_PATTERN: /^(#*)(@+)(#*)$/,
  EXPONENT_PATTERN: /^E(\+?)(0+)$/,
  FRACTION_PATTERN: /^(0*)(#*)$/,
  INTEGER_PATTERN: /^(#*)(0+)$/,
  GROUP_SIZE_PATTERN: /(\,[0#]+)?\,([0#]+)(\.[0#]+)?$/
};

/**
 * Create format object representing a NumberFormat. A base class for
 * FloatingnumberFormat and SignificantNumberFormat.
 *
 * @class FloatingNumberFormat
 * @constructor
 * @api private
 */

AST.NumberFormat._NumberFormat = function(attributes) {
  this.prefix = attributes.prefix;
  this.suffix = attributes.suffix;
  this.paddingCharacter = typeof attributes.paddingCharacter !== 'undefined' ? attributes.paddingCharacter : null;
  this.groupSize = typeof attributes.groupSize !== 'undefined' ? attributes.groupSize : null;
  this.percentage = typeof attributes.percentage !== 'undefined' ? attributes.percentage : null;
  this.permille = typeof attributes.permille !== 'undefined' ? attributes.permille : null;
  this.currency = typeof attributes.currency !== 'undefined' ? attributes.currency : null;
  this.formatLength = attributes.formatLength;
};

/**
 * Create format object representing a FloatingNumberFormat.
 *
 * @class FloatingNumberFormat
 * @constructor
 * @api private
 */

AST.NumberFormat._FloatingNumberFormat = function(attributes) {
  AST.NumberFormat._NumberFormat.apply(this, arguments);

  this.exponent = typeof attributes.exponent !== 'undefined' ? attributes.exponent : null;
  this.fraction = typeof attributes.fraction !== 'undefined' ? attributes.fraction : null;
  this.integer = attributes.integer;
};

AST.NumberFormat._FloatingNumberFormat.prototype = Object.create(AST.NumberFormat._NumberFormat.prototype);

/**
 * Create format object representing a SignificantFormat.
 *
 * @class SignificantNumberFormat
 * @constructor
 * @api private
 */

AST.NumberFormat._SignificantNumberFormat = function(attributes) {
  AST.NumberFormat._NumberFormat.apply(this, arguments);

  this.leftAbsentNumbers = attributes.leftAbsentNumbers;
  this.nonAbsentNumbers = attributes.nonAbsentNumbers;
  this.rightAbsentNumbers = attributes.rightAbsentNumbers;
};

AST.NumberFormat._SignificantNumberFormat.prototype = Object.create(AST.NumberFormat._NumberFormat.prototype);

/**
 * Parse argument and sets format
 *
 * @param {String} argument
 * @return{void}
 * @api private
 */

AST.NumberFormat.prototype._parseArgument = function(argument) {
  var _this = this
    , numberPatterns = argument;

  if(this.Syntaxes.NUMBER_SIMPLE_ARGUMENTS.test(numberPatterns)) {
    // Valid pattern
    return;
  }
  else {
    var positive = true;
    numberPatterns.split(';').forEach(function(numberPattern) {
      var attributes = {};

      numberPattern = _this._setPrefixesAndSuffixAttributes(numberPattern, attributes);

      _this._handleSetNumberFormat(numberPattern, attributes, positive);

      positive = false;
    });
  }
};

/**
 * Handle set number format
 *
 * @param {String} numberPattern String that represents a number pattern
 * excluding prefix and suffix
 * @param {Object} attributes Object used during initialization of a
 * NumberFormat.NumberFormat
 * @param {Boolean} positive Set positive side or negative
 * @return {void}
 * @throws TypeError
 * @api private
 */

AST.NumberFormat.prototype._handleSetNumberFormat = function(numberPattern, attributes, positive) {
  if(this.Syntaxes.SIGNIFICANT_PATTERN.test(numberPattern)) {
    this._setSignificantNumberFormat(numberPattern, positive);
  }

  var floatAndExponentPattern = numberPattern.split('E');
  if(floatAndExponentPattern.length <= 2) {
    this._handleSetFloatingNumberFormat(floatAndExponentPattern, attributes, positive);
  }
  else {
    throw new TypeError('Expected only one \'E\' in your exponent pattern in your NumberFormat argument, got ' + (floatAndExponentPattern.length - 1)+ ' \'E\':s in ' + numberPattern);
  }
};

/**
 * Handle set floating number format
 *
 * @param {Array} floatAndExponentPattern An array representing float and
 * exponent pattern that is splitted using the string `E`
 * @param {Object} attributes Object used during initialization of a
 * NumberFormat.NumberFormat
 * @param {Boolean} positive
 * @return {void}
 * @throws TypeError
 * @api private
 */

AST.NumberFormat.prototype._handleSetFloatingNumberFormat = function(floatAndExponentPattern, attributes, positive) {
  if(floatAndExponentPattern.length === 2) {
    attributes.exponent = this._getExponentAttributes('E' + floatAndExponentPattern[1]);
  }

  var integerAndFractionPattern = floatAndExponentPattern[0].split('.');
  if(integerAndFractionPattern.length <= 2) {
    if(integerAndFractionPattern.length === 2) {
      attributes.fraction = this._getFractionAttributes(integerAndFractionPattern[1]);
    }

    attributes.integer = this._getIntegerAttributes(integerAndFractionPattern[0]);

    this._setFloatingNumberFormat(attributes, positive);
  }
  else {
    throw new TypeError('Expected only one \'.\' in your number pattern in your NumberFormat argument, got ' + (integerAndFractionPattern.length - 1) + ' \'.\':s in ' + numberPattern);
  }
};

/**
 * Get integer attributes from an integerAndFractionPattern
 *
 * @param {String} integerAndFractionPattern
 * @return {void}
 * @throws TypeError
 * @api private
 */

AST.NumberFormat.prototype._getIntegerAttributes = function(integerAndFractionPattern) {
  if(!this.Syntaxes.INTEGER_PATTERN.test(integerAndFractionPattern)) {
    throw new TypeError('Expected a valid integer pattern (/^#*0+$/) in your NumberFormat argument, got (' + integerAndFractionPattern[0] + ') in '  + numberPattern);
  }

  var pattern = this.Syntaxes.INTEGER_PATTERN.exec(integerAndFractionPattern);
  return {
    leftAbsentNumbers: pattern[1].length,
    nonAbsentNumbers: pattern[2].length
  };
};

/**
 * Get fraction attributes from an integerAndFractionPattern
 *
 * @param {String} integerAndFractionPattern
 * @return {void}
 * @throws TypeError
 * @api private
 */

AST.NumberFormat.prototype._getFractionAttributes = function(integerAndFractionPattern) {
  if(!this.Syntaxes.FRACTION_PATTERN.test(integerAndFractionPattern)) {
    throw new TypeError('Expected a valid fraction pattern (/^0*#*$/) in your NumberFormat argument, got (' + integerAndFractionPattern[1] + ') in ' + numberPattern);
  }

  var pattern = this.Syntaxes.FRACTION_PATTERN.exec(integerAndFractionPattern);
  return {
    nonAbsentNumbers: pattern[1].length,
    rightAbsentNumbers: pattern[2].length
  };
};

/**
 * Get exponent attributes from a exponent pattern string
 *
 * @param {String} exponentPatter
 * @return {void}
 * @throws TypeError
 * @api private
 */

AST.NumberFormat.prototype._getExponentAttributes = function(exponentPattern) {
  if(!this.Syntaxes.EXPONENT_PATTERN.test(exponentPattern)) {
    throw new TypeError('Expected a valid exponent pattern (/^E\\+?[0-9]+$/) in your NumberFormat argument, got (' + floatAndExponentPattern[1] + ') in ' + numberPattern);
  }

  var pattern = this.Syntaxes.EXPONENT_PATTERN.exec(exponentPattern);
  return {
    nonAbsentNumbers: pattern[2].length,
    showPositiveCharacter: !!pattern[1].length
  };
};

/**
 * Set floating number format on NumberFormat's property format.positive
 * or format.negative depending on if it is positive or not.
 *
 * @param {String} attributes
 * @param {Boolean} positive
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._setFloatingNumberFormat = function(attributes, positive) {
  var format = new AST.NumberFormat._FloatingNumberFormat(attributes);

  if(positive) {
    this.format.positive = format;
  }
  else {
    this.format.negative = format;
  }
};

/**
 * Set signifcant number format on NumberFormat's property format.positive
 * or format.negative depending on if it is positive or not.
 *
 * @param {String} numberPattern
 * @param {Boolean} positive
 * @return {void}
 * @api private
 */

AST.NumberFormat.prototype._setSignificantNumberFormat = function(numberPattern, positive) {
  var pattern = this.Syntaxes.SIGNIFICANT_PATTERN.exec(numberPattern);
  var format = new AST.NumberFormat._SignificantNumberFormat({
    leftAbsentNumbers: pattern[1].length,
    nonAbsentNumbers: pattern[2].length,
    rightAbsentNumbers: pattern[2].length
  });

  if(positive) {
    this.format.positive = format;
  }
  else {
    this.format.negative = format;
  }
};

/**
 * Set prefixes and suffix attributes
 *
 * @info http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html
 * @param {String} numberPattern
 * @param {Object} attributes (passed by reference)
 * @return {String} the number string excluding prefix and suffix
 * @api private
 */

AST.NumberFormat.prototype._setPrefixesAndSuffixAttributes = function(numberPattern, attributes) {
  var result = ''
    , prefix = ''
    , suffix = ''
    , hasEncounterdNumberCharacters = false
    , currencyCharacterCounter = 0
    , index = 0
    , inQuote = false
    , setPaddingCharacter = false;

  for(var index = 0; index < numberPattern.length; index++) {
    // Check quote for escaping special characters
    if(numberPattern[index] === '\'') {
      if(inQuote) {
        inQuote = false;
        continue;
      }
      var hasEndingQuote =
        typeof numberPattern[index + 2] !== 'undefined' &&
        numberPattern[index + 2] === '\'';

      if(!inQuote && !hasEndingQuote) {
        throw new TypeError('Expected ending quote (\') in ' + numberPattern);
      }
      inQuote = true;
      continue;
    }

    // Padding character deferred setting
    if(setPaddingCharacter) {
      attributes.paddingCharacter = numberPattern[index];
      setPaddingCharacter = false;
      continue;
    }

    switch(numberPattern[index]) {
      case '*':
        // Defer padding character set
        setPaddingCharacter = true;
        continue;
      case '%':
        if(attributes.percentage) {
          throw new TypeError('Can not set double percentage character(%) in ' + numberPattern);
        }
        if(attributes.permille || attributes.currency) {
          throw new TypeError('Can not set percentage whenever permille or currency are set in ' + numberPattern);
        }
        if(!hasEncounterdNumberCharacters) {
          attributes.percentage = {
            position: 'prefix'
          };
        }
        else {
          attributes.percentage = {
            position: 'suffix'
          };
        }
        continue;
      case '‰':
        if(attributes.permille) {
          throw new TypeError('Can not set double permille character(‰) in ' + numberPattern);
        }
        if(attributes.percentage || attributes.currency) {
          throw new TypeError('Can not set permille whenever percentage or currency are set in ' + numberPattern);
        }
        if(!hasEncounterdNumberCharacters) {
          attributes.permille = {
            position: 'prefix'
          };
        }
        else {
          attributes.permille = {
            position: 'suffix'
          };
        }
        continue;
      case '¤':
        if(attributes.percentage || attributes.permille) {
          throw new TypeError('Can not set currency whenever percentage and permille are set in ' + numberPattern);
        }
        if(!hasEncounterdNumberCharacters) {
          currencyCharacterCounter++;
          attributes.currency = {
            position: 'prefix',
            characterLength: currencyCharacterCounter
          };
        }
        else {
          if(typeof attributes.currency !== 'undefined' &&
             typeof attributes.currency.position === 'prefix') {
            throw new TypeError('Can not set both a currency prefix and suffix in ' + numberPattern);
          }
          currencyCharacterCounter++;
          attributes.currency = {
            position: 'suffix',
            characterLength: currencyCharacterCounter
          };
        }
        continue;
    }

    if(this.Syntaxes.NUMBER_CHARACTER.test(numberPattern[index])) {
      hasEncounterdNumberCharacters = true;
      result += numberPattern[index];
      continue;
    }

    if(!hasEncounterdNumberCharacters) {
      prefix += numberPattern[index];
      continue;
    }
    else {
      suffix += numberPattern[index];
    }
  }

  // Set prefix and suffix
  attributes.prefix = prefix;
  attributes.suffix = suffix;

  // Calculate group size
  if(this.Syntaxes.GROUP_SIZE_PATTERN.test(result)) {
    var pattern = this.Syntaxes.GROUP_SIZE_PATTERN.exec(result);
    attributes.groupSize = {
      primary: pattern[1].length,
      // We subtract by one to remove one length unit caused by comma
      secondary: (pattern[0].length - 1) || pattern[1].length
    };
  }

  // Format length
  attributes.formatLength = prefix.length + result.length + suffix.length;

  return result;
};

/**
 * AST class representing an ICU SelectFormat
 *
 * @param {String} variable
 * @constructor
 */

AST.ChoiceFormat = function(variable, values) {
  this.variable = variable;
  this.values = values;
};

/**
 * AST class representing an ICU SelectFormat
 *
 * @param {String} variable
 * @constructor
 */

AST.SelectFormat = function(variable, values) {
  this.variable = variable;
  this.values = values;
};

/**
 * AST class representing an ICU PluralFormat
 *
 * @param {String} variable
 * @constructor
 */

AST.PluralFormat = function(variable, values, offset) {
  this.variable = variable;
  this.values = values;
  this.offset = offset;
};

/**
 * AST class representing an ICU PluralFormat count '#'
 *
 * @param {String} variable
 * @constructor
 */

AST.PluralRemaining = function() {};

module.exports = AST;
<|endoftext|>


<commit_before>import {Motion} from 'react-motion';
import React, {Component} from 'react';

class SceneNavigator extends Component{
    constructor(props) {
        super(props);
        var {state, data, url} = props.stateNavigator.stateContext;
        this.state = {scenes: {[url]: state.renderScene(data)}};
    }
    componentDidMount() {
        var {stateNavigator} = this.props;
        stateNavigator.onNavigate((oldState, state, data, asyncData) => {
            this.setState((prevState) => {
                var {url, crumbs} = stateNavigator.stateContext;
                var scenes = {[url]: state.renderScene(data, asyncData)};
                for(var i = 0; i < crumbs.length; i++) {
                    scenes[crumbs[i].url] = prevState.scenes[crumbs[i].url];
                }
                return {scenes};
            });
        });
    }
    render() {
        var {oldState, state, data, url, crumbs} = this.props.stateNavigator.stateContext;
        var {getDefaultStyle, getStyle, interpolateStyle} = this.props;
        var scenes = crumbs.concat({state, data, url, show: true})
            .map(({state, data, url, show}) =>
                <Motion key={url} defaultStyle={getDefaultStyle(state, data)}
                    style={getStyle(!!show, state, data)}>
                    {(interpolatingStyle) =>

                            {this.state.scenes[url]}

                    }
                </Motion>);
        return
{scenes}
;
    }
}

export default SceneNavigator;
<commit_msg>Set show to avoid falsy value<commit_after>import {Motion} from 'react-motion';
import React, {Component} from 'react';

class SceneNavigator extends Component{
    constructor(props) {
        super(props);
        var {state, data, url} = props.stateNavigator.stateContext;
        this.state = {scenes: {[url]: state.renderScene(data)}};
    }
    componentDidMount() {
        var {stateNavigator} = this.props;
        stateNavigator.onNavigate((oldState, state, data, asyncData) => {
            this.setState((prevState) => {
                var {url, crumbs} = stateNavigator.stateContext;
                var scenes = {[url]: state.renderScene(data, asyncData)};
                for(var i = 0; i < crumbs.length; i++) {
                    scenes[crumbs[i].url] = prevState.scenes[crumbs[i].url];
                }
                return {scenes};
            });
        });
    }
    render() {
        var {oldState, state, data, url, crumbs} = this.props.stateNavigator.stateContext;
        var {getDefaultStyle, getStyle, interpolateStyle} = this.props;
        var scenes = crumbs.map(crumb => ({...crumb, show: false}))
            .concat({state, data, url, show: true})
            .map(({state, data, url, show}) =>
                <Motion key={url} defaultStyle={getDefaultStyle(state, data)}
                    style={getStyle(show, state, data)}>
                    {(interpolatingStyle) =>

                            {this.state.scenes[url]}

                    }
                </Motion>);
        return
{scenes}
;
    }
}

export default SceneNavigator;
<|endoftext|>


<commit_before>var Whitespace = require('./whitespace');

var output = [];
var whitespace = null;

module.exports = function(node, options) {
	output.length = 0;
	whitespace = new Whitespace(options, token);
	generate(node);
	whitespace = null;
	return output.join('');
}

var types = {
	'root': gen_root,
	'type': gen_type,
	'preprocessor': gen_preprocessor,
	'identifier': gen_identifier,
	'operator': gen_operator,
	'parameter': gen_parameter,
	'function_declaration': gen_function,
	'function_call': gen_function_call,
	'scope': gen_scope,
	'declarator': gen_declarator,
	'declarator_item': gen_declarator_item,
	'expression': gen_expression,
	'binary': gen_binary,
	'unary': gen_unary,
	'ternary': gen_ternary,
	'postfix': gen_postfix,
	'field_selector': gen_field_selector,
	'precision': gen_precision,
	'accessor': gen_accessor,
	'if_statement': gen_if_statement,
	'for_statement': gen_for_statement,
	'while_statement': gen_while_statement,
	'do_statement': gen_do_statement,
	'continue': gen_continue,
	'break': gen_break,
	'return': gen_return,
	'discard': gen_discard,
	'float': gen_float,
	'int': gen_int,
	'bool': gen_bool,
};

function gen_bool(node) {
	token(node.value);
}

var noTerminator = {
	'preprocessor': true,
	'function_declaration' : true,
	'if_statement' : true,
	'for_statement' : true,
	'while_statement' : true,
	'scope': true
};

function token(s) {
	output.push(s);
}

function list_parameters(a) {
	for (var i=0; i<a.length; i++) {
		generate(a[i]);
		if (i < a.length-1)
			whitespace.separator();
	}
}

function list_statements(a) {
	for (var i=0; i<a.length; i++) {
		if (a[i].type == 'expression' && !a[i].expression)
			continue;
		whitespace.tab();
		generate(a[i]);
		if (!(a[i].type in noTerminator))
			whitespace.terminateLine();
	}
}

function generate(node) {
	if (!node)
		return;
	var fn = types[node.type];
	if (!fn) {
		return console.warn('Warning: Encountered an AST node that has no generator:', node.type);
	}
	return fn(node);
}

function gen_root(node) {
	list_statements(node.statements);
}

function gen_type(node) {
	if (node.qualifier) {
		token(node.qualifier);
		whitespace.space();
	}
	token(node.name);
}

function gen_preprocessor(node) {
	switch (node.directive) {
		case '#define':
			token(node.directive);
			whitespace.space();
			token(node.identifier);
			whitespace.space();
			token(node.token_string);
			whitespace.newline();
			break;

		case '#ifdef':
		case '#ifndef':
		case '#if':
		case '#elif':
			token(node.directive);
			whitespace.space();
			token(node.value);
			whitespace.newline();
			list_statements(node.guarded_statements);
			if ('elseBody' in node)
				generate(node.elseBody);
			else {
				token('#endif');
				whitespace.newline();
			}
			break;
		case '#else':
			token(node.directive);
			whitespace.newline();
			list_statements(node.guarded_statements);
			token('#endif');
			whitespace.newline();
			break;

		case '#version':
		case '#undef':
		case '#pragma':
		case '#extension':
		case '#line':
		case '#error':
		case '#include':
			token(node.directive);
			whitespace.space();
			token(node.value);
			whitespace.newline();
			break;
	}
}

function gen_identifier(node) {
	token(node.name);
}

function gen_operator(node) {
	token(node.operator);
}

function gen_declarator(node) {
	generate(node.typeAttribute);
	whitespace.space();
	list_parameters(node.declarators);
}

function gen_declarator_item(node) {
	generate(node.name);
	if ('initializer' in node) {
		whitespace.space();
		token('=');
		whitespace.space();
		generate(node.initializer);
	}
}

function gen_expression(node) {
	generate(node.expression);
}

function gen_binary(node) {
	switch (node.operator.operator) {
		case '=':
			generate(node.left);
			whitespace.space();
			generate(node.operator);
			whitespace.space();
			generate(node.right);
			break;

		default:
			if (node.left.type == 'binary') {
				token('(');
				generate(node.left);
				token(')');
			}
			else {
				generate(node.left);
			}

			whitespace.space();
			generate(node.operator);
			whitespace.space();

			if (node.right.type == 'binary') {
				token('(');
				generate(node.right);
				token(')');
			}
			else {
				generate(node.right);
			}
			break;
	}
}

function gen_unary(node) {
	generate(node.operator);
	generate(node.expression);
}

function gen_parameter(node) {
	if (node.typeQualifier) {
		token(node.typeQualifier);
		whitespace.space(true);
	}
	if (node.parameterQualifier) {
		token(node.parameterQualifier);
		whitespace.space(true);
	}
	token(node.type_name);
	whitespace.space(true);
	token(node.name);
}

function gen_function(node) {
	generate(node.returnType);
	whitespace.space();
	token(node.name);
	token('(');
	list_parameters(node.parameters);
	token(')');
	whitespace.space();
	generate(node.body);
}

function gen_function_call(node) {
	token(node.function_name);
	token('(');
	list_parameters(node.parameters);
	token(')');
}

function gen_scope(node, noNewline) {
	token('{');
	whitespace.newline();
	whitespace.indent();
	list_statements(node.statements);
	whitespace.dedent();
	whitespace.tab();
	token('}');
	if (!noNewline)
		whitespace.newline();
}

function gen_postfix(node) {
	generate(node.expression);
	generate(node.operator);
}

function gen_field_selector(node) {
	token('.');
	token(node.selection);
}

function gen_accessor(node) {
	token('[');
	generate(node.index);
	token(']');
}

function gen_float(node) {
	token(node.value);
	if (node.value % 1 == 0)
		token('.0');
}

function gen_int(node) {
	token(node.value);
}

function gen_bool(node) {
	token(node.value);
}

function gen_precision(node) {
	token(node.type);
	whitespace.space();
	token(node.precision);
	whitespace.space();
	token(node.typeName);
}

function gen_if_statement(node, isElseIf) {
	if (isElseIf) {
		whitespace.tab();
		token('elseif');
	}
	else token('if');
	whitespace.space();
	token('(');
	generate(node.condition);
	token(')');
	whitespace.space();
	generate(node.body);
	if (node.elseBody) {
		if (node.elseBody.type == 'if_statement') {
			gen_if_statement(node.elseBody, true);
		}
		else {
			whitespace.tab();
			token('else');
			whitespace.space();
			generate(node.elseBody);
		}
	}
}

function gen_return(node) {
	token('return');
	whitespace.space();
	generate(node.value);
}

function gen_ternary(node) {
	generate(node.condition);
	whitespace.space();
	token('?');
	whitespace.space();
	generate(node.is_true);
	whitespace.space();
	token(':');
	whitespace.space();
	generate(node.is_false);
}

function gen_for_statement(node) {
	token('for');
	whitespace.space();
	token('(');
	generate(node.initializer);
	whitespace.terminator();
	whitespace.space();
	generate(node.condition);
	whitespace.terminator();
	whitespace.space();
	generate(node.increment);
	token(')');
	whitespace.space();
	generate(node.body);
}

function gen_while_statement(node) {
	token('while');
	whitespace.space();
	token('(');
	generate(node.condition);
	token(')');
	whitespace.space();
	generate(node.body);
}

function gen_do_statement(node) {
	token('do');
	whitespace.space();
	gen_scope(node.body, true);
	whitespace.space();
	token('while');
	whitespace.space();
	token('(');
	generate(node.condition);
	token(')');
}

function gen_continue(node) {
	token('continue');
}

function gen_break(node) {
	token('break');
}

function gen_discard(node) {
	token('discard');
}
<commit_msg>Fix: More required spaces<commit_after>var Whitespace = require('./whitespace');

var output = [];
var whitespace = null;

module.exports = function(node, options) {
	output.length = 0;
	whitespace = new Whitespace(options, token);
	generate(node);
	whitespace = null;
	return output.join('');
}

var types = {
	'root': gen_root,
	'type': gen_type,
	'preprocessor': gen_preprocessor,
	'identifier': gen_identifier,
	'operator': gen_operator,
	'parameter': gen_parameter,
	'function_declaration': gen_function,
	'function_call': gen_function_call,
	'scope': gen_scope,
	'declarator': gen_declarator,
	'declarator_item': gen_declarator_item,
	'expression': gen_expression,
	'binary': gen_binary,
	'unary': gen_unary,
	'ternary': gen_ternary,
	'postfix': gen_postfix,
	'field_selector': gen_field_selector,
	'precision': gen_precision,
	'accessor': gen_accessor,
	'if_statement': gen_if_statement,
	'for_statement': gen_for_statement,
	'while_statement': gen_while_statement,
	'do_statement': gen_do_statement,
	'continue': gen_continue,
	'break': gen_break,
	'return': gen_return,
	'discard': gen_discard,
	'float': gen_float,
	'int': gen_int,
	'bool': gen_bool,
};

function gen_bool(node) {
	token(node.value);
}

var noTerminator = {
	'preprocessor': true,
	'function_declaration' : true,
	'if_statement' : true,
	'for_statement' : true,
	'while_statement' : true,
	'scope': true
};

function token(s) {
	output.push(s);
}

function list_parameters(a) {
	for (var i=0; i<a.length; i++) {
		generate(a[i]);
		if (i < a.length-1)
			whitespace.separator();
	}
}

function list_statements(a) {
	for (var i=0; i<a.length; i++) {
		if (a[i].type == 'expression' && !a[i].expression)
			continue;
		whitespace.tab();
		generate(a[i]);
		if (!(a[i].type in noTerminator))
			whitespace.terminateLine();
	}
}

function generate(node) {
	if (!node)
		return;
	var fn = types[node.type];
	if (!fn) {
		return console.warn('Warning: Encountered an AST node that has no generator:', node.type);
	}
	return fn(node);
}

function gen_root(node) {
	list_statements(node.statements);
}

function gen_type(node) {
	if (node.qualifier) {
		token(node.qualifier);
		whitespace.space(true);
	}
	token(node.name);
}

function gen_preprocessor(node) {
	switch (node.directive) {
		case '#define':
			token(node.directive);
			whitespace.space(true);
			token(node.identifier);
			whitespace.space(true);
			token(node.token_string);
			whitespace.newline();
			break;

		case '#ifdef':
		case '#ifndef':
		case '#if':
		case '#elif':
			token(node.directive);
			whitespace.space(true);
			token(node.value);
			whitespace.newline();
			list_statements(node.guarded_statements);
			if ('elseBody' in node)
				generate(node.elseBody);
			else {
				token('#endif');
				whitespace.newline();
			}
			break;
		case '#else':
			token(node.directive);
			whitespace.newline();
			list_statements(node.guarded_statements);
			token('#endif');
			whitespace.newline();
			break;

		case '#version':
		case '#undef':
		case '#pragma':
		case '#extension':
		case '#line':
		case '#error':
		case '#include':
			token(node.directive);
			whitespace.space(true);
			token(node.value);
			whitespace.newline();
			break;
	}
}

function gen_identifier(node) {
	token(node.name);
}

function gen_operator(node) {
	token(node.operator);
}

function gen_declarator(node) {
	generate(node.typeAttribute);
	whitespace.space(true);
	list_parameters(node.declarators);
}

function gen_declarator_item(node) {
	generate(node.name);
	if ('initializer' in node) {
		whitespace.space();
		token('=');
		whitespace.space();
		generate(node.initializer);
	}
}

function gen_expression(node) {
	generate(node.expression);
}

function gen_binary(node) {
	switch (node.operator.operator) {
		case '=':
			generate(node.left);
			whitespace.space();
			generate(node.operator);
			whitespace.space();
			generate(node.right);
			break;

		default:
			if (node.left.type == 'binary') {
				token('(');
				generate(node.left);
				token(')');
			}
			else {
				generate(node.left);
			}

			whitespace.space();
			generate(node.operator);
			whitespace.space();

			if (node.right.type == 'binary') {
				token('(');
				generate(node.right);
				token(')');
			}
			else {
				generate(node.right);
			}
			break;
	}
}

function gen_unary(node) {
	generate(node.operator);
	generate(node.expression);
}

function gen_parameter(node) {
	if (node.typeQualifier) {
		token(node.typeQualifier);
		whitespace.space(true);
	}
	if (node.parameterQualifier) {
		token(node.parameterQualifier);
		whitespace.space(true);
	}
	token(node.type_name);
	whitespace.space(true);
	token(node.name);
}

function gen_function(node) {
	generate(node.returnType);
	whitespace.space(true);
	token(node.name);
	token('(');
	list_parameters(node.parameters);
	token(')');
	whitespace.space();
	generate(node.body);
}

function gen_function_call(node) {
	token(node.function_name);
	token('(');
	list_parameters(node.parameters);
	token(')');
}

function gen_scope(node, noNewline) {
	token('{');
	whitespace.newline();
	whitespace.indent();
	list_statements(node.statements);
	whitespace.dedent();
	whitespace.tab();
	token('}');
	if (!noNewline)
		whitespace.newline();
}

function gen_postfix(node) {
	generate(node.expression);
	generate(node.operator);
}

function gen_field_selector(node) {
	token('.');
	token(node.selection);
}

function gen_accessor(node) {
	token('[');
	generate(node.index);
	token(']');
}

function gen_float(node) {
	token(node.value);
	if (node.value % 1 == 0)
		token('.0');
}

function gen_int(node) {
	token(node.value);
}

function gen_bool(node) {
	token(node.value);
}

function gen_precision(node) {
	token(node.type);
	whitespace.space(true);
	token(node.precision);
	whitespace.space(true);
	token(node.typeName);
}

function gen_if_statement(node, isElseIf) {
	if (isElseIf) {
		whitespace.tab();
		token('elseif');
	}
	else token('if');
	whitespace.space();
	token('(');
	generate(node.condition);
	token(')');
	whitespace.space();
	generate(node.body);
	if (node.elseBody) {
		if (node.elseBody.type == 'if_statement') {
			gen_if_statement(node.elseBody, true);
		}
		else {
			whitespace.tab();
			token('else');
			whitespace.space();
			generate(node.elseBody);
		}
	}
}

function gen_return(node) {
	token('return');
	whitespace.space(true);
	generate(node.value);
}

function gen_ternary(node) {
	generate(node.condition);
	whitespace.space();
	token('?');
	whitespace.space();
	generate(node.is_true);
	whitespace.space();
	token(':');
	whitespace.space();
	generate(node.is_false);
}

function gen_for_statement(node) {
	token('for');
	whitespace.space();
	token('(');
	generate(node.initializer);
	whitespace.terminator();
	whitespace.space();
	generate(node.condition);
	whitespace.terminator();
	whitespace.space();
	generate(node.increment);
	token(')');
	whitespace.space();
	generate(node.body);
}

function gen_while_statement(node) {
	token('while');
	whitespace.space();
	token('(');
	generate(node.condition);
	token(')');
	whitespace.space();
	generate(node.body);
}

function gen_do_statement(node) {
	token('do');
	whitespace.space();
	gen_scope(node.body, true);
	whitespace.space();
	token('while');
	whitespace.space();
	token('(');
	generate(node.condition);
	token(')');
}

function gen_continue(node) {
	token('continue');
}

function gen_break(node) {
	token('break');
}

function gen_discard(node) {
	token('discard');
}
<|endoftext|>


<commit_before>import deep from 'deep-diff';
import fs   from 'fs';
import glob from 'glob';
import url  from 'url';
import yml  from 'yamljs';

import express    from 'express';
import https      from 'https';
import bodyParser from 'body-parser';

// ログ用
const red   = '\u001b[31m';
const green = '\u001b[32m';
const cyab  = '\u001b[36m';
const reset = '\u001b[0m';

//------------------------------------------------------------------------------
// サポート関数
//------------------------------------------------------------------------------
const privates = {
    /**
     * リクエストパラメータの取得
     *
     * @param {object} req リクエストオブジェクト
     * @return {object} リクエストパラメーターのオブジェクト
     */
    getParams : (req) => {
        if (req.method === 'GET') {
            return req.query;
        }
        return req.body;
    },

    /**
     * 部分集合かチェック
     * スタブ設定がリクエストの部分集合ならマッチとして使う
     * （part ⊆ whole ならOK）
     *
     * @param {object} whole  含む方
     * @param {object} part   含まれる方
     * @return {boolean} 部分集合が成り立てばtrue
     */
    isSubsetObject : (whole, part) => {
        const changes = deep.diff(whole, part) || [];
        for (const change of changes) {
            if (change.kind !== 'D') {
                return false;
            }
        }
        return true;
    },

    /**
     * 渡された文字列がプレースホルダー部分かどうか
     *
     * @param {string} str
     * @return {boolean}
     */
    isPlaceholder : str => (str.indexOf('{') === 0),

    /**
     * pathを比較する
     *
     * @param {string} stubPath スタブ設定のパス
     * @param {string} reqPath  リクエストパス
     * @return {array}
     *   0 : {boolean} マッチしたか
     *   1 : {object}  抜き出したルーティングパラメータのオブジェクト
     */
    isMatchingPathAndExtractParams : (stubPath, reqPath) => {
        const stubDirs  = stubPath.split('/');
        const reqDirs   = reqPath.split('/');
        let reqParams   = {};
        let isMatch     = true;
        if (stubDirs.length === reqDirs.length) {
            for (const j of stubDirs.keys()) {
                if (privates.isPlaceholder(stubDirs[j])) {
                    reqParams[stubDirs[j].slice(1, -1)] = reqDirs[j];
                } else if (stubDirs[j] !== reqDirs[j]) {
                    isMatch   = false;
                    reqParams = {};
                    break;
                }
            }
        } else {
            isMatch = false;
        }

        return [isMatch, reqParams];
    },

    /**
     * 指定ディレクトリから設定を読み込む
     */
    loadFiles : (directory) => {
        const data = {};

        [
            // YAML
            {
                extension : 'yml',
                parse     : file => yml.load(file),
            },
            // JSON
            {
                extension : 'json',
                parse     : file => JSON.parse(fs.readFileSync(file, 'utf-8')),
            },
        ].forEach((typeInfo) => {
            const files = glob.sync(`${directory}/*.${typeInfo.extension}`);
            files.forEach((file) => {
                data[file] = {};
                const parsed = typeInfo.parse(file);
                Object.keys(parsed).forEach((key) => {
                    data[file][key] = parsed[key];
                });
            });
        });

        return data;
    },

    // 出力系
    outputJson : (res, data) => {
        res.writeHead(data.status, { 'Content-Type' : 'application/json; charset=utf-8' });
        res.end(JSON.stringify(data.body));
    },

    outputNotFound : (res) => {
        res.writeHead(404);
        res.end('Not Found');
    },

    outputError : (res) => {
        res.writeHead(500);
        res.end('Server Error!');
    },
};

//------------------------------------------------------------------------------
// メイン
//------------------------------------------------------------------------------
/**
 * Stubon
 *
 * @prop {object}  app   expressオブジェクト
 * @prop {object}  stubs スタブ設定のオブジェクト
 * @prop {boolean} debug デバッグモードフラグ
 * @prop {boolean} ssl   SSLモードフラグ
 */
class Stubon {

    /**
     * コンストラクタ
     *
     * @param {string}  directory スタブ設定ファイルの置き場所
     * @param {object}  options
     * @param {boolean} [options.debug=false] ログを多めに出すモード
     * @param {boolean} [options.ssl=false]   sslにするモード
     */
    constructor(directory, options = {}) {
        this.debug = options.debug || false;
        this.ssl   = options.ssl || false;
        this.stubs = privates.loadFiles(directory);
        this.app   = express();
    }

    /**
     * サーバー起動
     */
    server() {
        // postのデータを受け取れるようにする設定
        this.app.use(bodyParser.urlencoded({ extended : true }));
        this.app.use(bodyParser.json());

        this.app.use((req, res, next) => {
            res.header('Access-Control-Allow-Origin', '*');
            next();
        });

        // HTTPリクエストを受けて、レスポンスを返すところ
        this.app.all('*', (req, res) => {
            this.log('\n----- recieve request -----');
            this.router(req, res);
        });

        if (this.ssl) {
            this.app = https.createServer({
                key  : fs.readFileSync(`${__dirname}/../ssl/server.key`),
                cert : fs.readFileSync(`${__dirname}/../ssl/server.crt`),
            }, this.app);
        }

        return this.app;
    }

    /**
     * ルーター
     *
     * @param {object} req リクエストオブジェクト
     * @param {object} res レスポンスオブジェクト
     */
    router(req, res) {
        const {
            getParams,
            isMatchingPathAndExtractParams,
            isSubsetObject,
            outputJson,
            outputNotFound,
            outputError,
        } = privates;
        try {
            const reqPath    = decodeURI(url.parse(req.url).pathname);
            const reqQueries = getParams(req);

            this.log([
                { path : reqPath },
                { method : req.method },
                { query : reqQueries },
                { header : JSON.stringify(req.headers) },
            ], true);

            for (const file of Object.keys(this.stubs)) {
                this.log(`file: ${file}`, true);
                for (const stubPath of Object.keys(this.stubs[file])) {
                    // パスを比較
                    this.log(`  path: ${stubPath}`, true);
                    const [isMatch, reqParams] =
                        isMatchingPathAndExtractParams(stubPath, reqPath);
                    if (isMatch) {
                        this.log('  matched.', true);
                        for (const i of this.stubs[file][stubPath].keys()) {
                            this.log(`    index: ${i}`, true);
                            const exp = this.stubs[file][stubPath][i].request;

                            // パラメーターを比較
                            if ((!exp.method || exp.method === req.method)
                                && (!exp.params || isSubsetObject(reqParams, exp.params))
                                && (!exp.queries || isSubsetObject(reqQueries, exp.queries))
                                && (!exp.headers || isSubsetObject(req.headers, exp.headers))
                            ) {
                                this.log(`>> ${green}match!${reset} ${file} ${stubPath} [${i}]`);
                                const response = this.stubs[file][stubPath][i].response;
                                const options  = this.stubs[file][stubPath][i].options || {};
                                const lagSec   = parseInt(options.lagSec, 10) || 0;
                                setTimeout(() => outputJson(res, response), lagSec * 1000);
                                return;
                            }
                        }
                    }
                }
            }
            this.log(`>> ${cyab}not found${reset}`);
            outputNotFound(res);
        } catch (e) {
            this.log(`>> ${red}error${reset}`);
            this.log(e);
            outputError(res);
        }
    }

    /**
     * ロガー
     *
     * @param {string}  msg                ログメッセージ
     * @param {boolean} [isDebugLog=false] デバッグログか。trueならdebugモード時のみ出力
     */
    log(msg, isDebugLog = false) {
        if ((isDebugLog && this.debug) || !isDebugLog) {
            console.log(msg);
        }
    }
}

export { privates };
export default Stubon;
<commit_msg>[fix] #3 add allow-headers.<commit_after>import deep from 'deep-diff';
import fs   from 'fs';
import glob from 'glob';
import url  from 'url';
import yml  from 'yamljs';

import express    from 'express';
import https      from 'https';
import bodyParser from 'body-parser';

// ログ用
const red   = '\u001b[31m';
const green = '\u001b[32m';
const cyab  = '\u001b[36m';
const reset = '\u001b[0m';

//------------------------------------------------------------------------------
// サポート関数
//------------------------------------------------------------------------------
const privates = {
    /**
     * リクエストパラメータの取得
     *
     * @param {object} req リクエストオブジェクト
     * @return {object} リクエストパラメーターのオブジェクト
     */
    getParams : (req) => {
        if (req.method === 'GET') {
            return req.query;
        }
        return req.body;
    },

    /**
     * 部分集合かチェック
     * スタブ設定がリクエストの部分集合ならマッチとして使う
     * （part ⊆ whole ならOK）
     *
     * @param {object} whole  含む方
     * @param {object} part   含まれる方
     * @return {boolean} 部分集合が成り立てばtrue
     */
    isSubsetObject : (whole, part) => {
        const changes = deep.diff(whole, part) || [];
        for (const change of changes) {
            if (change.kind !== 'D') {
                return false;
            }
        }
        return true;
    },

    /**
     * 渡された文字列がプレースホルダー部分かどうか
     *
     * @param {string} str
     * @return {boolean}
     */
    isPlaceholder : str => (str.indexOf('{') === 0),

    /**
     * pathを比較する
     *
     * @param {string} stubPath スタブ設定のパス
     * @param {string} reqPath  リクエストパス
     * @return {array}
     *   0 : {boolean} マッチしたか
     *   1 : {object}  抜き出したルーティングパラメータのオブジェクト
     */
    isMatchingPathAndExtractParams : (stubPath, reqPath) => {
        const stubDirs  = stubPath.split('/');
        const reqDirs   = reqPath.split('/');
        let reqParams   = {};
        let isMatch     = true;
        if (stubDirs.length === reqDirs.length) {
            for (const j of stubDirs.keys()) {
                if (privates.isPlaceholder(stubDirs[j])) {
                    reqParams[stubDirs[j].slice(1, -1)] = reqDirs[j];
                } else if (stubDirs[j] !== reqDirs[j]) {
                    isMatch   = false;
                    reqParams = {};
                    break;
                }
            }
        } else {
            isMatch = false;
        }

        return [isMatch, reqParams];
    },

    /**
     * 指定ディレクトリから設定を読み込む
     */
    loadFiles : (directory) => {
        const data = {};

        [
            // YAML
            {
                extension : 'yml',
                parse     : file => yml.load(file),
            },
            // JSON
            {
                extension : 'json',
                parse     : file => JSON.parse(fs.readFileSync(file, 'utf-8')),
            },
        ].forEach((typeInfo) => {
            const files = glob.sync(`${directory}/*.${typeInfo.extension}`);
            files.forEach((file) => {
                data[file] = {};
                const parsed = typeInfo.parse(file);
                Object.keys(parsed).forEach((key) => {
                    data[file][key] = parsed[key];
                });
            });
        });

        return data;
    },

    // 出力系
    outputJson : (res, data) => {
        res.writeHead(data.status, { 'Content-Type' : 'application/json; charset=utf-8' });
        res.end(JSON.stringify(data.body));
    },

    outputNotFound : (res) => {
        res.writeHead(404);
        res.end('Not Found');
    },

    outputError : (res) => {
        res.writeHead(500);
        res.end('Server Error!');
    },
};

//------------------------------------------------------------------------------
// メイン
//------------------------------------------------------------------------------
/**
 * Stubon
 *
 * @prop {object}  app   expressオブジェクト
 * @prop {object}  stubs スタブ設定のオブジェクト
 * @prop {boolean} debug デバッグモードフラグ
 * @prop {boolean} ssl   SSLモードフラグ
 */
class Stubon {

    /**
     * コンストラクタ
     *
     * @param {string}  directory スタブ設定ファイルの置き場所
     * @param {object}  options
     * @param {boolean} [options.debug=false] ログを多めに出すモード
     * @param {boolean} [options.ssl=false]   sslにするモード
     */
    constructor(directory, options = {}) {
        this.debug = options.debug || false;
        this.ssl   = options.ssl || false;
        this.stubs = privates.loadFiles(directory);
        this.app   = express();
    }

    /**
     * サーバー起動
     */
    server() {
        // postのデータを受け取れるようにする設定
        this.app.use(bodyParser.urlencoded({ extended : true }));
        this.app.use(bodyParser.json());

        this.app.use((req, res, next) => {
            res.header('Access-Control-Allow-Origin', '*');
            res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
            next();
        });

        // HTTPリクエストを受けて、レスポンスを返すところ
        this.app.all('*', (req, res) => {
            this.log('\n----- recieve request -----');
            this.router(req, res);
        });

        if (this.ssl) {
            this.app = https.createServer({
                key  : fs.readFileSync(`${__dirname}/../ssl/server.key`),
                cert : fs.readFileSync(`${__dirname}/../ssl/server.crt`),
            }, this.app);
        }

        return this.app;
    }

    /**
     * ルーター
     *
     * @param {object} req リクエストオブジェクト
     * @param {object} res レスポンスオブジェクト
     */
    router(req, res) {
        const {
            getParams,
            isMatchingPathAndExtractParams,
            isSubsetObject,
            outputJson,
            outputNotFound,
            outputError,
        } = privates;
        try {
            const reqPath    = decodeURI(url.parse(req.url).pathname);
            const reqQueries = getParams(req);

            this.log([
                { path : reqPath },
                { method : req.method },
                { query : reqQueries },
                { header : JSON.stringify(req.headers) },
            ], true);

            for (const file of Object.keys(this.stubs)) {
                this.log(`file: ${file}`, true);
                for (const stubPath of Object.keys(this.stubs[file])) {
                    // パスを比較
                    this.log(`  path: ${stubPath}`, true);
                    const [isMatch, reqParams] =
                        isMatchingPathAndExtractParams(stubPath, reqPath);
                    if (isMatch) {
                        this.log('  matched.', true);
                        for (const i of this.stubs[file][stubPath].keys()) {
                            this.log(`    index: ${i}`, true);
                            const exp = this.stubs[file][stubPath][i].request;

                            // パラメーターを比較
                            if ((!exp.method || exp.method === req.method)
                                && (!exp.params || isSubsetObject(reqParams, exp.params))
                                && (!exp.queries || isSubsetObject(reqQueries, exp.queries))
                                && (!exp.headers || isSubsetObject(req.headers, exp.headers))
                            ) {
                                this.log(`>> ${green}match!${reset} ${file} ${stubPath} [${i}]`);
                                const response = this.stubs[file][stubPath][i].response;
                                const options  = this.stubs[file][stubPath][i].options || {};
                                const lagSec   = parseInt(options.lagSec, 10) || 0;
                                setTimeout(() => outputJson(res, response), lagSec * 1000);
                                return;
                            }
                        }
                    }
                }
            }
            this.log(`>> ${cyab}not found${reset}`);
            outputNotFound(res);
        } catch (e) {
            this.log(`>> ${red}error${reset}`);
            this.log(e);
            outputError(res);
        }
    }

    /**
     * ロガー
     *
     * @param {string}  msg                ログメッセージ
     * @param {boolean} [isDebugLog=false] デバッグログか。trueならdebugモード時のみ出力
     */
    log(msg, isDebugLog = false) {
        if ((isDebugLog && this.debug) || !isDebugLog) {
            console.log(msg);
        }
    }
}

export { privates };
export default Stubon;
<|endoftext|>


<commit_before>// #DROPZONE#
// This script implements the dropzone settings
'use strict';

/* global Dropzone */
(function ($) {
    $(function () {
        var dropzoneSelector = '.js-dropzone';
        var dropzone = $(dropzoneSelector);
        var dropzoneUrl = dropzone.data('url');
        var filerFile = $('.filerFile');

        if (dropzone.length && Dropzone) {
            Dropzone.autoDiscover = false;
            new Dropzone(dropzoneSelector, {
                url: dropzoneUrl,
                paramName: 'file', // The name that will be used to transfer the file
                maxFilesize: 2, // MB
                addRemoveLinks: true,
                maxFiles: 1,
                clickable: false,
                maxfilesexceeded: function (file) {
                    this.removeAllFiles();
                    this.addFile(file);
                },
                drop: function () {
                    filerFile.hide();
                },
                init: function() {
                    this.on('removedfile', function(file) {
                        filerFile.show();
                        this.removeAllFiles();
                    });
                }
            });
        }
    });
})(jQuery);
<commit_msg>updates spaces<commit_after>// #DROPZONE#
// This script implements the dropzone settings
'use strict';

/* global Dropzone */
(function ($) {
    $(function () {
        var dropzoneSelector = '.js-dropzone';
        var dropzone = $(dropzoneSelector);
        var dropzoneUrl = dropzone.data('url');
        var filerFile = $('.filerFile');

        if (dropzone.length && Dropzone) {
            Dropzone.autoDiscover = false;
            new Dropzone(dropzoneSelector, {
                url: dropzoneUrl,
                paramName: 'file', // The name that will be used to transfer the file
                maxFilesize: 2, // MB
                addRemoveLinks: true,
                maxFiles: 1,
                clickable: false,
                maxfilesexceeded: function (file) {
                    this.removeAllFiles();
                    this.addFile(file);
                },
                drop: function () {
                    filerFile.hide();
                },
                init: function () {
                    this.on('removedfile', function () {
                        filerFile.show();
                        this.removeAllFiles();
                    });
                }
            });
        }
    });
})(jQuery);
<|endoftext|>


<commit_before>'use strict';

// declare top-level module which depends on filters,and services
var myApp = angular.module('myApp',
    [   'myApp.filters',
        'myApp.directives', // custom directives
        'ngGrid', // angular grid
        'ui', // angular ui
        'ngSanitize', // for html-bind in ckeditor
        'ui.ace', // ace code editor
        'ui.bootstrap', // jquery ui bootstrap
        '$strap.directives' // angular strap
    ]);

// bootstrap angular
myApp.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

    // TODO use html5 *no hash) where possible
    // $locationProvider.html5Mode(true);

    $routeProvider.when('/', {
        templateUrl:'partials/home.html'
    });
    $routeProvider.when('/contact', {
        templateUrl:'partials/contact.html'
    });
    $routeProvider.when('/about', {
        templateUrl:'partials/about.html'
    });
    $routeProvider.when('/faq', {
        templateUrl:'partials/faq.html'
    });

    // note that to minimize playground impact on app.js, we
    // are including just this simple route with a parameterized
    // partial value (see playground.js and playground.html)
    $routeProvider.when('/playground/:widgetName', {
        templateUrl:'playground/playground.html',
        controller:'PlaygroundCtrl'
    });

    // by default, redirect to site root
    $routeProvider.otherwise({
        redirectTo:'/'
    });

}]);

// this is run after angular is instantiated and bootstrapped
myApp.run(function ($rootScope, $location, $http, $timeout, AuthService, RESTService) {

    // *****
    // Eager load some data using simple REST client
    // *****

    $rootScope.restService = RESTService;

    // async load constants
    $rootScope.constants = [];
    $rootScope.restService.get('data/constants.json', function (data) {
            $rootScope.constants = data[0];
        }
    );

    // async load data do be used in table (playgound grid widget)
    $rootScope.listData = [];
    $rootScope.restService.get('data/generic-list.json', function (data) {
            $rootScope.listData = data;
        }
    );

    // *****
    // Initialize authentication
    // *****
    $rootScope.authService = AuthService;

    // text input for login/password (only)
    $rootScope.loginInput = 'git@github.com';
    $rootScope.passwordInput = 'git';

    $rootScope.$watch('authService.authorized()', function () {

        // if never logged in, do nothing (otherwise bookmarks fail)
        if ($rootScope.authService.initialState()) {
            // we are public browsing
            return;
        }

        // when user logs in, redirect to home
        if ($rootScope.authService.authorized()) {
            $location.path("/");
        }

        // when user logs out, redirect to home
        if (!$rootScope.authService.authorized()) {
            $location.path("/");
        }

    }, true);

    $rootScope.faq = [

        {key: "Question 1", value:  "Answer1"},
        {key: "Question 2", value:  "Answer2"}

    ];

});<commit_msg>added faq content<commit_after>'use strict';

// declare top-level module which depends on filters,and services
var myApp = angular.module('myApp',
    [   'myApp.filters',
        'myApp.directives', // custom directives
        'ngGrid', // angular grid
        'ui', // angular ui
        'ngSanitize', // for html-bind in ckeditor
        'ui.ace', // ace code editor
        'ui.bootstrap', // jquery ui bootstrap
        '$strap.directives' // angular strap
    ]);

// bootstrap angular
myApp.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

    // TODO use html5 *no hash) where possible
    // $locationProvider.html5Mode(true);

    $routeProvider.when('/', {
        templateUrl:'partials/home.html'
    });
    $routeProvider.when('/contact', {
        templateUrl:'partials/contact.html'
    });
    $routeProvider.when('/about', {
        templateUrl:'partials/about.html'
    });
    $routeProvider.when('/faq', {
        templateUrl:'partials/faq.html'
    });

    // note that to minimize playground impact on app.js, we
    // are including just this simple route with a parameterized
    // partial value (see playground.js and playground.html)
    $routeProvider.when('/playground/:widgetName', {
        templateUrl:'playground/playground.html',
        controller:'PlaygroundCtrl'
    });

    // by default, redirect to site root
    $routeProvider.otherwise({
        redirectTo:'/'
    });

}]);

// this is run after angular is instantiated and bootstrapped
myApp.run(function ($rootScope, $location, $http, $timeout, AuthService, RESTService) {

    // *****
    // Eager load some data using simple REST client
    // *****

    $rootScope.restService = RESTService;

    // async load constants
    $rootScope.constants = [];
    $rootScope.restService.get('data/constants.json', function (data) {
            $rootScope.constants = data[0];
        }
    );

    // async load data do be used in table (playgound grid widget)
    $rootScope.listData = [];
    $rootScope.restService.get('data/generic-list.json', function (data) {
            $rootScope.listData = data;
        }
    );

    // *****
    // Initialize authentication
    // *****
    $rootScope.authService = AuthService;

    // text input for login/password (only)
    $rootScope.loginInput = 'git@github.com';
    $rootScope.passwordInput = 'git';

    $rootScope.$watch('authService.authorized()', function () {

        // if never logged in, do nothing (otherwise bookmarks fail)
        if ($rootScope.authService.initialState()) {
            // we are public browsing
            return;
        }

        // when user logs in, redirect to home
        if ($rootScope.authService.authorized()) {
            $location.path("/");
        }

        // when user logs out, redirect to home
        if (!$rootScope.authService.authorized()) {
            $location.path("/");
        }

    }, true);

    // TODO move this out to a more appropriate place
    $rootScope.faq = [
        {key: "What is the service-nucleus?", value: "The service nucleus is a starting point for a full-blown Java webservice and accompanying UI."},
        {key: "What are the pre-requisites for running the service nucleus?", value: "You need JDK 7 and Gradle>=1.6."},
        {key: "How do I change styling (css)?", value:  "See service-implementation/bootstrap.  First change the less modules, then compile using build.sh.  The resulting artifacts will be copied to the appropriate location."},
        {key: "How do I implement a REST service?", value:  "Simply add a new Jersey Resource.  See service-implementation/src/main/java/com/liaison/service/resources/examples for examples."},
        {key: "How do I brand my project (rename from Hello-World)?", value: "This is currently a manual process with about a half-dozen steps.  See README.md for details."},
        {key: "How do I expose JMX metrics?", value: "Checkout the MetricsResource example."}
    ];

});<|endoftext|>