<commit_before>const {extend} = require('./utils/objects');
const {VALUE, ERROR, ANY, END} = require('./constants');
const {Dispatcher, callSubscriber} = require('./dispatcher');
const {findByPred} = require('./utils/collections');

function Observable() {
  this._dispatcher = new Dispatcher();
  this._active = false;
  this._alive = true;
  this._activating = false;
  this._logHandlers = null;
}

extend(Observable.prototype, {

  _name: 'observable',

  _onActivation() {},
  _onDeactivation() {},

  _setActive(active) {
    if (this._active !== active) {
      this._active = active;
      if (active) {
        this._activating = true;
        this._onActivation();
        this._activating = false;
      } else {
        this._onDeactivation();
      }
    }
  },

  _clear() {
    this._setActive(false);
    this._alive = false;
    this._dispatcher = null;
    this._logHandlers = null;
  },

  _emit(type, x) {
    switch (type) {
      case VALUE: return this._emitValue(x);
      case ERROR: return this._emitError(x);
      case END: return this._emitEnd();
    }
  },

  _emitValue(value) {
    if (this._alive) {
      this._dispatcher.dispatch({type: VALUE, value, current: this._activating});
    }
  },

  _emitError(value) {
    if (this._alive) {
      this._dispatcher.dispatch({type: ERROR, value, current: this._activating});
    }
  },

  _emitEnd() {
    if (this._alive) {
      this._dispatcher.dispatch({type: END, current: this._activating});
      this._clear();
    }
  },

  _on(type, fn) {
    if (this._alive) {
      this._dispatcher.add(type, fn);
      this._setActive(true);
    } else {
      callSubscriber(type, fn, {type: END, current: true});
    }
    return this;
  },

  _off(type, fn) {
    if (this._alive) {
      let count = this._dispatcher.remove(type, fn);
      if (count === 0) {
        this._setActive(false);
      }
    }
    return this;
  },

  onValue(fn) {
    return this._on(VALUE, fn);
  },
  onError(fn) {
    return this._on(ERROR, fn);
  },
  onEnd(fn) {
    return this._on(END, fn);
  },
  onAny(fn) {
    return this._on(ANY, fn);
  },

  offValue(fn) {
    return this._off(VALUE, fn);
  },
  offError(fn) {
    return this._off(ERROR, fn);
  },
  offEnd(fn) {
    return this._off(END, fn);
  },
  offAny(fn) {
    return this._off(ANY, fn);
  },

  // A and B must be subclasses of Stream and Property (order doesn't matter)
  ofSameType(A, B) {
    return A.prototype.getType() === this.getType() ? A : B;
  },

  setName(sourceObs /* optional */, selfName) {
    this._name = selfName ? `${sourceObs._name}.${selfName}` : sourceObs;
    return this;
  },

  log(name = this.toString()) {

    let handler = function(event) {
      let type = `<${event.type}${event.current ? ':current' : ''}>`;
      if (event.type === END) {
        console.log(name, type);
      } else {
        console.log(name, type, event.value);
      }
    };

    if (this._alive) {
      if (!this._logHandlers) {
        this._logHandlers = [];
      }
      this._logHandlers.push({name: name, handler: handler});
    }

    this.onAny(handler);

    return this;
  },

  offLog(name = this.toString()) {

    if (this._logHandlers) {
      let handlerIndex = findByPred(this._logHandlers, obj => obj.name === name);
      if (handlerIndex !== -1) {
        this.offAny(this._logHandlers[handlerIndex].handler);
        this._logHandlers.splice(handlerIndex, 1);
      }
    }

    return this;
  }

});

// extend() can't handle `toString` in IE8
Observable.prototype.toString = function() {
  return '[' + this._name + ']';
};

module.exports = Observable;
<commit_msg>minor in obs.toString<commit_after>const {extend} = require('./utils/objects');
const {VALUE, ERROR, ANY, END} = require('./constants');
const {Dispatcher, callSubscriber} = require('./dispatcher');
const {findByPred} = require('./utils/collections');

function Observable() {
  this._dispatcher = new Dispatcher();
  this._active = false;
  this._alive = true;
  this._activating = false;
  this._logHandlers = null;
}

extend(Observable.prototype, {

  _name: 'observable',

  _onActivation() {},
  _onDeactivation() {},

  _setActive(active) {
    if (this._active !== active) {
      this._active = active;
      if (active) {
        this._activating = true;
        this._onActivation();
        this._activating = false;
      } else {
        this._onDeactivation();
      }
    }
  },

  _clear() {
    this._setActive(false);
    this._alive = false;
    this._dispatcher = null;
    this._logHandlers = null;
  },

  _emit(type, x) {
    switch (type) {
      case VALUE: return this._emitValue(x);
      case ERROR: return this._emitError(x);
      case END: return this._emitEnd();
    }
  },

  _emitValue(value) {
    if (this._alive) {
      this._dispatcher.dispatch({type: VALUE, value, current: this._activating});
    }
  },

  _emitError(value) {
    if (this._alive) {
      this._dispatcher.dispatch({type: ERROR, value, current: this._activating});
    }
  },

  _emitEnd() {
    if (this._alive) {
      this._dispatcher.dispatch({type: END, current: this._activating});
      this._clear();
    }
  },

  _on(type, fn) {
    if (this._alive) {
      this._dispatcher.add(type, fn);
      this._setActive(true);
    } else {
      callSubscriber(type, fn, {type: END, current: true});
    }
    return this;
  },

  _off(type, fn) {
    if (this._alive) {
      let count = this._dispatcher.remove(type, fn);
      if (count === 0) {
        this._setActive(false);
      }
    }
    return this;
  },

  onValue(fn) {
    return this._on(VALUE, fn);
  },
  onError(fn) {
    return this._on(ERROR, fn);
  },
  onEnd(fn) {
    return this._on(END, fn);
  },
  onAny(fn) {
    return this._on(ANY, fn);
  },

  offValue(fn) {
    return this._off(VALUE, fn);
  },
  offError(fn) {
    return this._off(ERROR, fn);
  },
  offEnd(fn) {
    return this._off(END, fn);
  },
  offAny(fn) {
    return this._off(ANY, fn);
  },

  // A and B must be subclasses of Stream and Property (order doesn't matter)
  ofSameType(A, B) {
    return A.prototype.getType() === this.getType() ? A : B;
  },

  setName(sourceObs /* optional */, selfName) {
    this._name = selfName ? `${sourceObs._name}.${selfName}` : sourceObs;
    return this;
  },

  log(name = this.toString()) {

    let handler = function(event) {
      let type = `<${event.type}${event.current ? ':current' : ''}>`;
      if (event.type === END) {
        console.log(name, type);
      } else {
        console.log(name, type, event.value);
      }
    };

    if (this._alive) {
      if (!this._logHandlers) {
        this._logHandlers = [];
      }
      this._logHandlers.push({name: name, handler: handler});
    }

    this.onAny(handler);

    return this;
  },

  offLog(name = this.toString()) {

    if (this._logHandlers) {
      let handlerIndex = findByPred(this._logHandlers, obj => obj.name === name);
      if (handlerIndex !== -1) {
        this.offAny(this._logHandlers[handlerIndex].handler);
        this._logHandlers.splice(handlerIndex, 1);
      }
    }

    return this;
  }

});

// extend() can't handle `toString` in IE8
Observable.prototype.toString = function() {
  return `[${this._name}]`;
};

module.exports = Observable;
<|endoftext|>


<commit_before>goog.provide('ol.dom');
goog.provide('ol.dom.BrowserFeature');

goog.require('goog.asserts');
goog.require('goog.userAgent');
goog.require('goog.vec.Mat4');
goog.require('ol');

/**
 * Create an html canvas element and returns its 2d context.
 * @param {number=} opt_width Canvas width.
 * @param {number=} opt_height Canvas height.
 * @return {CanvasRenderingContext2D} The context.
 */
ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
  var canvas = document.createElement('CANVAS');
  if (opt_width) {
    canvas.width = opt_width;
  }
  if (opt_height) {
    canvas.height = opt_height;
  }
  return canvas.getContext('2d');
};

/**
 * Detect 2d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms2d
 * @return {boolean}
 */
ol.dom.canUseCssTransform = (function() {
  var canUseCssTransform;
  return function() {
    if (canUseCssTransform === undefined) {
      goog.asserts.assert(document.body,
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform = false;
      } else {
        var el = document.createElement('P'),
            has2d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        document.body.appendChild(el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate(1px,1px)';
            has2d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        document.body.removeChild(el);

        canUseCssTransform = (has2d && has2d !== 'none');
      }
    }
    return canUseCssTransform;
  };
}());

/**
 * Detect 3d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms3d
 * @return {boolean}
 */
ol.dom.canUseCssTransform3D = (function() {
  var canUseCssTransform3D;
  return function() {
    if (canUseCssTransform3D === undefined) {
      goog.asserts.assert(document.body,
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform3D = false;
      } else {
        var el = document.createElement('P'),
            has3d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        document.body.appendChild(el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate3d(1px,1px,1px)';
            has3d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        document.body.removeChild(el);

        canUseCssTransform3D = (has3d && has3d !== 'none');
      }
    }
    return canUseCssTransform3D;
  };
}());

/**
 * @param {Element} element Element.
 * @param {string} value Value.
 */
ol.dom.setTransform = function(element, value) {
  var style = element.style;
  style.WebkitTransform = value;
  style.MozTransform = value;
  style.OTransform = value;
  style.msTransform = value;
  style.transform = value;

  // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason
  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9.0')) {
    element.style.transformOrigin = '0 0';
  }
};

/**
 * @param {!Element} element Element.
 * @param {goog.vec.Mat4.Number} transform Matrix.
 * @param {number=} opt_precision Precision.
 */
ol.dom.transformElement2D = function(element, transform, opt_precision) {
  // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer
  // matrix3d()
  var i;
  if (ol.dom.canUseCssTransform3D()) {
    var value3D;

    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings3D = new Array(16);
      for (i = 0; i < 16; ++i) {
        strings3D[i] = transform[i].toFixed(opt_precision);
      }
      value3D = strings3D.join(',');
    } else {
      value3D = transform.join(',');
    }
    ol.dom.setTransform(element, 'matrix3d(' + value3D + ')');
  } else if (ol.dom.canUseCssTransform()) {
    /** @type {Array.<number>} */
    var transform2D = [
      goog.vec.Mat4.getElement(transform, 0, 0),
      goog.vec.Mat4.getElement(transform, 1, 0),
      goog.vec.Mat4.getElement(transform, 0, 1),
      goog.vec.Mat4.getElement(transform, 1, 1),
      goog.vec.Mat4.getElement(transform, 0, 3),
      goog.vec.Mat4.getElement(transform, 1, 3)
    ];
    var value2D;
    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings2D = new Array(6);
      for (i = 0; i < 6; ++i) {
        strings2D[i] = transform2D[i].toFixed(opt_precision);
      }
      value2D = strings2D.join(',');
    } else {
      value2D = transform2D.join(',');
    }
    ol.dom.setTransform(element, 'matrix(' + value2D + ')');
  } else {
    element.style.left =
        Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px';
    element.style.top =
        Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px';

    // TODO: Add scaling here. This isn't quite as simple as multiplying
    // width/height, because that only changes the container size, not the
    // content size.
  }
};

/**
 * Get the current computed width for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerWidth(true)`.
 * @param {!Element} element Element.
 * @return {number} The width.
 */
ol.dom.outerWidth = function(element) {
  var width = element.offsetWidth;
  var style = element.currentStyle || window.getComputedStyle(element);
  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);

  return width;
};

/**
 * Get the current computed height for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerHeight(true)`.
 * @param {!Element} element Element.
 * @return {number} The height.
 */
ol.dom.outerHeight = function(element) {
  var height = element.offsetHeight;
  var style = element.currentStyle || window.getComputedStyle(element);
  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);

  return height;
};
<commit_msg>Remove unneeded and wrong goog.provide<commit_after>goog.provide('ol.dom');

goog.require('goog.asserts');
goog.require('goog.userAgent');
goog.require('goog.vec.Mat4');
goog.require('ol');

/**
 * Create an html canvas element and returns its 2d context.
 * @param {number=} opt_width Canvas width.
 * @param {number=} opt_height Canvas height.
 * @return {CanvasRenderingContext2D} The context.
 */
ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
  var canvas = document.createElement('CANVAS');
  if (opt_width) {
    canvas.width = opt_width;
  }
  if (opt_height) {
    canvas.height = opt_height;
  }
  return canvas.getContext('2d');
};

/**
 * Detect 2d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms2d
 * @return {boolean}
 */
ol.dom.canUseCssTransform = (function() {
  var canUseCssTransform;
  return function() {
    if (canUseCssTransform === undefined) {
      goog.asserts.assert(document.body,
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform = false;
      } else {
        var el = document.createElement('P'),
            has2d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        document.body.appendChild(el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate(1px,1px)';
            has2d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        document.body.removeChild(el);

        canUseCssTransform = (has2d && has2d !== 'none');
      }
    }
    return canUseCssTransform;
  };
}());

/**
 * Detect 3d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms3d
 * @return {boolean}
 */
ol.dom.canUseCssTransform3D = (function() {
  var canUseCssTransform3D;
  return function() {
    if (canUseCssTransform3D === undefined) {
      goog.asserts.assert(document.body,
          'document.body should not be null');
      if (!goog.global.getComputedStyle) {
        // this browser is ancient
        canUseCssTransform3D = false;
      } else {
        var el = document.createElement('P'),
            has3d,
            transforms = {
              'webkitTransform': '-webkit-transform',
              'OTransform': '-o-transform',
              'msTransform': '-ms-transform',
              'MozTransform': '-moz-transform',
              'transform': 'transform'
            };
        document.body.appendChild(el);
        for (var t in transforms) {
          if (t in el.style) {
            el.style[t] = 'translate3d(1px,1px,1px)';
            has3d = goog.global.getComputedStyle(el).getPropertyValue(
                transforms[t]);
          }
        }
        document.body.removeChild(el);

        canUseCssTransform3D = (has3d && has3d !== 'none');
      }
    }
    return canUseCssTransform3D;
  };
}());

/**
 * @param {Element} element Element.
 * @param {string} value Value.
 */
ol.dom.setTransform = function(element, value) {
  var style = element.style;
  style.WebkitTransform = value;
  style.MozTransform = value;
  style.OTransform = value;
  style.msTransform = value;
  style.transform = value;

  // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason
  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9.0')) {
    element.style.transformOrigin = '0 0';
  }
};

/**
 * @param {!Element} element Element.
 * @param {goog.vec.Mat4.Number} transform Matrix.
 * @param {number=} opt_precision Precision.
 */
ol.dom.transformElement2D = function(element, transform, opt_precision) {
  // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer
  // matrix3d()
  var i;
  if (ol.dom.canUseCssTransform3D()) {
    var value3D;

    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings3D = new Array(16);
      for (i = 0; i < 16; ++i) {
        strings3D[i] = transform[i].toFixed(opt_precision);
      }
      value3D = strings3D.join(',');
    } else {
      value3D = transform.join(',');
    }
    ol.dom.setTransform(element, 'matrix3d(' + value3D + ')');
  } else if (ol.dom.canUseCssTransform()) {
    /** @type {Array.<number>} */
    var transform2D = [
      goog.vec.Mat4.getElement(transform, 0, 0),
      goog.vec.Mat4.getElement(transform, 1, 0),
      goog.vec.Mat4.getElement(transform, 0, 1),
      goog.vec.Mat4.getElement(transform, 1, 1),
      goog.vec.Mat4.getElement(transform, 0, 3),
      goog.vec.Mat4.getElement(transform, 1, 3)
    ];
    var value2D;
    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings2D = new Array(6);
      for (i = 0; i < 6; ++i) {
        strings2D[i] = transform2D[i].toFixed(opt_precision);
      }
      value2D = strings2D.join(',');
    } else {
      value2D = transform2D.join(',');
    }
    ol.dom.setTransform(element, 'matrix(' + value2D + ')');
  } else {
    element.style.left =
        Math.round(goog.vec.Mat4.getElement(transform, 0, 3)) + 'px';
    element.style.top =
        Math.round(goog.vec.Mat4.getElement(transform, 1, 3)) + 'px';

    // TODO: Add scaling here. This isn't quite as simple as multiplying
    // width/height, because that only changes the container size, not the
    // content size.
  }
};

/**
 * Get the current computed width for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerWidth(true)`.
 * @param {!Element} element Element.
 * @return {number} The width.
 */
ol.dom.outerWidth = function(element) {
  var width = element.offsetWidth;
  var style = element.currentStyle || window.getComputedStyle(element);
  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);

  return width;
};

/**
 * Get the current computed height for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerHeight(true)`.
 * @param {!Element} element Element.
 * @return {number} The height.
 */
ol.dom.outerHeight = function(element) {
  var height = element.offsetHeight;
  var style = element.currentStyle || window.getComputedStyle(element);
  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);

  return height;
};
<|endoftext|>


<commit_before>'use strict';

/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import $C from '../deps/collection';
import Parser from './constructor';
import { ws } from '../helpers/string';
import { classRef } from '../consts/regs';
import { LEFT_BOUND, RIGHT_BOUND, ADV_LEFT_BOUND, FILTER } from '../consts/literals';

/**
 * Returns string declaration of an opening tag for the specified XML tag
 *
 * @param {string} tag - tag name
 * @param {?string=} [opt_attrs] - tag attributes
 * @param {?boolean=} [opt_inline=false] - if true, then the tag is inline
 * @return {string}
 */
Parser.prototype.getXMLTagDecl = function (tag, opt_attrs, opt_inline) {
	return (
		this.getXMLTagDeclStart(tag) +
		this.getXMLAttrsDeclStart() +
		(opt_attrs ? this.getXMLAttrsDeclBody(opt_attrs) : '') +
		this.getXMLAttrsDeclEnd() +
		this.getXMLTagDeclEnd(opt_inline)
	);
};

/**
 * Returns start declaration of the specified XML tag
 *
 * @param {string} tag - tag name
 * @return {string}
 */
Parser.prototype.getXMLTagDeclStart = function (tag) {
	let
		str = this.declVars(`__TAG__ = '${tag}'`, {sys: true});

	const
		link = this.out(`__TAG__`, {unsafe: true});

	if (!this.stringResult && this.renderMode === 'dom') {
		return `${str}$0 = new Snakeskin.Element(${link});`;
	}

	return str + this.wrap(`'<' + ${link}`);
};

/**
 * Returns end declaration of the specified XML tag
 *
 * @param {?boolean=} [opt_inline=false] - if true, then the tag is inline
 * @return {string}
 */
Parser.prototype.getXMLTagDeclEnd = function (opt_inline) {
	opt_inline = Boolean(opt_inline);

	const
		inlineTag = this.out(`__INLINE_TAGS__[__TAG__]`, {unsafe: true});

	if (!this.stringResult && this.renderMode === 'dom') {
		return ws`
			${this.wrap('$0')}
			if (${opt_inline} && (!${inlineTag} || ${inlineTag} === true)) {
				$0 = __RESULT__[__RESULT__.length - 1];

			} else if (${inlineTag} && ${inlineTag} !== true) {
				${this.declVars('__CALL_CACHE__ = __RESULT__, __NODE__ = $0', {sys: true})}
				__RESULT__ = ${this.getResultDecl()};
				$0 = __RESULT__[__RESULT__.length - 1];

			} else {
				__RESULT__.push($0);
			}
		`;
	}

	return ws`
		if (${opt_inline} && (!${inlineTag} || ${inlineTag} === true)) {
			${this.wrap(`'${this.doctype === 'xml' ? '/' : ''}>'`)}

		} else if (${inlineTag} && ${inlineTag} !== true) {
			${this.wrap(`' ' + ${inlineTag} + '="'`)}
			${this.declVars('__CALL_CACHE__ = __RESULT__', {sys: true})}
			__RESULT__ = ${this.getResultDecl()};

		} else {
			${this.wrap(`'>'`)}
		}
	`;
};

/**
 * Returns string declaration of a closing tag for the specified XML tag
 *
 * @param {?boolean=} [opt_inline=false] - if true, then the tag is inline
 * @return {string}
 */
Parser.prototype.getEndXMLTagDecl = function (opt_inline) {
	opt_inline = Boolean(opt_inline);

	const
		inlineTag = this.out(`__INLINE_TAGS__[__TAG__]`, {unsafe: true});

	if (!this.stringResult && this.renderMode === 'dom') {
		return ws`
			if (${inlineTag} && ${inlineTag} !== true) {
				${this.declVars(`__CALL_TMP__ = ${this.getReturnResultDecl()}`, {sys: true})}

				__RESULT__ =
					${this.out('__CALL_CACHE__', {unsafe: true})};

				Snakeskin.setAttribute(
					${this.out(`__NODE__`, {unsafe: true})},
					${inlineTag},
					${this.out('__CALL_TMP__', {unsafe: true})}
				);

			} else if (${!opt_inline}) {
				__RESULT__.pop();
				$0 = __RESULT__[__RESULT__.length - 1];
			}
		`;
	}

	return ws`
		if (${inlineTag} && ${inlineTag} !== true) {
			${this.declVars(`__CALL_TMP__ = ${this.getReturnResultDecl()}`, {sys: true})}

			__RESULT__ =
					${this.out('__CALL_CACHE__', {unsafe: true})};

			${this.wrap(`${this.out('__CALL_TMP__')} + '"${this.doctype === 'xml' ? '/' : ''}>'`)}

		} else if (${!opt_inline}) {
			${this.wrap(`'</' + ${this.out(`__TAG__`, {unsafe: true})} + '>'`)}
		}
	`;
};

/**
 * Analyzes a string of XML tag declaration
 * and returns a reporting object
 *
 * @param {string} str - source string
 * @return {$$SnakeskinParserGetXMLTagDescResult}
 */
Parser.prototype.getXMLTagDesc = function (str) {
	str = this.replaceTplVars(str, {replace: true});

	const
		points = [],
		types = [];

	let
		action = '',
		tag = '',
		id = '';

	let
		inline = false,
		inlineMap = false,
		hasId = false;

	const
		pseudo = [],
		classes = [];

	const
		s = ADV_LEFT_BOUND + LEFT_BOUND,
		e = RIGHT_BOUND;

	let
		bOpen = 0,
		bStart = false;

	const bMap = {
		'[': true,
		']': true
	};

	const sys = {
		'!': true,
		'#': true,
		'.': true
	};

	const error = {
		classes: [],
		id: '',
		inline: false,
		inlineMap: false,
		pseudo: [],
		tag: ''
	};

	function pseudoHelper() {
		const
			val = pseudo[pseudo.length - 1];

		if (val === 'inline') {
			inline = true;

		} else if (/inline=/.test(val)) {
			inlineMap = val.split('=')[1].trim();
		}
	}

	for (let i = 0; i < str.length; i++) {
		const
			el = str[i];

		if (bMap[el]) {
			if (el === '[') {
				bOpen++;
				bStart = true;

			} else {
				bOpen--;
			}

			continue;
		}

		if (bStart && el !== '.') {
			this.error('invalid syntax');
			return error;
		}

		bStart = false;
		if (sys[el] && (el !== '#' || !bOpen)) {
			if (el === '#') {
				if (hasId) {
					this.error('invalid syntax');
					return error;
				}

				hasId = true;
			}

			tag = tag || 'div';
			action = el;

			if (el === '.') {
				if (bOpen) {
					if (points.length) {
						$C(points).forEach((point, i) => {
							if (point) {
								if (point.stage >= bOpen) {
									return;
								}

								let
									tmp = classes[i],
									pos = point.from;

								if (point.val != null) {
									tmp = tmp.replace(classRef, point.val);
								}

								while (points[pos] != null) {
									const {val, from} = points[pos];
									tmp = tmp.replace(classRef, val);
									pos = from;
								}

								points.push({
									from: i,
									stage: bOpen,
									val: tmp
								});

								return false;
							}

							points.push({
								from: i,
								stage: bOpen,
								val: classes[i]
							});

							return false;

						}, {reverse: true});

					} else {
						points.push({
							from: null,
							stage: bOpen,
							val: null
						});
					}

				} else {
					points.push(null);
				}

				types.push(!bOpen);
				classes.push('');

			} else if (el === '!') {
				pseudoHelper();
				pseudo.push('');
			}

			continue;
		}

		switch (action) {
			case '#':
				id += el;
				break;

			case '.':
				classes[classes.length - 1] += el;
				break;

			case '!':
				pseudo[pseudo.length - 1] += el;
				break;

			default:
				tag += el;
		}
	}

	if (bOpen) {
		this.error('invalid syntax');
		return error;
	}

	let ref = this.bemRef;
	$C(classes).forEach((el, i) => {
		const
			point = points[i];

		if (point && point.val != null) {
			el = el.replace(classRef, point.val);
		}

		if (classRef.test(el) && ref) {
			el = `${s}'${ref}'${FILTER}${this.bemFilter} '${el.slice(1)}'${e}`;
			el = this.pasteDangerBlocks(this.replaceTplVars(el));

		} else if (el && types[i]) {
			ref = this.pasteTplVarBlocks(el);
		}

		classes[i] = this.pasteTplVarBlocks(el);
	});

	this.bemRef = ref;
	pseudoHelper();

	return {
		classes,
		id: this.pasteTplVarBlocks(id),
		inline,
		inlineMap,
		pseudo,
		tag: this.pasteTplVarBlocks(tag)
	};
};
<commit_msg>:wrench:<commit_after>'use strict';

/*!
 * Snakeskin
 * https://github.com/SnakeskinTpl/Snakeskin
 *
 * Released under the MIT license
 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
 */

import $C from '../deps/collection';
import Parser from './constructor';
import { ws } from '../helpers/string';
import { classRef } from '../consts/regs';
import { LEFT_BOUND, RIGHT_BOUND, ADV_LEFT_BOUND, FILTER } from '../consts/literals';

/**
 * Returns string declaration of an opening tag for the specified XML tag
 *
 * @param {string} tag - tag name
 * @param {?string=} [opt_attrs] - tag attributes
 * @param {?boolean=} [opt_inline=false] - if true, then the tag is inline
 * @return {string}
 */
Parser.prototype.getXMLTagDecl = function (tag, opt_attrs, opt_inline) {
	return (
		this.getXMLTagDeclStart(tag) +
		this.getXMLAttrsDeclStart() +
		(opt_attrs ? this.getXMLAttrsDeclBody(opt_attrs) : '') +
		this.getXMLAttrsDeclEnd() +
		this.getXMLTagDeclEnd(opt_inline)
	);
};

/**
 * Returns start declaration of the specified XML tag
 *
 * @param {string} tag - tag name
 * @return {string}
 */
Parser.prototype.getXMLTagDeclStart = function (tag) {
	let
		str = this.declVars(`__TAG__ = '${tag}'`, {sys: true});

	const
		link = this.out(`__TAG__`, {unsafe: true});

	if (!this.stringResult && this.renderMode === 'dom') {
		return `${str}$0 = new Snakeskin.Element(${link});`;
	}

	return str + this.wrap(`'<' + ${link}`);
};

/**
 * Returns end declaration of the specified XML tag
 *
 * @param {?boolean=} [opt_inline=false] - if true, then the tag is inline
 * @return {string}
 */
Parser.prototype.getXMLTagDeclEnd = function (opt_inline) {
	opt_inline = Boolean(opt_inline);

	const
		inlineTag = this.out(`__INLINE_TAGS__[__TAG__]`, {unsafe: true});

	if (!this.stringResult && this.renderMode === 'dom') {
		return ws`
			${this.wrap('$0')}
			if (${opt_inline} && (!${inlineTag} || ${inlineTag} === true)) {
				$0 = __RESULT__[__RESULT__.length - 1];

			} else if (${inlineTag} && ${inlineTag} !== true) {
				${this.declVars('__CALL_CACHE__ = __RESULT__, __NODE__ = $0', {sys: true})}
				__RESULT__ = ${this.getResultDecl()};
				$0 = __RESULT__[__RESULT__.length - 1];

			} else {
				__RESULT__.push($0);
			}
		`;
	}

	return ws`
		if (${opt_inline} && (!${inlineTag} || ${inlineTag} === true)) {
			${this.wrap(`'${this.doctype === 'xml' ? '/' : ''}>'`)}

		} else if (${inlineTag} && ${inlineTag} !== true) {
			${this.declVars('__CALL_CACHE__ = __RESULT__', {sys: true})}
			__RESULT__ = ${this.getResultDecl()};

		} else {
			${this.wrap(`'>'`)}
		}
	`;
};

/**
 * Returns string declaration of a closing tag for the specified XML tag
 *
 * @param {?boolean=} [opt_inline=false] - if true, then the tag is inline
 * @return {string}
 */
Parser.prototype.getEndXMLTagDecl = function (opt_inline) {
	opt_inline = Boolean(opt_inline);

	const
		inlineTag = this.out(`__INLINE_TAGS__[__TAG__]`, {unsafe: true});

	if (!this.stringResult && this.renderMode === 'dom') {
		return ws`
			if (${inlineTag} && ${inlineTag} !== true) {
				${this.declVars(`__CALL_TMP__ = ${this.getReturnResultDecl()}`, {sys: true})}

				__RESULT__ =
					${this.out('__CALL_CACHE__', {unsafe: true})};

				Snakeskin.setAttribute(
					${this.out(`__NODE__`, {unsafe: true})},
					${inlineTag},
					${this.out('__CALL_TMP__', {unsafe: true})}
				);

			} else if (${!opt_inline}) {
				__RESULT__.pop();
				$0 = __RESULT__[__RESULT__.length - 1];
			}
		`;
	}

	return ws`
		if (${inlineTag} && ${inlineTag} !== true) {
			${this.declVars(`__CALL_TMP__ = ${this.getReturnResultDecl()}`, {sys: true})}

			__RESULT__ =
					${this.out('__CALL_CACHE__', {unsafe: true})};

			${this.wrap(`' ' + ${inlineTag} + '="' + ${this.out('__CALL_TMP__')} + '"${this.doctype === 'xml' ? '/' : ''}>'`)}

		} else if (${!opt_inline}) {
			${this.wrap(`'</' + ${this.out(`__TAG__`, {unsafe: true})} + '>'`)}
		}
	`;
};

/**
 * Analyzes a string of XML tag declaration
 * and returns a reporting object
 *
 * @param {string} str - source string
 * @return {$$SnakeskinParserGetXMLTagDescResult}
 */
Parser.prototype.getXMLTagDesc = function (str) {
	str = this.replaceTplVars(str, {replace: true});

	const
		points = [],
		types = [];

	let
		action = '',
		tag = '',
		id = '';

	let
		inline = false,
		inlineMap = false,
		hasId = false;

	const
		pseudo = [],
		classes = [];

	const
		s = ADV_LEFT_BOUND + LEFT_BOUND,
		e = RIGHT_BOUND;

	let
		bOpen = 0,
		bStart = false;

	const bMap = {
		'[': true,
		']': true
	};

	const sys = {
		'!': true,
		'#': true,
		'.': true
	};

	const error = {
		classes: [],
		id: '',
		inline: false,
		inlineMap: false,
		pseudo: [],
		tag: ''
	};

	function pseudoHelper() {
		const
			val = pseudo[pseudo.length - 1];

		if (val === 'inline') {
			inline = true;

		} else if (/inline=/.test(val)) {
			inlineMap = val.split('=')[1].trim();
		}
	}

	for (let i = 0; i < str.length; i++) {
		const
			el = str[i];

		if (bMap[el]) {
			if (el === '[') {
				bOpen++;
				bStart = true;

			} else {
				bOpen--;
			}

			continue;
		}

		if (bStart && el !== '.') {
			this.error('invalid syntax');
			return error;
		}

		bStart = false;
		if (sys[el] && (el !== '#' || !bOpen)) {
			if (el === '#') {
				if (hasId) {
					this.error('invalid syntax');
					return error;
				}

				hasId = true;
			}

			tag = tag || 'div';
			action = el;

			if (el === '.') {
				if (bOpen) {
					if (points.length) {
						$C(points).forEach((point, i) => {
							if (point) {
								if (point.stage >= bOpen) {
									return;
								}

								let
									tmp = classes[i],
									pos = point.from;

								if (point.val != null) {
									tmp = tmp.replace(classRef, point.val);
								}

								while (points[pos] != null) {
									const {val, from} = points[pos];
									tmp = tmp.replace(classRef, val);
									pos = from;
								}

								points.push({
									from: i,
									stage: bOpen,
									val: tmp
								});

								return false;
							}

							points.push({
								from: i,
								stage: bOpen,
								val: classes[i]
							});

							return false;

						}, {reverse: true});

					} else {
						points.push({
							from: null,
							stage: bOpen,
							val: null
						});
					}

				} else {
					points.push(null);
				}

				types.push(!bOpen);
				classes.push('');

			} else if (el === '!') {
				pseudoHelper();
				pseudo.push('');
			}

			continue;
		}

		switch (action) {
			case '#':
				id += el;
				break;

			case '.':
				classes[classes.length - 1] += el;
				break;

			case '!':
				pseudo[pseudo.length - 1] += el;
				break;

			default:
				tag += el;
		}
	}

	if (bOpen) {
		this.error('invalid syntax');
		return error;
	}

	let ref = this.bemRef;
	$C(classes).forEach((el, i) => {
		const
			point = points[i];

		if (point && point.val != null) {
			el = el.replace(classRef, point.val);
		}

		if (classRef.test(el) && ref) {
			el = `${s}'${ref}'${FILTER}${this.bemFilter} '${el.slice(1)}'${e}`;
			el = this.pasteDangerBlocks(this.replaceTplVars(el));

		} else if (el && types[i]) {
			ref = this.pasteTplVarBlocks(el);
		}

		classes[i] = this.pasteTplVarBlocks(el);
	});

	this.bemRef = ref;
	pseudoHelper();

	return {
		classes,
		id: this.pasteTplVarBlocks(id),
		inline,
		inlineMap,
		pseudo,
		tag: this.pasteTplVarBlocks(tag)
	};
};
<|endoftext|>


<commit_before>var Curve = this.Curve = Base.extend({
	beans: true,

	initialize: function(arg0, arg1, arg2, arg3) {
		if (arguments.length == 0) {
			this._segment1 = new Segment();
			this._segment2 = new Segment();
		} else if (arguments.length == 1) {
			// TODO: If beans are not activated, this won't copy from
			// an existing segment. OK?
			this._segment1 = new Segment(arg0.segment1);
			this._segment2 = new Segment(arg0.segment2);
		} else if (arguments.length == 2) {
			if (arg0 instanceof Path) {
				this._path = arg0;
				this._index1 = arg1;
				this._updateSegments();
			} else {
				this._segment1 = new Segment(arg0);
				this._segment2 = new Segment(arg1);
			}
		} else if (arguments.length == 4) {
			this._segment1 = new Segment(arg0, null, arg1);
			this._segment2 = new Segment(arg3, arg2, null);
		}
	},

	_updateSegments: function() {
		if (this._path) {
			this._index2 = this._index1 + 1;
			// A closing curve?
			var segments = this._path._segments;
			if (this._index2 >= segments.length)
				this._index2 = 0;
			this._segment1 = segments[this._index1];
			this._segment2 = segments[this._index2];
		}
	},

	/**
	 * The first anchor point of the curve.
	 */
	getPoint1: function() {
		return this._segment1._point;
	},

	setPoint1: function() {
		var point = Point.read(arguments);
		this._segment1._point.set(point.x, point.y);
	},

	/**
	 * The second anchor point of the curve.
	 */
	getPoint2: function() {
		return this._segment2._point;
	},

	setPoint2: function() {
		var point = Point.read(arguments);
		this._segment2._point.set(point.x, point.y);
	},

	/**
	 * The handle point that describes the tangent in the first anchor point.
	 */
	getHandle1: function() {
		return this._segment1._handleOut;
	},

	setHandle1: function() {
		var point = Point.read(arguments);
		this._segment1._handleOut.set(point.x, point.y);
	},

	/**
	 * The handle point that describes the tangent in the second anchor point.
	 */
	getHandle2: function() {
		return this._segment2._handleIn;
	},

	setHandle2: function() {
		var point = Point.read(arguments);
		this._segment2._handleIn.set(point.x, point.y);
	},

	/**
	 * The first segment of the curve.
	 */
	getSegment1: function() {
		return this._segment1;
	},

	/**
	 * The second segment of the curve.
	 */
	getSegment2: function() {
		return this._segment2;
	},

	getPath: function() {
		return this._path;
	},

	getIndex: function() {
		return this._index1;
	},

	_setIndex: function(index) {
		this._index1 = index;
		this._updateSegments();
	},

	getNext: function() {
		var curves = this._path && this._path._curves;
		// TODO: Add cyclic looping when closed back to Scriptographer
		return curves && (curves[this._index1 + 1]
				|| this._path.closed && curves[0]) || null;
	},

	getPrevious: function() {
		var curves = this._path && this._path._curves;
		return curves && (curves[this._index1 - 1]
				|| this._path.closed && curves[curves.length - 1]) || null;
	},

	getCurveValues: function() {
		var p1 = this._segment1._point,
			h1 = this._segment1._handleOut,
			h2 = this._segment2._handleIn,
			p2 = this._segment2._point;
		return [
			p1.x, p1.y,
			p1.x + h1.x, p1.y + h1.y,
			p2.x + h2.x, p2.y + h2.y,
			p2.x, p2.y
		];
	},

	getLength: function() {
		return Curve.getLength.apply(Curve, this.getCurveValues());
	},

	/**
	 * Checks if this curve is linear, meaning it does not define any curve
	 * handle.

	 * @return {@true if the curve is linear}
	 */
	isLinear: function() {
		return this._segment1._handleOut.isZero()
				&& this._segment2._handleIn.isZero();
	},

	getParameter: function(length) {
		return Curve.getParameter.apply(Curve,
				this.getCurveValues().concat(length));
	},

	// TODO: getParameter(point, precision)
	// TODO: getLocation
	// TODO: getIntersections
	// TODO: adjustThroughPoint

	transform: function(matrix) {
		return new Curve(
				matrix.transform(this._segment1._point),
				matrix.transform(this._segment1._handleOut),
				matrix.transform(this._segment2._handleIn),
				matrix.transform(this._segment2._point));
	},

	reverse: function() {
		return new Curve(this._segment2.reverse(), this._segment1.reverse());
	},

	// TODO: divide
	// TODO: split
	// TODO: getPartLength(fromParameter, toParameter)

	clone: function() {
		return new Curve(this._segment1, this._segment2);
	},

	toString: function() {
		return '{ point1: ' + this._segment1._point
				+ (!this._segment1._handleOut.isZero()
					? ', handle1: ' + this._segment1._handleOut : '')
				+ (this._segment2._handleIn.isZero()
					? ', handle2: ' + this._segment2._handleIn : '')
				+ ', point2: ' + this._segment2._point
				+ ' }';
	},

	statics: {
		getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
			// TODO: Check for straight lines and handle separately.

			// Calculate the coefficients of a Bezier derivative.
			var ax = 9 * (c1x - c2x) + 3 * (p2x - p1x),
				bx = 6 * (p1x + c2x) - 12 * c1x,
				cx = 3 * (c1x - p1x),

				ay = 9 * (c1y - c2y) + 3 * (p2y - p1y),
				by = 6 * (p1y + c2y) - 12 * c1y,
				cy = 3 * (c1y - p1y);

			function ds(t) {
				// Calculate quadratic equations of derivatives for x and y
				var dx = (ax * t + bx) * t + cx,
					dy = (ay * t + by) * t + cy;
				return Math.sqrt(dx * dx + dy * dy);
			}

			return MathUtils.gauss(ds, 0.0, 1.0, 8);
		},

		subdivide: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
			var u = 1 - t,
				// Interpolate from 4 to 3 points
				p3x = u * p1x + t * c1x,
				p3y = u * p1y + t * c1y,
				p4x = u * c1x + t * c2x,
				p4y = u * c1y + t * c2y,
				p5x = u * c2x + t * p2x,
				p5y = u * c2y + t * p2y,
				// Interpolate from 3 to 2 points
				p6x = u * p3x + t * p4x,
				p6y = u * p3y + t * p4y,
				p7x = u * p4x + t * p5x,
				p7y = u * p4y + t * p5y,
				// Interpolate from 2 points to 1 point
				p8x = u * p6x + t * p7x,
				p8y = u * p6y + t * p7y;
			// We now have all the values we need to build the subcurves
			return [
				[p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], // left
				[p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] // right
			];
		},

		getPartLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t, right) {
			if (t == 0)
				return 0;
			if (t < 1) {
				curve = Curve.subdivide(p1x, p1y, c1x, c1y, c2x, c2y,
						p2x, p2y, t)[right ? 1 : 0];
			} else {
				curve = arguments;
			}
			return Curve.getLength.apply(Curve, curve);
		},

		getParameter: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
			if (length <= 0)
				return 0;
			var bezierLength = Curve.getLength(
					p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
			if (length >= bezierLength)
				return 1;
			// Let's use the Van Wijngaarden–Dekker–Brent Method to find
			// solutions more reliably than with False Position Method.
			function f(t) {
				return Curve.getPartLength(
						p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) - length;
			}
			return MathUtils.brent(f, 0, length / bezierLength, 10e-6);
		}
	}
}, new function() {
	function evaluate(that, t, type) {
		// Calculate the polynomial coefficients. caution: handles are relative
		// to points
		var point1 = that._segment1._point,
			handle1 = that._segment1._handleOut,
			handle2 = that._segment2._handleIn,
			point2 = that._segment2._point,
			x, y;

		// Handle special case at beginning / end of curve
		// TODO: Port back to Scriptographer, so 0.000000000001 won't be
		// required anymore
		if (t == 0 || t == 1) {
			var point;
			switch (type) {
			case 0: // point
				point = t == 0 ? point1 : point2;
				break;
			case 1: // tangent
			case 2: // normal
				point = t == 0
					? handle1.isZero()
						? handle2.isZero()
							? point2.subtract(point1)
							: point2.add(handle2).subtract(point1)
						: handle1
					: handle2.isZero() // t == 1
						? handle1.isZero()
							? point1.subtract(point2)
							: point1.add(handle1).subtract(point2)
						: handle2;
				break;
			}
			x = point.x;
			y = point.y;
		} else {
			var dx = point2.x - point1.x,
				cx = 3 * handle1.x,
				bx = 3 * (dx + handle2.x - handle1.x) - cx,
				ax = dx - cx - bx,

				dy = point2.y - point1.y,
				cy = 3.0 * handle1.y,
				by = 3.0 * (dy + handle2.y - handle1.y) - cy,
				ay = dy - cy - by;

			switch (type) {
			case 0: // point
				x = ((ax * t + bx) * t + cx) * t + point1.x;
				y = ((ay * t + by) * t + cy) * t + point1.y;
				break;
			case 1: // tangent
			case 2: // normal
				// Simply use the derivation of the bezier function for both
				// the x and y coordinates:
				x = (3 * ax * t + 2 * bx) * t + cx,
				y = (3 * ay * t + 2 * by) * t + cy;
			}
		}
		// The normal is simply the rotated tangent:
		// TODO: Rotate normals the other way in Scriptographer too?
		// (Depending on orientation, I guess?)
		return type == 2 ? new Point(y, -x) : new Point(x, y);
	}

	return {
		getPoint: function(parameter) {
			return evaluate(this, parameter, 0);
		},

		getTangent: function(parameter) {
			return evaluate(this, parameter, 1);
		},

		getNormal: function(parameter) {
			return evaluate(this, parameter, 2);
		}
	};
});
<commit_msg>Add comments about initial guess.<commit_after>var Curve = this.Curve = Base.extend({
	beans: true,

	initialize: function(arg0, arg1, arg2, arg3) {
		if (arguments.length == 0) {
			this._segment1 = new Segment();
			this._segment2 = new Segment();
		} else if (arguments.length == 1) {
			// TODO: If beans are not activated, this won't copy from
			// an existing segment. OK?
			this._segment1 = new Segment(arg0.segment1);
			this._segment2 = new Segment(arg0.segment2);
		} else if (arguments.length == 2) {
			if (arg0 instanceof Path) {
				this._path = arg0;
				this._index1 = arg1;
				this._updateSegments();
			} else {
				this._segment1 = new Segment(arg0);
				this._segment2 = new Segment(arg1);
			}
		} else if (arguments.length == 4) {
			this._segment1 = new Segment(arg0, null, arg1);
			this._segment2 = new Segment(arg3, arg2, null);
		}
	},

	_updateSegments: function() {
		if (this._path) {
			this._index2 = this._index1 + 1;
			// A closing curve?
			var segments = this._path._segments;
			if (this._index2 >= segments.length)
				this._index2 = 0;
			this._segment1 = segments[this._index1];
			this._segment2 = segments[this._index2];
		}
	},

	/**
	 * The first anchor point of the curve.
	 */
	getPoint1: function() {
		return this._segment1._point;
	},

	setPoint1: function() {
		var point = Point.read(arguments);
		this._segment1._point.set(point.x, point.y);
	},

	/**
	 * The second anchor point of the curve.
	 */
	getPoint2: function() {
		return this._segment2._point;
	},

	setPoint2: function() {
		var point = Point.read(arguments);
		this._segment2._point.set(point.x, point.y);
	},

	/**
	 * The handle point that describes the tangent in the first anchor point.
	 */
	getHandle1: function() {
		return this._segment1._handleOut;
	},

	setHandle1: function() {
		var point = Point.read(arguments);
		this._segment1._handleOut.set(point.x, point.y);
	},

	/**
	 * The handle point that describes the tangent in the second anchor point.
	 */
	getHandle2: function() {
		return this._segment2._handleIn;
	},

	setHandle2: function() {
		var point = Point.read(arguments);
		this._segment2._handleIn.set(point.x, point.y);
	},

	/**
	 * The first segment of the curve.
	 */
	getSegment1: function() {
		return this._segment1;
	},

	/**
	 * The second segment of the curve.
	 */
	getSegment2: function() {
		return this._segment2;
	},

	getPath: function() {
		return this._path;
	},

	getIndex: function() {
		return this._index1;
	},

	_setIndex: function(index) {
		this._index1 = index;
		this._updateSegments();
	},

	getNext: function() {
		var curves = this._path && this._path._curves;
		// TODO: Add cyclic looping when closed back to Scriptographer
		return curves && (curves[this._index1 + 1]
				|| this._path.closed && curves[0]) || null;
	},

	getPrevious: function() {
		var curves = this._path && this._path._curves;
		return curves && (curves[this._index1 - 1]
				|| this._path.closed && curves[curves.length - 1]) || null;
	},

	getCurveValues: function() {
		var p1 = this._segment1._point,
			h1 = this._segment1._handleOut,
			h2 = this._segment2._handleIn,
			p2 = this._segment2._point;
		return [
			p1.x, p1.y,
			p1.x + h1.x, p1.y + h1.y,
			p2.x + h2.x, p2.y + h2.y,
			p2.x, p2.y
		];
	},

	getLength: function() {
		return Curve.getLength.apply(Curve, this.getCurveValues());
	},

	/**
	 * Checks if this curve is linear, meaning it does not define any curve
	 * handle.

	 * @return {@true if the curve is linear}
	 */
	isLinear: function() {
		return this._segment1._handleOut.isZero()
				&& this._segment2._handleIn.isZero();
	},

	getParameter: function(length) {
		return Curve.getParameter.apply(Curve,
				this.getCurveValues().concat(length));
	},

	// TODO: getParameter(point, precision)
	// TODO: getLocation
	// TODO: getIntersections
	// TODO: adjustThroughPoint

	transform: function(matrix) {
		return new Curve(
				matrix.transform(this._segment1._point),
				matrix.transform(this._segment1._handleOut),
				matrix.transform(this._segment2._handleIn),
				matrix.transform(this._segment2._point));
	},

	reverse: function() {
		return new Curve(this._segment2.reverse(), this._segment1.reverse());
	},

	// TODO: divide
	// TODO: split
	// TODO: getPartLength(fromParameter, toParameter)

	clone: function() {
		return new Curve(this._segment1, this._segment2);
	},

	toString: function() {
		return '{ point1: ' + this._segment1._point
				+ (!this._segment1._handleOut.isZero()
					? ', handle1: ' + this._segment1._handleOut : '')
				+ (this._segment2._handleIn.isZero()
					? ', handle2: ' + this._segment2._handleIn : '')
				+ ', point2: ' + this._segment2._point
				+ ' }';
	},

	statics: {
		getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
			// TODO: Check for straight lines and handle separately.

			// Calculate the coefficients of a Bezier derivative.
			var ax = 9 * (c1x - c2x) + 3 * (p2x - p1x),
				bx = 6 * (p1x + c2x) - 12 * c1x,
				cx = 3 * (c1x - p1x),

				ay = 9 * (c1y - c2y) + 3 * (p2y - p1y),
				by = 6 * (p1y + c2y) - 12 * c1y,
				cy = 3 * (c1y - p1y);

			function ds(t) {
				// Calculate quadratic equations of derivatives for x and y
				var dx = (ax * t + bx) * t + cx,
					dy = (ay * t + by) * t + cy;
				return Math.sqrt(dx * dx + dy * dy);
			}

			return MathUtils.gauss(ds, 0.0, 1.0, 8);
		},

		subdivide: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
			var u = 1 - t,
				// Interpolate from 4 to 3 points
				p3x = u * p1x + t * c1x,
				p3y = u * p1y + t * c1y,
				p4x = u * c1x + t * c2x,
				p4y = u * c1y + t * c2y,
				p5x = u * c2x + t * p2x,
				p5y = u * c2y + t * p2y,
				// Interpolate from 3 to 2 points
				p6x = u * p3x + t * p4x,
				p6y = u * p3y + t * p4y,
				p7x = u * p4x + t * p5x,
				p7y = u * p4y + t * p5y,
				// Interpolate from 2 points to 1 point
				p8x = u * p6x + t * p7x,
				p8y = u * p6y + t * p7y;
			// We now have all the values we need to build the subcurves
			return [
				[p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], // left
				[p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] // right
			];
		},

		getPartLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t, right) {
			if (t == 0)
				return 0;
			if (t < 1) {
				curve = Curve.subdivide(p1x, p1y, c1x, c1y, c2x, c2y,
						p2x, p2y, t)[right ? 1 : 0];
			} else {
				curve = arguments;
			}
			return Curve.getLength.apply(Curve, curve);
		},

		getParameter: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
			if (length <= 0)
				return 0;
			var bezierLength = Curve.getLength(
					p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
			if (length >= bezierLength)
				return 1;
			// Let's use the Van Wijngaarden–Dekker–Brent Method to find
			// solutions more reliably than with False Position Method.
			function f(t) {
				return Curve.getPartLength(
						p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) - length;
			}
			// Use length / bezierLength for an initial guess for b, to bring
			// us closer:
			return MathUtils.brent(f, 0, length / bezierLength,
					MathUtils.TOLERANCE);
		}
	}
}, new function() {
	function evaluate(that, t, type) {
		// Calculate the polynomial coefficients. caution: handles are relative
		// to points
		var point1 = that._segment1._point,
			handle1 = that._segment1._handleOut,
			handle2 = that._segment2._handleIn,
			point2 = that._segment2._point,
			x, y;

		// Handle special case at beginning / end of curve
		// TODO: Port back to Scriptographer, so 0.000000000001 won't be
		// required anymore
		if (t == 0 || t == 1) {
			var point;
			switch (type) {
			case 0: // point
				point = t == 0 ? point1 : point2;
				break;
			case 1: // tangent
			case 2: // normal
				point = t == 0
					? handle1.isZero()
						? handle2.isZero()
							? point2.subtract(point1)
							: point2.add(handle2).subtract(point1)
						: handle1
					: handle2.isZero() // t == 1
						? handle1.isZero()
							? point1.subtract(point2)
							: point1.add(handle1).subtract(point2)
						: handle2;
				break;
			}
			x = point.x;
			y = point.y;
		} else {
			var dx = point2.x - point1.x,
				cx = 3 * handle1.x,
				bx = 3 * (dx + handle2.x - handle1.x) - cx,
				ax = dx - cx - bx,

				dy = point2.y - point1.y,
				cy = 3.0 * handle1.y,
				by = 3.0 * (dy + handle2.y - handle1.y) - cy,
				ay = dy - cy - by;

			switch (type) {
			case 0: // point
				x = ((ax * t + bx) * t + cx) * t + point1.x;
				y = ((ay * t + by) * t + cy) * t + point1.y;
				break;
			case 1: // tangent
			case 2: // normal
				// Simply use the derivation of the bezier function for both
				// the x and y coordinates:
				x = (3 * ax * t + 2 * bx) * t + cx,
				y = (3 * ay * t + 2 * by) * t + cy;
			}
		}
		// The normal is simply the rotated tangent:
		// TODO: Rotate normals the other way in Scriptographer too?
		// (Depending on orientation, I guess?)
		return type == 2 ? new Point(y, -x) : new Point(x, y);
	}

	return {
		getPoint: function(parameter) {
			return evaluate(this, parameter, 0);
		},

		getTangent: function(parameter) {
			return evaluate(this, parameter, 1);
		},

		getNormal: function(parameter) {
			return evaluate(this, parameter, 2);
		}
	};
});
<|endoftext|>


<commit_before>import '../extension/Object.deepAssign.js';
import Discord from 'discord.js';
import Plugin from '../plugin.js';
import * as tools from '../lib/tools.js';

/** A plugin to manage other plugins. */
export default class AdminPlugin extends Plugin {
	/** Creates a new AdminPlugin object.
	 * @param {Saiko} saiko - a Saiko object, which is gonna use that plugin
	 * @returns {AdminPlugin} - a AdminPlugin object */
	constructor(saiko) {
		super(saiko);

		this.name = 'admin';
		this.description = `Commands to administrate ${saiko.name}.`;
		this.commands = [
			{
				trigger: 'plugins',
				action: (message, ...params) => {
					const [, action]   = params;
					const plugin       = this.saiko.plugins.find(plugin => plugin.name === params[2]);
					const guildMode    = params[3] === 'guild' && message.channel.type === 'text';
					const place        = guildMode ? message.channel.guild : message.channel;
					const config       = this.saiko.data[guildMode ? 'guilds' : 'channels'];

					if (!config[place.id])
						config[place.id] = {};

					const placeConfig  = config[place.id];
					const pluginConfig = plugin === undefined ? {} : (placeConfig.plugins || {})[plugin.name] || {};

					// invalid or no paramaters
					if (!['enable', 'disable', 'default'].includes(action) ||
					    plugin === undefined) {
						const embed = new Discord.RichEmbed()
							.setColor('#14908d')
							.setTitle('Plugins')
							.setDescription(
								'**Usage:**\n' +
								'    plugins enable "<plugin name>" [guild]\n' +
								'    plugins disable "<plugin name>" [guild]\n' +
								'    plugins default "<plugin name>" [guild]\n' +
								'\n' +
								'**Available plugins:**'
							);

						this.saiko.plugins.forEach(plugin => {
							const pluginEnabled = this.saiko.isPluginEnabled(plugin, message.channel);
							embed.addField(
								`${tools.getEmoji(pluginEnabled ? 'check mark' : 'cross mark')} ${plugin.name}`,
								plugin.description
							);
						});

						return {embed};
					}

					// the state is already set to what the user wants
					if ((action === 'enable'  && pluginConfig.enabled === true) ||
					    (action === 'disable' && pluginConfig.enabled === false)) {
						const embed = new Discord.RichEmbed()
							.setColor('#14908d')
							.setTitle('Plugins')
							.setDescription(`Plugin ${plugin.name} already is ${action}d on this ${guildMode ? 'guild' : 'channel'}.`);

						return {embed};
					}

					// change config
					Object.deepAssign(placeConfig, {
						plugins: {
							[plugin.name]: {
								enabled: action === 'enable'  ? true :
								         action === 'disable' ? false : undefined
							}
						}
					});

					if (action === 'default')
						delete placeConfig.plugins[plugin.name].enabled;

					this.saiko.saveData();

					const actionDescription =
						action === 'enable'  ? 'enabled' :
						action === 'disable' ? 'disabled' :
						action === 'default' ? 'reset to default state' : undefined;
					const embed = new Discord.RichEmbed()
						.setColor('#14908d')
						.setTitle('Plugins')
						.setDescription(`Plugin ${plugin.name} ${actionDescription} on this ${guildMode ? 'guild' : 'channel'}.`);

					return {embed};
				}
			}
		];
	}

	/** Runs matching commands.
	 * @listens Discord.Client#message
	 * @param {Discord.Message} message - new message
	 * @returns {void} */
	onMessage(message) {
		this.runMatchingCommand(message);
	}
}
<commit_msg>update AdminPlugin's plugins command's description<commit_after>import '../extension/Object.deepAssign.js';
import Discord from 'discord.js';
import Plugin from '../plugin.js';
import * as tools from '../lib/tools.js';

/** A plugin to manage other plugins. */
export default class AdminPlugin extends Plugin {
	/** Creates a new AdminPlugin object.
	 * @param {Saiko} saiko - a Saiko object, which is gonna use that plugin
	 * @returns {AdminPlugin} - a AdminPlugin object */
	constructor(saiko) {
		super(saiko);

		this.name = 'admin';
		this.description = `Commands to administrate ${saiko.name}.`;
		this.commands = [
			{
				trigger: 'plugins',
				action: (message, ...params) => {
					const [, action]   = params;
					const plugin       = this.saiko.plugins.find(plugin => plugin.name === params[2]);
					const guildMode    = params[3] === 'guild' && message.channel.type === 'text';
					const place        = guildMode ? message.channel.guild : message.channel;
					const config       = this.saiko.data[guildMode ? 'guilds' : 'channels'];

					if (!config[place.id])
						config[place.id] = {};

					const placeConfig  = config[place.id];
					const pluginConfig = plugin === undefined ? {} : (placeConfig.plugins || {})[plugin.name] || {};

					// invalid or no paramaters
					if (!['enable', 'disable', 'default'].includes(action) ||
					    plugin === undefined) {
						const embed = new Discord.RichEmbed()
							.setColor('#14908d')
							.setTitle('Plugins')
							.setDescription(
								'**Usage:**\n' +
								'    plugins <action> <plugin name> [guild]\n' +
								'\n' +
								'Actions:\n' +
								'- enable - enable plugin,\n' +
								'- disable - disable plugin,\n' +
								'- default - reset plugin\'s status to the defaul value.\n' +
								'\n' +
								'If the plugin name contains spaces, use quote marks around the name.\n' +
								'If you add "guild" at the end, the changes will be applied to the guild\'s config ' +
								'instead of the channel\'s config (that option is ignored for DM and group DM channels).\n' +
								'\n' +
								'**Available plugins:**'
							);

						this.saiko.plugins.forEach(plugin => {
							const pluginEnabled = this.saiko.isPluginEnabled(plugin, message.channel);
							embed.addField(
								`${tools.getEmoji(pluginEnabled ? 'check mark' : 'cross mark')} ${plugin.name}`,
								plugin.description
							);
						});

						return {embed};
					}

					// the state is already set to what the user wants
					if ((action === 'enable'  && pluginConfig.enabled === true) ||
					    (action === 'disable' && pluginConfig.enabled === false)) {
						const embed = new Discord.RichEmbed()
							.setColor('#14908d')
							.setTitle('Plugins')
							.setDescription(`Plugin ${plugin.name} already is ${action}d on this ${guildMode ? 'guild' : 'channel'}.`);

						return {embed};
					}

					// change config
					Object.deepAssign(placeConfig, {
						plugins: {
							[plugin.name]: {
								enabled: action === 'enable'  ? true :
								         action === 'disable' ? false : undefined
							}
						}
					});

					if (action === 'default')
						delete placeConfig.plugins[plugin.name].enabled;

					this.saiko.saveData();

					const actionDescription =
						action === 'enable'  ? 'enabled' :
						action === 'disable' ? 'disabled' :
						action === 'default' ? 'reset to default state' : undefined;
					const embed = new Discord.RichEmbed()
						.setColor('#14908d')
						.setTitle('Plugins')
						.setDescription(`Plugin ${plugin.name} ${actionDescription} on this ${guildMode ? 'guild' : 'channel'}.`);

					return {embed};
				}
			}
		];
	}

	/** Runs matching commands.
	 * @listens Discord.Client#message
	 * @param {Discord.Message} message - new message
	 * @returns {void} */
	onMessage(message) {
		this.runMatchingCommand(message);
	}
}
<|endoftext|>


<commit_before>var target;

process.on("message", function(message) {
  switch(message.type) {
    case "__init":
      target = require(message.data);
      process.send({ id: message.id });
      break;
    default:
      processMessage(message);
      break;
  }
});

function processMessage(message) {
  var deferred = target[message.type](message.data, (err, data) => err ? handleError(message)(err) : handleSuccess(message)(data));

  if (deferred) {
    deferred.then(handleSuccess(message), handlerError(message));
  }
}

function handleSuccess(message) {
  return function(data) {
    process.send({
      id: message.id,
      data: data
    });
  };
}

function handlerError(message) {
  return function(error) {
    //
    // TODO: Error isn't properly serializing when it is of type Error.
    // Gotta make sure to smooth that out soon.
    //
    process.send({
      id: message.id,
      error: error || "Unknown error"
    });
  };
}
<commit_msg>added isChild property to process to make it really easy to test if you are running in a child process or not<commit_after>var target;

// Tag the current process as child... For convenience.
Object.defineProperty(process, "isChild", {
  get: function() {
    return true;
  },
  set: function() {
    throw new Error("isChild is a readonly property.")
  }
});

process.on("message", function(message) {
  switch(message.type) {
    case "__init":
      target = require(message.data);
      process.send({ id: message.id });
      break;
    default:
      processMessage(message);
      break;
  }
});

function processMessage(message) {
  var deferred = target[message.type](message.data, (err, data) => err ? handleError(message)(err) : handleSuccess(message)(data));

  if (deferred) {
    deferred.then(handleSuccess(message), handlerError(message));
  }
}

function handleSuccess(message) {
  return function(data) {
    process.send({
      id: message.id,
      data: data
    });
  };
}

function handlerError(message) {
  return function(error) {
    //
    // TODO: Error isn't properly serializing when it is of type Error.
    // Gotta make sure to smooth that out soon.
    //
    process.send({
      id: message.id,
      error: error || "Unknown error"
    });
  };
}
<|endoftext|>


<commit_before>import {inject} from 'aurelia-framework';
import {Rest} from 'spoonx/aurelia-api';

@inject(Rest)
export class Repository {
  /**
   * Construct.
   *
   * @param {Rest} restClient
   *
   * @constructor
   */
  constructor (restClient) {
    this.api = restClient;
  }

  /**
   * Set an entity instance.
   * Used to harvest information such as the resource name.
   *
   * @param {Entity} entity
   * @return {Repository}
   */
  setEntity (entity) {
    this.entity = entity;

    return this;
  }

  /**
   * Set the entity reference.
   * Used to create new instances of the entity using aurelia DI.
   *
   * @param {Entity} entityReference
   * @return {Repository}
   */
  setEntityReference (entityReference) {
    this.entityReference = entityReference;

    return this;
  }

  /**
   * Perform a find query.
   *
   * @param {null|{}|Number} criteria Criteria to add to the query.
   * @param {boolean}        raw      Set to true to get a POJO in stead of populated entities.
   * @return {*}
   */
  find (criteria, raw) {
    let findQuery = this.api.find(this.entity.resource, criteria);

    if (raw) {
      return findQuery;
    }

    return findQuery.then(x => this.populateEntities(x));
  }

  /**
   * Perform a count.
   *
   * @param {null|{}} criteria
   * @return {*}
   */
  count (criteria) {
    return this.api.find(this.entity.resource + '/count', criteria);
  }

  /**
   * Create a new, populated entity based on supplied `data`.
   *
   * @param {{}} data
   * @return {*}
   */
  create (data) {
    return this.getPopulatedEntity(data);
  }

  /**
   * Populate entities based on supplied data.
   *
   * @param {{}} data
   * @return {*}
   */
  populateEntities (data) {
    if (!data) {
      return null;
    }

    if (!Array.isArray(data)) {
      return this.getPopulatedEntity(data);
    }

    let collection = [];

    data.forEach(source => {
      collection.push(this.getPopulatedEntity(source));
    });

    return collection;
  }

  /**
   * @param {{}} data
   * @return {Entity}
   */
  getPopulatedEntity (data) {
    return this.getNewEntity().setData(data);
  }

  /**
   * Get a new instance for entityReference.
   *
   * @return {Entity}
   */
  getNewEntity () {
    return this.entityManager.getEntity(this.entityReference);
  }
}
<commit_msg>Associations<commit_after>import {inject} from 'aurelia-framework';
import {Rest} from 'spoonx/aurelia-api';
import {metadata} from 'aurelia-metadata';
import {AssociationMetaData} from './association-metadata';

@inject(Rest)
export class Repository {
  /**
   * Construct.
   *
   * @param {Rest} restClient
   *
   * @constructor
   */
  constructor (restClient) {
    this.api = restClient;
  }

  /**
   * Set an entity instance.
   * Used to harvest information such as the resource name.
   *
   * @param {Entity} entity
   * @return {Repository}
   */
  setEntity (entity) {
    this.entity = entity;

    return this;
  }

  /**
   * Set the entity reference.
   * Used to create new instances of the entity using aurelia DI.
   *
   * @param {Entity} entityReference
   * @return {Repository}
   */
  setEntityReference (entityReference) {
    this.entityReference = entityReference;

    return this;
  }

  /**
   * Perform a find query.
   *
   * @param {null|{}|Number} criteria Criteria to add to the query.
   * @param {boolean}        raw      Set to true to get a POJO in stead of populated entities.
   * @return {*}
   */
  find (criteria, raw) {
    let findQuery = this.api.find(this.entity.resource, criteria);

    if (raw) {
      return findQuery;
    }

    return findQuery.then(x => this.populateEntities(x));
  }

  /**
   * Perform a count.
   *
   * @param {null|{}} criteria
   * @return {*}
   */
  count (criteria) {
    return this.api.find(this.entity.resource + '/count', criteria);
  }

  /**
   * Create a new, populated entity based on supplied `data`.
   *
   * @param {{}} data
   * @return {*}
   */
  create (data) {
    return this.getPopulatedEntity(data);
  }

  /**
   * Populate entities based on supplied data.
   *
   * @param {{}} data
   * @return {*}
   */
  populateEntities (data) {
    if (!data) {
      return null;
    }

    if (!Array.isArray(data)) {
      return this.getPopulatedEntity(data);
    }

    let collection = [];

    data.forEach(source => {
      collection.push(this.getPopulatedEntity(source));
    });

    return collection;
  }

  /**
   * @param {{}} data
   * @return {Entity}
   */
  getPopulatedEntity (data) {
    let entity               = this.getNewEntity();
    let associationsMetadata = metadata.getOwn(AssociationMetaData.key, entity);
    let populatedData        = {};
    let key;

    for (key in data) {
      if (!data.hasOwnProperty(key)) {
        continue;
      }

      if (!associationsMetadata || !associationsMetadata.has(key) || typeof data[key] !== 'object') {
        // Not an association, or not an object. clean copy.
        populatedData[key] = data[key];

        continue;
      }

      // Fetch the reference
      let reference = associationsMetadata.fetch(key);

      // Get the populated entities.
      populatedData[key] = this.entityManager.getRepository(reference).populateEntities(data[key]);
    }

    return entity.setData(populatedData);
  }

  /**
   * Get a new instance for entityReference.
   *
   * @return {Entity}
   */
  getNewEntity () {
    return this.entityManager.getEntity(this.entityReference);
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * restposten is a data restposten module for flatiron. It's an adaptation of
 * resourceful and is intended to be used as a replacement for Resourceful. It
 * uses JSON Schema for data validation. It also has support for JSON Schema
 * links, which are directly mapped to relations in the internal data model.
 *
 * @module restposten
 */

var events = require('events');
var clone = require('clone');
var append = require('append');
var errs = require('errs');
var inflection = require('i')();
var pluralize = inflection.pluralize;
var camelize = inflection.camelize;
var errors = require('./errors.js');

exports.resources = {};
exports.deferredRelations = {};
exports.database;
exports.validator;
exports.commonSchemas = {};

/**
 * Registers a resource.
 *
 * @param {String}
 *                name
 * @param {Resource}
 *                resource
 */
exports.register = function(name, resource) {
  exports.resources[name] = resource;
};

/**
 * Unregisters a resource.
 *
 * @param {String}
 *                name
 */
exports.unregister = function(name) {
  delete this.resources[name];
};

/**
 * Registers a schema for common use.
 */
exports.registerCommonSchema = function(schema) {
  if (typeof schema.id != 'undefined') {
    exports.commonSchemas[schema.id] = schema;
    if (typeof exports.validator != 'undefined')
      exports.validator.register(schema);
  }
};

/**
 * Defines a new schema factory for creating instances of schemas.
 *
 * @param {String}
 *                name lower case singular name of the schema (e.g. `'address'`)
 * @param {Object}
 *                schema JSON schema object
 *
 * @returns factory function for creating new instances
 */
exports.define = function(name, schema) {
  // register schema
  exports.registerCommonSchema(schema);

  var resource = new Resource(name, schema);

  // register resource
  exports.register(name, resource);

  // check if any deferred relationships from previously defined resources are
  // related to this resource
  if (typeof exports.deferredRelations[name] != 'undefined') {
    // for every deferredRelationship we find for our current resource, resolve
    // it
    exports.deferredRelations[name].forEach(function(r) {
      resolveRelations(r.resource);
    });
  }

  // resolve the relations for the newly created resource
  resolveRelations(resource);
  resource.init();

  return resource;
};

/**
 * Resolves the relations for a resource.
 *
 * @param {Resource}
 *                resource
 *
 * @private
 */
function resolveRelations(resource) {
  var props = resource.schema.properties;

  // go through the schema's properties and check for links with "rel": "full"
  Object.keys(props).forEach(function (key) {
    var property = props[key];
    if (typeof property.links == 'undefined')
      return;

    var links = property.links;
    var len = links.length;
    var link;
    for (var i = 0; i < len; i++) {
      link = links[i];
      if (link.rel === 'full')
        foreignKey(resource, key, link.href);
    }
  });
}

/**
 * Creates a foreign key relationship. (One-To-Many)
 *
 * @param {Resource}
 *                from
 *
 * @private
 */
function foreignKey(from, propertyName, href) {
  var components = href.split('/');

  // this method will only work for standard values like href = 'author/{@}'
  if (components.length != 2 && components[1] !== "{@}")
    return;

  var otherResourceName = components[0];

  // if other schema is not yet defined, defer relationship
  if (typeof exports.resources[otherResourceName] == 'undefined') {
    var rel = {
      name: from.name,
      resource: from,
      property: propertyName
    };

    if (typeof exports.deferredRelations[otherResourceName] == 'undefined')
      exports.deferredRelations[otherResourceName] = [];

    exports.deferredRelations[otherResourceName].push(rel);

    return;
  }

  var getAll = 'get' + camelize(pluralize(from.name));

  // define function to get the referenced collection
  // e.g. getBooks()
  var other = exports.resources[otherResourceName];
  other.Instance.prototype[getAll] = function(callback) {
    var query = {};
    query[propertyName] = this.properties._id;
    from.get(query, callback);
  };

  var getOne = 'get' + camelize(otherResourceName);

  // define function to get the referenced document
  // e.g. getAuthor()
  from.Instance.prototype[getOne] = function(callback) {
    other.getOne(this.properties[propertyName], callback);
  }
}

/**
 * Creates a resource with a name and a schema.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 *
 */
function Resource(name, schema) {
  var self = this;
  this.schema = schema;
  this.name = name;

  this.allProperties = schema.properties;
  if (typeof schema.allOf != 'undefined') {
    schema.allOf.reverse().forEach(function (inc) {
      var other = {};
      if (typeof inc['$ref'] != 'undefined')
        other = exports.commonSchemas[inc['$ref']];
      else
        other = inc;

      if (typeof other.properties == 'undefined')
        return;

      self.allProperties = append(other.properties, self.allProperties);
    });
  }

  // constructor for instances of this resource
  this.Instance = function(properties) {
    var inst = this;

    // define not-enumerable properties
    Object.defineProperty(this, 'resource', {
      enumerable: false,
      value: self
    });

    Object.defineProperty(this, 'properties', {
      enumerable: false,
      set: function (properties) {
        // remove old keys
        Object.keys(inst).forEach(function (k) {
          delete inst[k];
        });

        // append new
        append(inst, properties);
      },
      get: function () {
        return properties;
      }
    });

    // mix all properties from properties into this
    append(this, properties);
  };
  this.Instance.prototype = new ResourceInstance;
}

Resource.prototype = new events.EventEmitter;
exports.Resource = Resource;

/**
 * Emits the 'init' event.
 *
 * @private
 */
Resource.prototype.init = function() {
  this.emit('init', this);
};

/**
 * Creates a new instance of the resource and saves it.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.create = function(properties, callback) {
  var instance = this.instantiate(properties);
  instance.save(callback);
};

/**
 * Updates an instance of the resource. (Must have an _id property)
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.save = Resource.prototype.create;

/**
 * Updates an instance of the resource. (Must match the criteria)
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.update = function(criteria, properties, options, callback) {
  if (arguments.length == 3) {
    callback = options;
    options = {};
  }

  var instance = this.instantiate(properties);
  var collName = pluralize(this.name);
  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.update(criteria, properties, options, function (err, result) {
      if (err)
        return callback(err);

      callback(null, result);
    });
  });
};

/**
 * Creates an instance with some properties.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 *
 * @returns {ResourceInstance} new instance
 */
Resource.prototype.instantiate = function(properties) {
  return new (this.Instance)(properties);
};

Resource.prototype.__defineGetter__('_resource', function() {
  return this.name;
});

Resource.prototype.__defineGetter__('lowerResource', function() {
  return pluralize(this.name);
});

/**
 * Creates an instance of a resource. Instances are usually created via
 * `Resource.create()`.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 */
function ResourceInstance() {
}
exports.ResourceInstance = ResourceInstance;

/**
 * Validates itself by using the global JSON Schema validator.
 *
 * @returns {Object[]} an array of objects that contain error information
 */
ResourceInstance.prototype.validate = function() {
  var errs = exports.validator.validate(this.properties, this.resource.schema);

  // transform the errors into a more convenient format
  var len = errs.length;
  var i, e1, e2;
  for (i = 0; i < len; i++) {
    e1 = errs[i];

    e2 = {
      errorType: e1.kind,
      errorMessage: e1.desc
    };

    var context = e1.instanceContext;

    if (context !== "#")
      e2.property = context.replace(/^#\//, '').replace(/\//g, '.');

    if (typeof e1.testedValue != 'undefined')
      e2.value = e1.testedValue;

    e2.constraint = {};
    e2.constraint[e1.constraintName] = e1.constraintValue;

    errs[i] = e2;
  }

  return errs;
};

/**
 * Get an array of matching instances.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.get = function (query, fields, options, callback) {
  if (arguments.length == 2) {
    callback = fields;
    options = {};
    fields = {};
  } else if (arguments.length == 3) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.find(query, fields, options, function (err, results) {
      if (err)
        return callback(err);

      // instantiate all objects
      var len = results.length;
      for (var i = 0; i < len; i++) {
        results[i] = resource.instantiate(results[i]);
      }
      callback(null, results);
    });
  });
};

/**
 * Get the first matching instance.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [fields]
 * @param {Object}
 *                [options]
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.getOne = function (query, fields, options, callback) {
  if (arguments.length == 2) {
    callback = fields;
    options = {};
    fields = {};
  } else if (arguments.length == 3) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.findOne(query, fields, options, function (err, result) {
      if (err)
        return callback(err);

      // instantiate object
      result = resource.instantiate(result);

      callback(null, result);
    });
  });
};

/**
 * Count all matching instances.
 *
 * @param {Object}
 *                [query]
 * @param {Object}
 *                [options]
 * @param {Function(err,count)}
 *                callback
 */
Resource.prototype.count = function(query, options, callback) {
  if (arguments.length == 1) {
    callback = query;
    query = {};
    options = {};
  } else if (arguments.length == 2) {
    callback = options;
    options = {};
  }

  var collName = pluralize(this.name);
  exports.database.getCollection(collName, function (err, coll) {
    coll.count(query, options, callback);
  });
};

/**
 * Get an array of all instances of the schema.
 *
 * @param {Object}
 *                [fields]
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.all = function(fields, options, callback) {
  if (arguments.length == 1) {
    callback = fields;
    options = {};
    fields = {};
  } else if(arguments.length == 2) {
    callback = fields;
    options = {};
  }

  // query all schema instances
  this.get({}, fields, options, callback);
};

/**
 * Saves itself to the database. If the instance already exists in the database,
 * the old instance is replaced.
 *
 * @param {Object}
 *                [options]
 * @param {Function(err,saved)}
 *                callback
 */
ResourceInstance.prototype.save = function(options, callback) {
  if (arguments.length == 1) {
    callback = options;
    options = {};
  }

  var instance = this;

  // validate instance
  var errors = this.validate();
  if (errors.length > 0) {
    return callback(errors);
  }

  // collection name is the plural of resource name
  var collName = pluralize(this.resource.name);

  // get collection and save to the database
  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    // FIXME callback must receive new instance as a result
    coll.save(instance.properties, options, function(err, saved) {
      if (typeof saved == 'number')
        return callback(null, instance);

      instance.properties = saved;
      callback(null, instance);
    });
  });
};

/**
 * Deletes all matching instances.
 *
 * @param {Object}
 *                query
 * @param {Function(err,deleted)}
 *                callback
 */
Resource.prototype.delete = function(query, callback) {
  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function (err, coll) {
    if (err)
      return callback(err);

    coll.delete(query, callback);
  });
};
<commit_msg>Add setter for JSON Schema validator<commit_after>'use strict';

/**
 * restposten is a data restposten module for flatiron. It's an adaptation of
 * resourceful and is intended to be used as a replacement for Resourceful. It
 * uses JSON Schema for data validation. It also has support for JSON Schema
 * links, which are directly mapped to relations in the internal data model.
 *
 * @module restposten
 */

var events = require('events');
var clone = require('clone');
var append = require('append');
var errs = require('errs');
var inflection = require('i')();
var pluralize = inflection.pluralize;
var camelize = inflection.camelize;
var errors = require('./errors.js');

exports.resources = {};
exports.deferredRelations = {};
exports.database;
exports.validator;
exports.commonSchemas = {};

/**
 * Registers a resource.
 *
 * @param {String}
 *                name
 * @param {Resource}
 *                resource
 */
exports.register = function(name, resource) {
  exports.resources[name] = resource;
};

/**
 * Unregisters a resource.
 *
 * @param {String}
 *                name
 */
exports.unregister = function(name) {
  delete this.resources[name];
};

/**
 * Registers a schema for common use.
 */
exports.registerCommonSchema = function(schema) {
  if (typeof schema.id != 'undefined') {
    exports.commonSchemas[schema.id] = schema;
    if (typeof exports.validator != 'undefined')
      exports.validator.register(schema);
  }
};

/**
 * Sets the JSON Schema validator.
 */
exports.setValidator = function(validator) {
  exports.validator = validator;
  Object.keys(exports.commonSchemas).forEach(function (id) {
    validator.register(exports.commonSchemas[id]);
  });
}

/**
 * Defines a new schema factory for creating instances of schemas.
 *
 * @param {String}
 *                name lower case singular name of the schema (e.g. `'address'`)
 * @param {Object}
 *                schema JSON schema object
 *
 * @returns factory function for creating new instances
 */
exports.define = function(name, schema) {
  // register schema
  exports.registerCommonSchema(schema);

  var resource = new Resource(name, schema);

  // register resource
  exports.register(name, resource);

  // check if any deferred relationships from previously defined resources are
  // related to this resource
  if (typeof exports.deferredRelations[name] != 'undefined') {
    // for every deferredRelationship we find for our current resource, resolve
    // it
    exports.deferredRelations[name].forEach(function(r) {
      resolveRelations(r.resource);
    });
  }

  // resolve the relations for the newly created resource
  resolveRelations(resource);
  resource.init();

  return resource;
};

/**
 * Resolves the relations for a resource.
 *
 * @param {Resource}
 *                resource
 *
 * @private
 */
function resolveRelations(resource) {
  var props = resource.schema.properties;

  // go through the schema's properties and check for links with "rel": "full"
  Object.keys(props).forEach(function (key) {
    var property = props[key];
    if (typeof property.links == 'undefined')
      return;

    var links = property.links;
    var len = links.length;
    var link;
    for (var i = 0; i < len; i++) {
      link = links[i];
      if (link.rel === 'full')
        foreignKey(resource, key, link.href);
    }
  });
}

/**
 * Creates a foreign key relationship. (One-To-Many)
 *
 * @param {Resource}
 *                from
 *
 * @private
 */
function foreignKey(from, propertyName, href) {
  var components = href.split('/');

  // this method will only work for standard values like href = 'author/{@}'
  if (components.length != 2 && components[1] !== "{@}")
    return;

  var otherResourceName = components[0];

  // if other schema is not yet defined, defer relationship
  if (typeof exports.resources[otherResourceName] == 'undefined') {
    var rel = {
      name: from.name,
      resource: from,
      property: propertyName
    };

    if (typeof exports.deferredRelations[otherResourceName] == 'undefined')
      exports.deferredRelations[otherResourceName] = [];

    exports.deferredRelations[otherResourceName].push(rel);

    return;
  }

  var getAll = 'get' + camelize(pluralize(from.name));

  // define function to get the referenced collection
  // e.g. getBooks()
  var other = exports.resources[otherResourceName];
  other.Instance.prototype[getAll] = function(callback) {
    var query = {};
    query[propertyName] = this.properties._id;
    from.get(query, callback);
  };

  var getOne = 'get' + camelize(otherResourceName);

  // define function to get the referenced document
  // e.g. getAuthor()
  from.Instance.prototype[getOne] = function(callback) {
    other.getOne(this.properties[propertyName], callback);
  }
}

/**
 * Creates a resource with a name and a schema.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 *
 */
function Resource(name, schema) {
  var self = this;
  this.schema = schema;
  this.name = name;

  this.allProperties = schema.properties;
  if (typeof schema.allOf != 'undefined') {
    schema.allOf.reverse().forEach(function (inc) {
      var other = {};
      if (typeof inc['$ref'] != 'undefined')
        other = exports.commonSchemas[inc['$ref']];
      else
        other = inc;

      if (typeof other.properties == 'undefined')
        return;

      self.allProperties = append(other.properties, self.allProperties);
    });
  }

  // constructor for instances of this resource
  this.Instance = function(properties) {
    var inst = this;

    // define not-enumerable properties
    Object.defineProperty(this, 'resource', {
      enumerable: false,
      value: self
    });

    Object.defineProperty(this, 'properties', {
      enumerable: false,
      set: function (properties) {
        // remove old keys
        Object.keys(inst).forEach(function (k) {
          delete inst[k];
        });

        // append new
        append(inst, properties);
      },
      get: function () {
        return properties;
      }
    });

    // mix all properties from properties into this
    append(this, properties);
  };
  this.Instance.prototype = new ResourceInstance;
}

Resource.prototype = new events.EventEmitter;
exports.Resource = Resource;

/**
 * Emits the 'init' event.
 *
 * @private
 */
Resource.prototype.init = function() {
  this.emit('init', this);
};

/**
 * Creates a new instance of the resource and saves it.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.create = function(properties, callback) {
  var instance = this.instantiate(properties);
  instance.save(callback);
};

/**
 * Updates an instance of the resource. (Must have an _id property)
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.save = Resource.prototype.create;

/**
 * Updates an instance of the resource. (Must match the criteria)
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.update = function(criteria, properties, options, callback) {
  if (arguments.length == 3) {
    callback = options;
    options = {};
  }

  var instance = this.instantiate(properties);
  var collName = pluralize(this.name);
  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.update(criteria, properties, options, function (err, result) {
      if (err)
        return callback(err);

      callback(null, result);
    });
  });
};

/**
 * Creates an instance with some properties.
 *
 * @param {Object}
 *                properties properties, the resulting instance will have
 *
 * @returns {ResourceInstance} new instance
 */
Resource.prototype.instantiate = function(properties) {
  return new (this.Instance)(properties);
};

Resource.prototype.__defineGetter__('_resource', function() {
  return this.name;
});

Resource.prototype.__defineGetter__('lowerResource', function() {
  return pluralize(this.name);
});

/**
 * Creates an instance of a resource. Instances are usually created via
 * `Resource.create()`.
 *
 * @constructor
 * @classdesc Resources are used to abstract away data management and
 *            validation.
 *
 * @param {String}
 *                name name of the resource
 * @param {Object}
 *                schema JSON Schema for validation of instances
 */
function ResourceInstance() {
}
exports.ResourceInstance = ResourceInstance;

/**
 * Validates itself by using the global JSON Schema validator.
 *
 * @returns {Object[]} an array of objects that contain error information
 */
ResourceInstance.prototype.validate = function() {
  var errs = exports.validator.validate(this.properties, this.resource.schema);

  // transform the errors into a more convenient format
  var len = errs.length;
  var i, e1, e2;
  for (i = 0; i < len; i++) {
    e1 = errs[i];

    e2 = {
      errorType: e1.kind,
      errorMessage: e1.desc
    };

    var context = e1.instanceContext;

    if (context !== "#")
      e2.property = context.replace(/^#\//, '').replace(/\//g, '.');

    if (typeof e1.testedValue != 'undefined')
      e2.value = e1.testedValue;

    e2.constraint = {};
    e2.constraint[e1.constraintName] = e1.constraintValue;

    errs[i] = e2;
  }

  return errs;
};

/**
 * Get an array of matching instances.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.get = function (query, fields, options, callback) {
  if (arguments.length == 2) {
    callback = fields;
    options = {};
    fields = {};
  } else if (arguments.length == 3) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.find(query, fields, options, function (err, results) {
      if (err)
        return callback(err);

      // instantiate all objects
      var len = results.length;
      for (var i = 0; i < len; i++) {
        results[i] = resource.instantiate(results[i]);
      }
      callback(null, results);
    });
  });
};

/**
 * Get the first matching instance.
 *
 * @param {String|Object}
 *                query _id or query object that all resulting instances match
 * @param {Object}
 *                [fields]
 * @param {Object}
 *                [options]
 * @param {Function(err,instance)}
 *                callback
 */
Resource.prototype.getOne = function (query, fields, options, callback) {
  if (arguments.length == 2) {
    callback = fields;
    options = {};
    fields = {};
  } else if (arguments.length == 3) {
    callback = options;
    options = {};
  }

  var resource = this;

  if (typeof query == 'string')
    query = { _id: query };

  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    coll.findOne(query, fields, options, function (err, result) {
      if (err)
        return callback(err);

      // instantiate object
      result = resource.instantiate(result);

      callback(null, result);
    });
  });
};

/**
 * Count all matching instances.
 *
 * @param {Object}
 *                [query]
 * @param {Object}
 *                [options]
 * @param {Function(err,count)}
 *                callback
 */
Resource.prototype.count = function(query, options, callback) {
  if (arguments.length == 1) {
    callback = query;
    query = {};
    options = {};
  } else if (arguments.length == 2) {
    callback = options;
    options = {};
  }

  var collName = pluralize(this.name);
  exports.database.getCollection(collName, function (err, coll) {
    coll.count(query, options, callback);
  });
};

/**
 * Get an array of all instances of the schema.
 *
 * @param {Object}
 *                [fields]
 * @param {Object}
 *                [options]
 * @param {Function(err,instances)}
 *                callback
 */
Resource.prototype.all = function(fields, options, callback) {
  if (arguments.length == 1) {
    callback = fields;
    options = {};
    fields = {};
  } else if(arguments.length == 2) {
    callback = fields;
    options = {};
  }

  // query all schema instances
  this.get({}, fields, options, callback);
};

/**
 * Saves itself to the database. If the instance already exists in the database,
 * the old instance is replaced.
 *
 * @param {Object}
 *                [options]
 * @param {Function(err,saved)}
 *                callback
 */
ResourceInstance.prototype.save = function(options, callback) {
  if (arguments.length == 1) {
    callback = options;
    options = {};
  }

  var instance = this;

  // validate instance
  var errors = this.validate();
  if (errors.length > 0) {
    return callback(errors);
  }

  // collection name is the plural of resource name
  var collName = pluralize(this.resource.name);

  // get collection and save to the database
  exports.database.getCollection(collName, function(err, coll) {
    if (err)
      return callback(err);

    // FIXME callback must receive new instance as a result
    coll.save(instance.properties, options, function(err, saved) {
      if (typeof saved == 'number')
        return callback(null, instance);

      instance.properties = saved;
      callback(null, instance);
    });
  });
};

/**
 * Deletes all matching instances.
 *
 * @param {Object}
 *                query
 * @param {Function(err,deleted)}
 *                callback
 */
Resource.prototype.delete = function(query, callback) {
  var collName = pluralize(this.name);

  exports.database.getCollection(collName, function (err, coll) {
    if (err)
      return callback(err);

    coll.delete(query, callback);
  });
};
<|endoftext|>