<commit_before>// Copyright 2018-2020, University of Colorado Boulder

/**
 * ObjectIO is the root of the IO Type hierarchy.  All IO Types extend from ObjectIO.  This type can be subtyped or
 * used directly for types that only need basic serialization static methods.
 *
 * This type purposefully does not have a `parametricTypes` static member. The presence of this field marks that it is
 * a parametric type. ObjectIO is not.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrew Adare (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import validate from '../../../axon/js/validate.js';
import ValidatorDef from '../../../axon/js/ValidatorDef.js';
import merge from '../../../phet-core/js/merge.js';
import tandemNamespace from '../tandemNamespace.js';

/**
 * @param {Object} phetioObject
 * @param {string} phetioID
 * @constructor
 * @abstract
 */
class ObjectIO {

  constructor( phetioObject, phetioID ) {
    assert && assert( phetioObject, 'phetioObject should be truthy' );
    assert && assert( phetioID, 'phetioID should be truthy' );

    // @public (read-only)
    this.phetioObject = phetioObject;

    // @public (read-only)
    this.phetioID = phetioID;

    // Use the validator defined on the constructor to make sure the phetioObject is valid
    validate( phetioObject, this.constructor.validator );
  }

  /**
   * Return the serialized form of the wrapped PhetioObject. Most often this looks like an object literal that holds the
   * data about the PhetioObject instance.  This can be overridden by subclasses, or types can use ObjectIO type directly
   * to use this implementation. This implementation should call `validate()` on the parameter with its `validator` field.
   * @param {*} o
   * @returns {*}
   * @public
   */
  static toStateObject( o ) {
    return o;
  }

  /**
   * For data-type serialization. Decodes the object from a state into an instance. This can be overridden by subclasses,
   * or types can use ObjectIO type directly to use this implementation.
   * @param {*} o - whatever was returned from the toStateObject method
   * @returns {*} - the deserialized instance of the same type that the toStateObject was provided as a parameter
   * @public
   */
  static fromStateObject( o ) {
    return o;
  }

  /**
   * Map the stateObject to arguments that are passed to the `create` function in PhetioGroup.js (or other
   * `PhetioDynamicElementContainer` creation functions). Note that other non-serialized args (not dealt with here) may
   * be supplied as closure variables. This function only needs to be implemented on IO Types that are
   * phetioDynamicElement: true, such as PhetioGroup or PhetioCapsule elements.
   * @param {Object} stateObject - from a corresponding`toStateObject` method
   * @returns {Array.<*>} - the array of arguments to be passed to creation functions like PhetioGroup.create() or PhetioCapsule.getElement().
   * @public
   */
  static stateToArgsForConstructor( stateObject ) {
    return [];
  }

  /**
   * For reference-type serialization. Applies the stateObject value to the object. When setting PhET-iO state, this
   * function will be called on an instrumented instance to set the stateObject's value to it.
   * For more understanding, please read https://github.com/phetsims/phet-io/blob/master/doc/phet-io-instrumentation-guide.md#three-types-of-deserialization
   * @param {PhetioObject} o
   * @param {Object} value - result from toStateObject
   * @public
   */
  static applyState( o, value ) { }

  /**
   * Get the supertype of the passed in IO Type.
   * @example
   * ObjectIO.getSupertype( SliderIO )
   * --> NodeIO
   * ObjectIO.getSupertype( ObjectIO )
   * --> null
   *
   * @param {function(new:ObjectIO)} typeIO
   * @returns {function(new:ObjectIO)|null} - null if `typeIO` is ObjectIO, because that is the root of the IO hierarchy
   * @public
   */
  static getSupertype( typeIO ) {
    assert && assert( ObjectIO.isIOType( typeIO ), 'IO Type expected' );

    // getPrototypeOf get's the typeIO's parent type, because the prototype is for the parent.
    const supertype = Object.getPrototypeOf( typeIO );
    return supertype === Object.getPrototypeOf( window.Object ) ? null : supertype;
  }

  /**
   * Make sure the ObjectIO subtype has all the required attributes.
   * @param {function(new:ObjectIO)} subtype - class to check
   * @public
   */
  static validateSubtype( subtype ) {

    const typeName = subtype.typeName;
    assert && assert( typeName.indexOf( '.' ) === -1, 'Dots should not appear in type names' );

    // Validate that parametric types look as expected
    if ( typeName.indexOf( '<' ) >= 0 ) {
      assert && assert( Array.isArray( subtype.parameterTypes ), 'angle bracket notation is only used for parametric IO Types that have parameter IO Types' );
      subtype.parameterTypes.forEach( parameterType => assert && assert( ObjectIO.isIOType( parameterType ), `parameter type should be an IO Type: ${parameterType}` ) );
    }

    const splitOnParameters = typeName.split( /[<(]/ )[ 0 ];
    assert && assert( splitOnParameters.indexOf( 'IO' ) === splitOnParameters.length - 'IO'.length, 'IO Type name must end with IO' );

    assert && assert( !subtype.prototype.toStateObject, 'toStateObject should be a static method, not prototype one.' );
    assert && assert( !subtype.prototype.fromStateObject, 'fromStateObject should be a static method, not prototype one.' );
    assert && assert( !subtype.prototype.applyState, 'applyState should be a static method, not prototype one.' );
    assert && assert( !subtype.prototype.stateToArgsForConstructor, 'stateToArgsForConstructor should be a static method, not prototype one.' );

    // Prevent inheritance of methods, see https://github.com/phetsims/phet-io/issues/1623
    if ( !subtype.hasOwnProperty( 'methods' ) ) {
      subtype.methods = {};
    }

    // assert that each public method adheres to the expected schema
    for ( const method in subtype.methods ) {
      const methodObject = subtype.methods[ method ];
      if ( typeof methodObject === 'object' ) {
        assert && assert( ObjectIO.isIOType( methodObject.returnType ), 'return type must be of type IO: ' + methodObject.returnType );

        assert && assert( Array.isArray( methodObject.parameterTypes ),
          'parameter types must be an array: ' + methodObject.parameterTypes );

        methodObject.parameterTypes.forEach( parameterType => {
          assert && assert( ObjectIO.isIOType( parameterType ), 'parameter type must be of type IO: ' + parameterType );
        } );

        assert && assert( typeof methodObject.implementation === 'function',
          'implementation must be of type function: ' + methodObject.implementation );

        assert && assert( typeof methodObject.documentation === 'string',
          'documentation must be of type string: ' + methodObject.documentation );

        assert && methodObject.invocableForReadOnlyElements && assert( typeof methodObject.invocableForReadOnlyElements === 'boolean',
          'invocableForReadOnlyElements must be of type boolean: ' + methodObject.invocableForReadOnlyElements );
      }
    }

    assert && assert( subtype.validator, 'validator must be provided' );
    assert && assert( subtype.documentation, 'documentation must be provided' );
    assert && ValidatorDef.validateValidator( subtype.validator );

    subtype.hasOwnProperty( 'methodOrder' ) && subtype.methodOrder.forEach( function( methodName ) {
      assert && assert( subtype.methods[ methodName ], 'methodName not in public methods: ' + methodName );
    } );

    if ( subtype.hasOwnProperty( 'api' ) ) {
      assert && assert( subtype.api instanceof Object, 'Object expected for api' );
      assert && assert( Object.getPrototypeOf( subtype.api ) === Object.prototype, 'no extra prototype allowed on API object' );
    }

    // TODO make this check recursive, see https://github.com/phetsims/phet-io/issues/1371
    // const supertype = ObjectIO.getSupertype( subtype );
    // const superEvents = [];
    // const getEvents = type => {
    //   if ( type.events ) {
    //     type.events.forEach( e => superEvents.push( e ) );
    //   }
    //   ObjectIO.getSupertype( type ) && getEvents( ObjectIO.getSupertype( type ) );
    // };
    // getEvents( supertype );
    //
    // assert && subtype.events && subtype.events.forEach( function( event ) {
    //   assert( superEvents.indexOf( event ) < 0, 'subtype should not declare event that parent also has.' );
    // } );
  }
}

/**
 * Checks if type is an IO Type
 * @param {*} type
 * @public
 * @returns {boolean} - true if inherits from ObjectIO or is ObjectIO
 */
ObjectIO.isIOType = type => type === ObjectIO || type.prototype instanceof ObjectIO;

/**
 * @typeDef {Object} MethodObject
 * @property {string} documentation
 * @property {function()} implementation - the function to execute when this method is called
 * @property {function(new:ObjectIO)} returnType - the return IO Type of the method
 * @property {Array.<function(new:ObjectIO)>} parameterTypes - the parameter IO Types for the method
 * @property {boolean} [invocableForReadOnlyElements=true] - by default, all methods are invocable for all elements.
 *    However, for some read-only elements, certain methods should not be invocable. In that case, they are marked as
 *    invocableForReadOnlyElements: false.
 */

/**
 * The public methods available for this IO Type. Each method is not just a function, but a collection of metadata
 * about the method to be able to serialize parameters and return types and provide better documentation.
 * @type {Object.<string, MethodObject>}
 */
ObjectIO.methods = {};

/**
 * Documentation that appears in PhET-iO Studio, supports HTML markup.
 * @public
 */
ObjectIO.documentation = 'The root of the wrapper object hierarchy.';

/**
 * The name that this TypeIO will have in the public PhET-iO API. In general, this should only be word characters,
 * ending in "IO". Parameteric types are a special subset of TypeIOs that include their parameters in their typeName.
 * If a TypeIO's parameters are other IO Type(s), then they should be included within angle brackets, like
 * "PropertyIO<Boolean>". Some other types use a more custom format for displaying their parameter types, in this case
 * the parameter section of the type name (immediately following "IO") should begin with an open paren, "(". Thus the
 * schema for a typeName could be defined (using regex) as `[A-Z]\w*IO([(<].*){0,1}`. In most cases, parameterized
 * types should also include a `parameterTypes` field on the TypeIO.
 * @type {string}
 */
ObjectIO.typeName = 'ObjectIO';

/**
 * A validator object to be used to validate the core types that IOTypes wrap.
 * @type {ValidatorDef}
 * @public
 * @override
 */
ObjectIO.validator = {
  isValidValue: value => {

    // It could be a number, string, Object, etc.
    return value !== undefined;
  }
};

ObjectIO.validateSubtype( ObjectIO );

/**
 * Function that creates an IO Type associated with a core type. Methods are forwarded to the core type.
 * @param {function} CoreType, e.g., Bunny
 * @param {string} typeName, e.g., BunnyIO
 * @param {function} [ParentIOType] IO Type of CoreType's parent class
 * @param {Object} [options]
 * @returns {IOType}
 */
ObjectIO.createIOType = ( CoreType, typeName, ParentIOType = ObjectIO, options ) => {
  assert && assert( typeName.endsWith( 'IO' ) || typeName.includes( 'IO<' ), 'IO Type name must end with IO' );
  options = merge( {

    // {string} e.g., "Animal that has a genotype (genetic blueprint) and a phenotype (appearance)."
    documentation: `IO Type for ${typeName.substring( 0, typeName.length - 2 )}`,

    // {Object} - key/value pairs with methods, see PhetioEngineIO for an example
    methods: {},

    events: [],
    parameterTypes: []
  }, options );

  class IOType extends ParentIOType {

    /**
     * @param {PhetioObject} phetioObject
     * @returns {Object}
     * @public
     * @override
     */
    static toStateObject( phetioObject ) {
      validate( phetioObject, this.validator );
      return phetioObject.toStateObject();
    }

    // @public
    static fromStateObject( stateObject ) {
      return CoreType.fromStateObject( stateObject );
    }

    /**
     * @param state
     * @returns {Object[]}
     * @public
     * @override
     */
    static stateToArgsForConstructor( state ) {
      return CoreType.stateToArgsForConstructor( state );
    }

    /**
     * Restores CoreType state after instantiation.
     * @param {PhetioObject} phetioObject
     * @param {Object} state
     * @public
     * @override
     */
    static applyState( phetioObject, state ) {
      validate( phetioObject, this.validator );
      phetioObject.applyState( state );
    }
  }

  IOType.documentation = options.documentation;
  IOType.validator = { valueType: CoreType };
  IOType.typeName = typeName;
  IOType.events = options.events;
  IOType.parameterTypes = options.parameterTypes;
  IOType.methods = options.methods;
  ObjectIO.validateSubtype( IOType );

  return IOType;
};

tandemNamespace.register( 'ObjectIO', ObjectIO );
export default ObjectIO;<commit_msg>Move parentIOType to the options, see https://github.com/phetsims/tandem/issues/188<commit_after>// Copyright 2018-2020, University of Colorado Boulder

/**
 * ObjectIO is the root of the IO Type hierarchy.  All IO Types extend from ObjectIO.  This type can be subtyped or
 * used directly for types that only need basic serialization static methods.
 *
 * This type purposefully does not have a `parametricTypes` static member. The presence of this field marks that it is
 * a parametric type. ObjectIO is not.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrew Adare (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import validate from '../../../axon/js/validate.js';
import ValidatorDef from '../../../axon/js/ValidatorDef.js';
import merge from '../../../phet-core/js/merge.js';
import tandemNamespace from '../tandemNamespace.js';

/**
 * @param {Object} phetioObject
 * @param {string} phetioID
 * @constructor
 * @abstract
 */
class ObjectIO {

  constructor( phetioObject, phetioID ) {
    assert && assert( phetioObject, 'phetioObject should be truthy' );
    assert && assert( phetioID, 'phetioID should be truthy' );

    // @public (read-only)
    this.phetioObject = phetioObject;

    // @public (read-only)
    this.phetioID = phetioID;

    // Use the validator defined on the constructor to make sure the phetioObject is valid
    validate( phetioObject, this.constructor.validator );
  }

  /**
   * Return the serialized form of the wrapped PhetioObject. Most often this looks like an object literal that holds the
   * data about the PhetioObject instance.  This can be overridden by subclasses, or types can use ObjectIO type directly
   * to use this implementation. This implementation should call `validate()` on the parameter with its `validator` field.
   * @param {*} o
   * @returns {*}
   * @public
   */
  static toStateObject( o ) {
    return o;
  }

  /**
   * For data-type serialization. Decodes the object from a state into an instance. This can be overridden by subclasses,
   * or types can use ObjectIO type directly to use this implementation.
   * @param {*} o - whatever was returned from the toStateObject method
   * @returns {*} - the deserialized instance of the same type that the toStateObject was provided as a parameter
   * @public
   */
  static fromStateObject( o ) {
    return o;
  }

  /**
   * Map the stateObject to arguments that are passed to the `create` function in PhetioGroup.js (or other
   * `PhetioDynamicElementContainer` creation functions). Note that other non-serialized args (not dealt with here) may
   * be supplied as closure variables. This function only needs to be implemented on IO Types that are
   * phetioDynamicElement: true, such as PhetioGroup or PhetioCapsule elements.
   * @param {Object} stateObject - from a corresponding`toStateObject` method
   * @returns {Array.<*>} - the array of arguments to be passed to creation functions like PhetioGroup.create() or PhetioCapsule.getElement().
   * @public
   */
  static stateToArgsForConstructor( stateObject ) {
    return [];
  }

  /**
   * For reference-type serialization. Applies the stateObject value to the object. When setting PhET-iO state, this
   * function will be called on an instrumented instance to set the stateObject's value to it.
   * For more understanding, please read https://github.com/phetsims/phet-io/blob/master/doc/phet-io-instrumentation-guide.md#three-types-of-deserialization
   * @param {PhetioObject} o
   * @param {Object} value - result from toStateObject
   * @public
   */
  static applyState( o, value ) { }

  /**
   * Get the supertype of the passed in IO Type.
   * @example
   * ObjectIO.getSupertype( SliderIO )
   * --> NodeIO
   * ObjectIO.getSupertype( ObjectIO )
   * --> null
   *
   * @param {function(new:ObjectIO)} typeIO
   * @returns {function(new:ObjectIO)|null} - null if `typeIO` is ObjectIO, because that is the root of the IO hierarchy
   * @public
   */
  static getSupertype( typeIO ) {
    assert && assert( ObjectIO.isIOType( typeIO ), 'IO Type expected' );

    // getPrototypeOf get's the typeIO's parent type, because the prototype is for the parent.
    const supertype = Object.getPrototypeOf( typeIO );
    return supertype === Object.getPrototypeOf( window.Object ) ? null : supertype;
  }

  /**
   * Make sure the ObjectIO subtype has all the required attributes.
   * @param {function(new:ObjectIO)} subtype - class to check
   * @public
   */
  static validateSubtype( subtype ) {

    const typeName = subtype.typeName;
    assert && assert( typeName.indexOf( '.' ) === -1, 'Dots should not appear in type names' );

    // Validate that parametric types look as expected
    if ( typeName.indexOf( '<' ) >= 0 ) {
      assert && assert( Array.isArray( subtype.parameterTypes ), 'angle bracket notation is only used for parametric IO Types that have parameter IO Types' );
      subtype.parameterTypes.forEach( parameterType => assert && assert( ObjectIO.isIOType( parameterType ), `parameter type should be an IO Type: ${parameterType}` ) );
    }

    const splitOnParameters = typeName.split( /[<(]/ )[ 0 ];
    assert && assert( splitOnParameters.indexOf( 'IO' ) === splitOnParameters.length - 'IO'.length, 'IO Type name must end with IO' );

    assert && assert( !subtype.prototype.toStateObject, 'toStateObject should be a static method, not prototype one.' );
    assert && assert( !subtype.prototype.fromStateObject, 'fromStateObject should be a static method, not prototype one.' );
    assert && assert( !subtype.prototype.applyState, 'applyState should be a static method, not prototype one.' );
    assert && assert( !subtype.prototype.stateToArgsForConstructor, 'stateToArgsForConstructor should be a static method, not prototype one.' );

    // Prevent inheritance of methods, see https://github.com/phetsims/phet-io/issues/1623
    if ( !subtype.hasOwnProperty( 'methods' ) ) {
      subtype.methods = {};
    }

    // assert that each public method adheres to the expected schema
    for ( const method in subtype.methods ) {
      const methodObject = subtype.methods[ method ];
      if ( typeof methodObject === 'object' ) {
        assert && assert( ObjectIO.isIOType( methodObject.returnType ), 'return type must be of type IO: ' + methodObject.returnType );

        assert && assert( Array.isArray( methodObject.parameterTypes ),
          'parameter types must be an array: ' + methodObject.parameterTypes );

        methodObject.parameterTypes.forEach( parameterType => {
          assert && assert( ObjectIO.isIOType( parameterType ), 'parameter type must be of type IO: ' + parameterType );
        } );

        assert && assert( typeof methodObject.implementation === 'function',
          'implementation must be of type function: ' + methodObject.implementation );

        assert && assert( typeof methodObject.documentation === 'string',
          'documentation must be of type string: ' + methodObject.documentation );

        assert && methodObject.invocableForReadOnlyElements && assert( typeof methodObject.invocableForReadOnlyElements === 'boolean',
          'invocableForReadOnlyElements must be of type boolean: ' + methodObject.invocableForReadOnlyElements );
      }
    }

    assert && assert( subtype.validator, 'validator must be provided' );
    assert && assert( subtype.documentation, 'documentation must be provided' );
    assert && ValidatorDef.validateValidator( subtype.validator );

    subtype.hasOwnProperty( 'methodOrder' ) && subtype.methodOrder.forEach( function( methodName ) {
      assert && assert( subtype.methods[ methodName ], 'methodName not in public methods: ' + methodName );
    } );

    if ( subtype.hasOwnProperty( 'api' ) ) {
      assert && assert( subtype.api instanceof Object, 'Object expected for api' );
      assert && assert( Object.getPrototypeOf( subtype.api ) === Object.prototype, 'no extra prototype allowed on API object' );
    }

    // TODO make this check recursive, see https://github.com/phetsims/phet-io/issues/1371
    // const supertype = ObjectIO.getSupertype( subtype );
    // const superEvents = [];
    // const getEvents = type => {
    //   if ( type.events ) {
    //     type.events.forEach( e => superEvents.push( e ) );
    //   }
    //   ObjectIO.getSupertype( type ) && getEvents( ObjectIO.getSupertype( type ) );
    // };
    // getEvents( supertype );
    //
    // assert && subtype.events && subtype.events.forEach( function( event ) {
    //   assert( superEvents.indexOf( event ) < 0, 'subtype should not declare event that parent also has.' );
    // } );
  }
}

/**
 * Checks if type is an IO Type
 * @param {*} type
 * @public
 * @returns {boolean} - true if inherits from ObjectIO or is ObjectIO
 */
ObjectIO.isIOType = type => type === ObjectIO || type.prototype instanceof ObjectIO;

/**
 * @typeDef {Object} MethodObject
 * @property {string} documentation
 * @property {function()} implementation - the function to execute when this method is called
 * @property {function(new:ObjectIO)} returnType - the return IO Type of the method
 * @property {Array.<function(new:ObjectIO)>} parameterTypes - the parameter IO Types for the method
 * @property {boolean} [invocableForReadOnlyElements=true] - by default, all methods are invocable for all elements.
 *    However, for some read-only elements, certain methods should not be invocable. In that case, they are marked as
 *    invocableForReadOnlyElements: false.
 */

/**
 * The public methods available for this IO Type. Each method is not just a function, but a collection of metadata
 * about the method to be able to serialize parameters and return types and provide better documentation.
 * @type {Object.<string, MethodObject>}
 */
ObjectIO.methods = {};

/**
 * Documentation that appears in PhET-iO Studio, supports HTML markup.
 * @public
 */
ObjectIO.documentation = 'The root of the wrapper object hierarchy.';

/**
 * The name that this TypeIO will have in the public PhET-iO API. In general, this should only be word characters,
 * ending in "IO". Parameteric types are a special subset of TypeIOs that include their parameters in their typeName.
 * If a TypeIO's parameters are other IO Type(s), then they should be included within angle brackets, like
 * "PropertyIO<Boolean>". Some other types use a more custom format for displaying their parameter types, in this case
 * the parameter section of the type name (immediately following "IO") should begin with an open paren, "(". Thus the
 * schema for a typeName could be defined (using regex) as `[A-Z]\w*IO([(<].*){0,1}`. In most cases, parameterized
 * types should also include a `parameterTypes` field on the TypeIO.
 * @type {string}
 */
ObjectIO.typeName = 'ObjectIO';

/**
 * A validator object to be used to validate the core types that IOTypes wrap.
 * @type {ValidatorDef}
 * @public
 * @override
 */
ObjectIO.validator = {
  isValidValue: value => {

    // It could be a number, string, Object, etc.
    return value !== undefined;
  }
};

ObjectIO.validateSubtype( ObjectIO );

/**
 * Function that creates an IO Type associated with a core type. Methods are forwarded to the core type.
 * @param {function} CoreType, e.g., Bunny
 * @param {string} typeName, e.g., BunnyIO
 * @param {Object} [options]
 * @returns {IOType}
 */
ObjectIO.createIOType = ( CoreType, typeName, options ) => {
  assert && assert( typeName.endsWith( 'IO' ) || typeName.includes( 'IO<' ), 'IO Type name must end with IO' );
  options = merge( {

    // The parent IO Type, which will have standard 'class extends' inheritance, and inherit methods, events, etc.
    // and be shown as a parent type in Studio + API docs
    parentIOType: ObjectIO,

    // {string} e.g., "Animal that has a genotype (genetic blueprint) and a phenotype (appearance)."
    documentation: `IO Type for ${typeName.substring( 0, typeName.length - 2 )}`,

    // {Object} - key/value pairs with methods, see PhetioEngineIO for an example
    methods: {},

    events: [],
    parameterTypes: []
  }, options );

  class IOType extends options.parentIOType {

    /**
     * @param {PhetioObject} phetioObject
     * @returns {Object}
     * @public
     * @override
     */
    static toStateObject( phetioObject ) {
      validate( phetioObject, this.validator );
      return phetioObject.toStateObject();
    }

    // @public
    static fromStateObject( stateObject ) {
      return CoreType.fromStateObject( stateObject );
    }

    /**
     * @param state
     * @returns {Object[]}
     * @public
     * @override
     */
    static stateToArgsForConstructor( state ) {
      return CoreType.stateToArgsForConstructor( state );
    }

    /**
     * Restores CoreType state after instantiation.
     * @param {PhetioObject} phetioObject
     * @param {Object} state
     * @public
     * @override
     */
    static applyState( phetioObject, state ) {
      validate( phetioObject, this.validator );
      phetioObject.applyState( state );
    }
  }

  IOType.documentation = options.documentation;
  IOType.validator = { valueType: CoreType };
  IOType.typeName = typeName;
  IOType.events = options.events;
  IOType.parameterTypes = options.parameterTypes;
  IOType.methods = options.methods;
  ObjectIO.validateSubtype( IOType );

  return IOType;
};

tandemNamespace.register( 'ObjectIO', ObjectIO );
export default ObjectIO;<|endoftext|>


<commit_before>//initialize Minecraft code

var MCPI = Object.create(null);

MCPI.TO_RADIANS = Math.PI / 180;
MCPI.block = "1";
MCPI.penDown = true;
MCPI.nib = [[0,0,0]];

MCPI.mmMultiply = function(a,b) {
    var c = [[0,0,0],[0,0,0],[0,0,0]];
    for (var i = 0; i < 3 ; i++) for (var j = 0; j < 3 ; j++)
      c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j];
    return c;
};

MCPI.yawMatrix = function(angleDegrees) {
    var theta = angleDegrees * MCPI.TO_RADIANS;
    return [[Math.cos(theta), 0., -Math.sin(theta)],
            [0.,         1., 0.],
            [Math.sin(theta), 0., Math.cos(theta)]];
};

MCPI.rollMatrix = function(angleDegrees) {
    var theta = angleDegrees * MCPI.TO_RADIANS;
    return [[Math.cos(theta), -Math.sin(theta), 0.],
            [Math.sin(theta), Math.cos(theta),0.],
            [0.,          0.,          1.]];
};

MCPI.pitchMatrix = function(angleDegrees) {
    var theta = angleDegrees * MCPI.TO_RADIANS;
    return [[1.,          0.,          0.],
            [0., Math.cos(theta),Math.sin(theta)],
            [0., -Math.sin(theta),Math.cos(theta)]];
};

MCPI.getLine = function(x1,y1,z1,x2,y2,z2) {
    var line = [];
    x1 = Math.round(x1);
    y1 = Math.round(y1);
    z1 = Math.round(z1);
    x2 = Math.round(x2);
    y2 = Math.round(y2);
    z2 = Math.round(z2);
    var point = [x1,y1,z1];
    var dx = x2 - x1;
    var dy = y2 - y1;
    var dz = z2 - z1;
    var x_inc = dx < 0 ? -1 : 1;
    var l = Math.abs(dx);
    var y_inc = dy < 0 ? -1 : 1;
    var m = Math.abs(dy);
    var z_inc = dz < 0 ? -1 : 1;
    var n = Math.abs(dz);
    var dx2 = l * 2;
    var dy2 = m * 2;
    var dz2 = n * 2;

    if (l >= m && l >= n) {
        var err_1 = dy2 - l;
        var err_2 = dz2 - l;
        for (var i=0; i<l; i++) {
            line.push([point[0],point[1],point[2]]);
            if (err_1 > 0) {
                point[1] += y_inc;
                err_1 -= dx2;
            }
            if (err_2 > 0) {
                point[2] += z_inc;
                err_2 -= dx2;
            }
            err_1 += dy2;
            err_2 += dz2;
            point[0] += x_inc;
        }
    }
    else if (m >= l && m >= n) {
        err_1 = dx2 - m;
        err_2 = dz2 - m;
        for (var i=0; i<m; i++) {
            line.push([point[0],point[1],point[2]]);
            if (err_1 > 0) {
                point[0] += x_inc;
                err_1 -= dy2;
            }
            if (err_2 > 0) {
                point[2] += z_inc;
                err_2 -= dy2;
            }
            err_1 += dx2;
            err_2 += dz2;
            point[1] += y_inc;
        }
    }
    else {
        err_1 = dy2 - n;
        err_2 = dx2 - n;
        for (var i=0; i < n; i++) {
            line.push([point[0],point[1],point[2]]);
            if (err_1 > 0) {
                point[1] += y_inc;
                err_1 -= dz2;
            }
            if (err_2 > 0) {
                point[0] += x_inc;
                err_2 -= dz2;
            }
            err_1 += dy2;
            err_2 += dx2;
            point[2] += z_inc;
        }
    }
    line.push([point[0],point[1],point[2]]);
    if (point[0] != x2 || point[1] != y2 || point[2] != z2) {
        line.push([x2,y2,z2]);
    }
    return line;
};

MCPI.postToChat = function(message) {
  MCPI.socket.send("chat.post("+message+")");
};

MCPI.setBlock = function(x,y,z,block) {
  if (block != "0" && Math.floor(x) == Math.floor(MCPI.playerX) && Math.floor(z) == Math.floor(MCPI.playerZ)
      && (Math.floor(y) >= MCPI.playerShiftedHeight) ) {
        MCPI.playerShiftedHeight = Math.floor(y) + 1;
        MCPI.socket.send("player.setPos("+MCPI.playerX+","+MCPI.playerShiftedHeight+","+MCPI.playerZ+")");
  }
  MCPI.socket.send("world.setBlock("+x+","+y+","+z+","+block+")");
};

MCPI.drawPoint = function(x0,y0,z0) {
    var l = MCPI.nib.length;
    if (l == 0) {
        return;
    }
    else if (l == 1) {
        MCPI.setBlock(x0,y0,z0,MCPI.block);
        return;
    }

    for (var i = 0 ; i < l ; i++) {
        var p = MCPI.nib[i];
        var x = p[0] + x0;
        var y = p[1] + y0;
        var z = p[2] + z0;
        var indexable = ""+x+","+y+","+z;
        if (! (indexable in MCPI.saved)) {
            MCPI.setBlock(x,y,z,MCPI.block);
            MCPI.saved[indexable] = 1;
        }
    }
}

MCPI.drawLine = function(x1,y1,z1,x2,y2,z2) {
    MCPI.saved = Object.create(null);
    var l = MCPI.getLine(x1,y1,z1,x2,y2,z2);
    for (var i=0; i<l.length ; i++) {
        MCPI.drawPoint(l[i][0],l[i][1],l[i][2]);
    }
}

MCPI.turtleSetWidth = function(w) {
    MCPI.nib = [];
    if (w == 0) {
        return;
    }
    else if (w == 1) {
        MCPI.nib = [[0,0,0]];
    }
    else if (w == 2) {
        for (x=-1; x<=0; x++)
          for (y=0; y<=1; y++)
            for (z=-1; z<=0; z++)
              MCPI.nib.push([x,y,z]);
    }
    else {
      var r = w/2;
      var r2 = r*r;
      for (var x = -Math.ceil(r) ; x <= Math.ceil(r); x++)
        for (var y = -Math.ceil(r) ; y <= Math.ceil(r); y++)
          for (var z = -Math.ceil(r) ; z <= Math.ceil(r); z++)
            if (x*x + y*y + z*z <= r2)
               MCPI.nib.push([x,y,z]);
    }
}

MCPI.turtleYaw = function(angleDegrees) {
    MCPI.matrix = MCPI.mmMultiply(MCPI.matrix, MCPI.yawMatrix(angleDegrees));
};

MCPI.turtlePitch = function(angleDegrees) {
    MCPI.matrix = MCPI.mmMultiply(MCPI.matrix, MCPI.pitchMatrix(angleDegrees));
};

MCPI.turtleRoll = function(angleDegrees) {
    MCPI.matrix = MCPI.mmMultiply(MCPI.matrix, MCPI.rollMatrix(angleDegrees));
};

MCPI.turtleGo = function(distance) {
    var newX = MCPI.curX + MCPI.matrix[0][2] * distance;
    var newY = MCPI.curY + MCPI.matrix[1][2] * distance;
    var newZ = MCPI.curZ + MCPI.matrix[2][2] * distance;
    if (MCPI.penDown)
        MCPI.drawLine(MCPI.curX,MCPI.curY,MCPI.curZ,newX,newY,newZ);
    MCPI.curX = newX;
    MCPI.curY = newY;
    MCPI.curZ = newZ;
};

MCPI.socket = new WebSocket("ws://" + (location.search.length ? location.search.substr(1) : "127.0.0.1") + ":14711");

MCPI.timeoutFunction = function() {
   MCPI.socket.close();
   window.alert('Cannot connect to Minecraft API. Make sure you have Minecraft running with Raspberry Jam Mod.');
   exit();
};

MCPI.timerID = setTimeout(MCPI.timeoutFunction, 5000);

MCPI.socket.onopen = function(event) {
  MCPI.socket.onmessage = function(event) {
    var args = event.data.trim().split(",");
    MCPI.playerX = Math.floor(parseFloat(args[0]));
    MCPI.playerY = Math.floor(parseFloat(args[1]));
    MCPI.playerZ = Math.floor(parseFloat(args[2]));
    MCPI.curX = MCPI.playerX;
    MCPI.curY = MCPI.playerY;
    MCPI.curZ = MCPI.playerZ;
    MCPI.playerShiftedHeight = MCPI.playerY;

    MCPI.socket.onmessage = function(event) {
      var yaw = parseFloat(event.data.trim());
      MCPI.matrix = MCPI.yawMatrix(yaw);
      clearTimeout(MCPI.timerID);

<commit_msg>monkey-patch a Websocket.send to output some debug info<commit_after>//initialize Minecraft code

var MCPI = Object.create(null);

MCPI.TO_RADIANS = Math.PI / 180;
MCPI.block = "1";
MCPI.penDown = true;
MCPI.nib = [[0,0,0]];

MCPI.mmMultiply = function(a,b) {
    var c = [[0,0,0],[0,0,0],[0,0,0]];
    for (var i = 0; i < 3 ; i++) for (var j = 0; j < 3 ; j++)
      c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] + a[i][2]*b[2][j];
    return c;
};

MCPI.yawMatrix = function(angleDegrees) {
    var theta = angleDegrees * MCPI.TO_RADIANS;
    return [[Math.cos(theta), 0., -Math.sin(theta)],
            [0.,         1., 0.],
            [Math.sin(theta), 0., Math.cos(theta)]];
};

MCPI.rollMatrix = function(angleDegrees) {
    var theta = angleDegrees * MCPI.TO_RADIANS;
    return [[Math.cos(theta), -Math.sin(theta), 0.],
            [Math.sin(theta), Math.cos(theta),0.],
            [0.,          0.,          1.]];
};

MCPI.pitchMatrix = function(angleDegrees) {
    var theta = angleDegrees * MCPI.TO_RADIANS;
    return [[1.,          0.,          0.],
            [0., Math.cos(theta),Math.sin(theta)],
            [0., -Math.sin(theta),Math.cos(theta)]];
};

MCPI.getLine = function(x1,y1,z1,x2,y2,z2) {
    var line = [];
    x1 = Math.round(x1);
    y1 = Math.round(y1);
    z1 = Math.round(z1);
    x2 = Math.round(x2);
    y2 = Math.round(y2);
    z2 = Math.round(z2);
    var point = [x1,y1,z1];
    var dx = x2 - x1;
    var dy = y2 - y1;
    var dz = z2 - z1;
    var x_inc = dx < 0 ? -1 : 1;
    var l = Math.abs(dx);
    var y_inc = dy < 0 ? -1 : 1;
    var m = Math.abs(dy);
    var z_inc = dz < 0 ? -1 : 1;
    var n = Math.abs(dz);
    var dx2 = l * 2;
    var dy2 = m * 2;
    var dz2 = n * 2;

    if (l >= m && l >= n) {
        var err_1 = dy2 - l;
        var err_2 = dz2 - l;
        for (var i=0; i<l; i++) {
            line.push([point[0],point[1],point[2]]);
            if (err_1 > 0) {
                point[1] += y_inc;
                err_1 -= dx2;
            }
            if (err_2 > 0) {
                point[2] += z_inc;
                err_2 -= dx2;
            }
            err_1 += dy2;
            err_2 += dz2;
            point[0] += x_inc;
        }
    }
    else if (m >= l && m >= n) {
        err_1 = dx2 - m;
        err_2 = dz2 - m;
        for (var i=0; i<m; i++) {
            line.push([point[0],point[1],point[2]]);
            if (err_1 > 0) {
                point[0] += x_inc;
                err_1 -= dy2;
            }
            if (err_2 > 0) {
                point[2] += z_inc;
                err_2 -= dy2;
            }
            err_1 += dx2;
            err_2 += dz2;
            point[1] += y_inc;
        }
    }
    else {
        err_1 = dy2 - n;
        err_2 = dx2 - n;
        for (var i=0; i < n; i++) {
            line.push([point[0],point[1],point[2]]);
            if (err_1 > 0) {
                point[1] += y_inc;
                err_1 -= dz2;
            }
            if (err_2 > 0) {
                point[0] += x_inc;
                err_2 -= dz2;
            }
            err_1 += dy2;
            err_2 += dx2;
            point[2] += z_inc;
        }
    }
    line.push([point[0],point[1],point[2]]);
    if (point[0] != x2 || point[1] != y2 || point[2] != z2) {
        line.push([x2,y2,z2]);
    }
    return line;
};

MCPI.postToChat = function(message) {
  MCPI.socket.send("chat.post("+message+")");
};

MCPI.setBlock = function(x,y,z,block) {
  if (block != "0" && Math.floor(x) == Math.floor(MCPI.playerX) && Math.floor(z) == Math.floor(MCPI.playerZ)
      && (Math.floor(y) >= MCPI.playerShiftedHeight) ) {
        MCPI.playerShiftedHeight = Math.floor(y) + 1;
        MCPI.socket.send("player.setPos("+MCPI.playerX+","+MCPI.playerShiftedHeight+","+MCPI.playerZ+")");
  }
  MCPI.socket.send("world.setBlock("+x+","+y+","+z+","+block+")");
};

MCPI.drawPoint = function(x0,y0,z0) {
    var l = MCPI.nib.length;
    if (l == 0) {
        return;
    }
    else if (l == 1) {
        MCPI.setBlock(x0,y0,z0,MCPI.block);
        return;
    }

    for (var i = 0 ; i < l ; i++) {
        var p = MCPI.nib[i];
        var x = p[0] + x0;
        var y = p[1] + y0;
        var z = p[2] + z0;
        var indexable = ""+x+","+y+","+z;
        if (! (indexable in MCPI.saved)) {
            MCPI.setBlock(x,y,z,MCPI.block);
            MCPI.saved[indexable] = 1;
        }
    }
}

MCPI.drawLine = function(x1,y1,z1,x2,y2,z2) {
    MCPI.saved = Object.create(null);
    var l = MCPI.getLine(x1,y1,z1,x2,y2,z2);
    for (var i=0; i<l.length ; i++) {
        MCPI.drawPoint(l[i][0],l[i][1],l[i][2]);
    }
}

MCPI.turtleSetWidth = function(w) {
    MCPI.nib = [];
    if (w == 0) {
        return;
    }
    else if (w == 1) {
        MCPI.nib = [[0,0,0]];
    }
    else if (w == 2) {
        for (x=-1; x<=0; x++)
          for (y=0; y<=1; y++)
            for (z=-1; z<=0; z++)
              MCPI.nib.push([x,y,z]);
    }
    else {
      var r = w/2;
      var r2 = r*r;
      for (var x = -Math.ceil(r) ; x <= Math.ceil(r); x++)
        for (var y = -Math.ceil(r) ; y <= Math.ceil(r); y++)
          for (var z = -Math.ceil(r) ; z <= Math.ceil(r); z++)
            if (x*x + y*y + z*z <= r2)
               MCPI.nib.push([x,y,z]);
    }
}

MCPI.turtleYaw = function(angleDegrees) {
    MCPI.matrix = MCPI.mmMultiply(MCPI.matrix, MCPI.yawMatrix(angleDegrees));
};

MCPI.turtlePitch = function(angleDegrees) {
    MCPI.matrix = MCPI.mmMultiply(MCPI.matrix, MCPI.pitchMatrix(angleDegrees));
};

MCPI.turtleRoll = function(angleDegrees) {
    MCPI.matrix = MCPI.mmMultiply(MCPI.matrix, MCPI.rollMatrix(angleDegrees));
};

MCPI.turtleGo = function(distance) {
    var newX = MCPI.curX + MCPI.matrix[0][2] * distance;
    var newY = MCPI.curY + MCPI.matrix[1][2] * distance;
    var newZ = MCPI.curZ + MCPI.matrix[2][2] * distance;
    if (MCPI.penDown)
        MCPI.drawLine(MCPI.curX,MCPI.curY,MCPI.curZ,newX,newY,newZ);
    MCPI.curX = newX;
    MCPI.curY = newY;
    MCPI.curZ = newZ;
};

MCPI.socket = new WebSocket("ws://" + (location.search.length ? location.search.substr(1) : "127.0.0.1") + ":14711");
var originalSocketSend = MCPI.socket.send
MCPI.socket.send = function(data) {
    console.log('SEND: '+ data);
    return originalSocketSend.apply(this, arguments);
};

MCPI.timeoutFunction = function() {
   MCPI.socket.close();
   window.alert('Cannot connect to Minecraft API. Make sure you have Minecraft running with Raspberry Jam Mod.');
   exit();
};

MCPI.timerID = setTimeout(MCPI.timeoutFunction, 5000);

MCPI.socket.onopen = function(event) {
  MCPI.socket.onmessage = function(event) {
    var args = event.data.trim().split(",");
    MCPI.playerX = Math.floor(parseFloat(args[0]));
    MCPI.playerY = Math.floor(parseFloat(args[1]));
    MCPI.playerZ = Math.floor(parseFloat(args[2]));
    MCPI.curX = MCPI.playerX;
    MCPI.curY = MCPI.playerY;
    MCPI.curZ = MCPI.playerZ;
    MCPI.playerShiftedHeight = MCPI.playerY;

    MCPI.socket.onmessage = function(event) {
      var yaw = parseFloat(event.data.trim());
      MCPI.matrix = MCPI.yawMatrix(yaw);
      clearTimeout(MCPI.timerID);

<|endoftext|>


<commit_before>$("#github").on("keyup", function() {
  var user = $("#github").val();

  emptyCurrentList();

  // Using the github API https://developer.github.com/v3/repos/#list-user-repositories
  $.ajax({
    url: "https://api.github.com/users/" + user + "/repos",
    success: function(repos) {
      iterateThroughData(repos);
    },
    error: function(data) {
      insertError();
    }
  });
});

function iterateThroughData(repos) {
  _.forEachRight(repos, function(repo) {
    insertHTML(repo);
  });
}

function insertHTML(repo) {
  $("#repositories").append("
" + repo.title + "
");
}

function emptyCurrentList() {
  $("#repositories").empty();
}

function insertError() {
  $("#repositories").append("
Uh oh, we're having a problem!
");
}
<commit_msg>small fix<commit_after>$("#github").on("keyup", function() {
  var user = $("#github");

  emptyCurrentList();

  // Using the github API https://developer.github.com/v3/repos/#list-user-repositories
  $.ajax({
    url: "https://api.github.com/users/" + user + "/repos",
    success: function(repos) {
      iterateThroughData(repos);
    },
    error: function(data) {
      insertError();
    }
  });
});

function iterateThroughData(repos) {
  _.forEachRight(repos, function(repo) {
    insertHTML(repo);
  });
}

function insertHTML(repo) {
  $("#repositories").append("
" + repo.title + "
");
}

function emptyCurrentList() {
  $("#repositories").empty();
}

function insertError() {
  $("#repositories").append("
Uh oh, we're having a problem!
");
}
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var DEAD = 0;
var LIVE = 1;

var sampleInput = "4 8\n........\n....*...\n...**...\n........\n";

var createGrid = function(s) {
  var lines = s.split('\n');
  var rc = lines[0].split(' ');
  var rows = rc[0] | 0; // 4
  var cols = rc[1] | 0; // 8

  var game = { rows: rows, cols: cols };

  for (var i=0;i<rows;++i) {
    for (var j=0;j<cols;++j) {
      var v = lines[1+i][j];
      if (game[j] === undefined) { game[j] = []; }
      game[j][i] = v === '*' ? LIVE : DEAD;
    }
  }

  return game;
};

var cellState = function(state,neighbours) {
  if (neighbours < 2 && state === LIVE) {
    return DEAD;
  }
};

describe('game of life', function() {
  describe('cell', function() {
    it('dies with underpopulation', function() {
      assert.equal(DEAD, cellState(LIVE,1));
    });
  });

  describe('grid', function() {
    it('should read input', function() {
      var grid = createGrid(sampleInput);

      assert.equal(4, grid.rows);

      assert.equal(DEAD, grid[0][0]);
      assert.equal(LIVE, grid[4][1]);
      assert.equal(LIVE, grid[3][2]);
      assert.equal(LIVE, grid[4][2]);
    });
  });
});

<commit_msg>failing test case for overpopulation<commit_after>"use strict";

var assert = require('assert');

var DEAD = 0;
var LIVE = 1;

var sampleInput = "4 8\n........\n....*...\n...**...\n........\n";

var createGrid = function(s) {
  var lines = s.split('\n');
  var rc = lines[0].split(' ');
  var rows = rc[0] | 0; // 4
  var cols = rc[1] | 0; // 8

  var game = { rows: rows, cols: cols };

  for (var i=0;i<rows;++i) {
    for (var j=0;j<cols;++j) {
      var v = lines[1+i][j];
      if (game[j] === undefined) { game[j] = []; }
      game[j][i] = v === '*' ? LIVE : DEAD;
    }
  }

  return game;
};

var cellState = function(state,neighbours) {
  if (neighbours < 2 && state === LIVE) {
    return DEAD;
  }
};

describe('game of life', function() {
  describe('cell', function() {
    it('dies with underpopulation', function() {
      assert.equal(DEAD, cellState(LIVE,1));
    });

    it('dies with overcrowding', function() {
      assert.equal(DEAD, cellState(LIVE,4));
    });
  });

  describe('grid', function() {
    it('should read input', function() {
      var grid = createGrid(sampleInput);

      assert.equal(4, grid.rows);

      assert.equal(DEAD, grid[0][0]);
      assert.equal(LIVE, grid[4][1]);
      assert.equal(LIVE, grid[3][2]);
      assert.equal(LIVE, grid[4][2]);
    });
  });
});

<|endoftext|>


<commit_before>var restify = require( 'restify' );
var bindIp = '0.0.0.0', bindPort = '8080';
var quotes = require('./model.js').model;
var crypto = require('crypto');
var ua = require('universal-analytics');
var visitor = ua('UA-2158627-15');
var fs = require('fs');
var rita = require('rita');
var testNLP = false;
var rms;

var { graphql, buildSchema } = require( 'graphql' );
var gqlSchema = buildSchema( fs.readFileSync( './htdocs/schema.gql', 'utf8' ) );
var gqlImpl = {
    random: function () {

        var index = Math.floor(Math.random() * (quotes.length)) ;

        visitor.pageview("/graphql/quote/" + index).send();
        visitor.event("Return Quote", "Random Quote").send()

        var quote = flattenQuote( getQuote(index) );

        return quote;
    },
    video: function () {
        var theObject = {
            "title": null,
            "link": null
        };

        var index;
        var theQuote;

        while(theObject.title === null) {
            index = Math.floor(Math.random() * (quotes.length)) ;
            theQuote = getQuote(index);

            if (theQuote.quote.videos && theQuote.quote.videos.length > 0) {
                var video = theQuote.quote.videos[Math.floor(Math.random() * (theQuote.quote.videos.length))];
                theObject.title = video.title;
                theObject.link = video.link;
            }
        }

        visitor.pageview("/json/random/video/" + index).send();
        visitor.event("Return Video", "Random Video").send()

        return theObject;
    },
    daily: function () {
        var dat = new Date();
        var index = parseInt(dat.getFullYear() + dat.getMonth() + dat.getDay()) % quotes.length;

        visitor.pageview("/graphql/quote/" + index).send();
        visitor.event("Return Quote", "Daily Quote").send()

        var quote = flattenQuote( getQuote(index) );

        return quote;

    },
    quote: function ( params ) {
        var quote = {};

        if ( typeof( quotes[ parseInt( params.id ) ] ) !== 'undefined' ) {
            visitor.pageview("/graphql/quote/" + params.id).send();
            visitor.event("Return Quote", "Fixed Quote").send()

            quote = flattenQuote( getQuote( parseInt( params.id ) ) );
        } else {
            throw new Error( "Quote doesn't exist" );
        }

        return quote;
    }
};

var server = restify.createServer( {

    name: 'howmuchisthe.fish',
    handleUpgrades: true,
    version: '1.2.0'

} );

server.use( restify.plugins.queryParser() );
server.use( restify.plugins.bodyParser() );

server.get( {path: '/json'}, noRequest );
server.get( {path: '/json/random'}, randomQuote );
server.get( {path: '/json/random/generate'}, generateQuote );
server.get( {path: '/json/random/video'}, randomVideo );
server.get( {path: '/json/daily'}, dailyQuote );
server.get( {path: '/json/perma/:quoteId'}, fixedQuote );

server.get( {path: '/graphql'}, noGql );
server.post( {path: '/graphql'}, parseGql );

server.get( {path: '/*'}, restify.plugins.serveStatic({
    directory: './htdocs',
    default: '/index.html'
}));

server.pre(restify.pre.userAgentConnection());

server.listen( bindPort, bindIp, function() {

    console.log( '%s listening at %s ', server.name , server.url );
    console.log( '#Quotes %s', quotes.length );
    console.log( 'Generating data for generation callback' );

    annotateModel();
    setupNLP();

    console.log( 'Ready.' );

});

function randomQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var index = Math.floor(Math.random() * (quotes.length)) ;

    visitor.pageview("/json/quote/" + index).send();
    visitor.event("Return Quote", "Random Quote").send()

    res.json( 200, getQuote(index) );
    return next();

}

function randomVideo( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var theObject = {
        "quote": {
            "videos": []
        }
    };

    var index;
    var theQuote;

    while(theObject.quote.videos.length < 1) {
        index = Math.floor(Math.random() * (quotes.length)) ;
        theQuote = getQuote(index);

        if (theQuote.quote.videos && theQuote.quote.videos.length > 0) {
            theObject.quote.videos.push(theQuote.quote.videos[Math.floor(Math.random() * (theQuote.quote.videos.length))]);
        }
    }

    visitor.pageview("/json/random/video/" + index).send();
    visitor.event("Return Video", "Random Video").send()

    res.json( 200, theObject );
    return next();

}

function dailyQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var dat = new Date();
    var index = parseInt(dat.getFullYear() + dat.getMonth() + dat.getDay()) % quotes.length;

    visitor.pageview("/json/quote/" + index).send();
    visitor.event("Return Quote", "Daily Quote").send()

    res.json( 200, getQuote(index) );
    return next();

}

function fixedQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var index = req.params.quoteId;
    if ( typeof( quotes[index] ) == 'undefined' ) {

        var error = { 'error': 'non existent' };

        visitor.event("Quote not found", "Non Existent Quote").send()

        res.send( 404, error );
        return next();

    }

    visitor.pageview("/json/quote/" + index).send();
    visitor.event("Return Quote", "Fixed Quote").send()

    res.json( 200, getQuote(index) );
    return next();

}

function generateQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    visitor.pageview("/json/quote/generated").send();
    visitor.event("Return Quote", "Generate Quote").send()

    res.json( 200, buildQuote() );
    return next();

}

function getQuote( index ) {

    var output = {
        'id': index,
        'permalink': 'http://howmuchisthe.fish/json/perma/' + index,
        'hash': getBasename(index),
        'quote': quotes[index]
    };

    return output;
}

function flattenQuote( obj ) {
    var output = obj.quote;
    output.id = obj.id;
    output.permalink = obj.permalink;
    output.hash = obj.hash;

    return output;
}

function noRequest( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var error = { 'error': 'no method' };

    res.send( 404, error );
    return next();

}

function noGql ( req, res, next ) {
    res.setHeader('Access-Control-Allow-Origin','*');

    var error = { 'error': 'Please POST GraphQL queries' };

    res.send( 400, error );
    return next();
}

function parseGql( req, res, next ) {
    res.setHeader('Access-Control-Allow-Origin','*');

    if ( req.body ) {
        var response = {};
        graphql(
            gqlSchema,
            req.body.query,
            gqlImpl
        ).then( result => {
            res.send( 200, result );

        });

    } else {
        res.send( 400, { "error": "Bad request" } );
    }

    return next();
}

function annotateModel() {
    for ( var i = 0 ; i < quotes.length; i++ ) {
        var basename = getBasename(i);
        if (fs.existsSync("htdocs/data/" + basename + ".json")) {
            var data = JSON.parse(fs.readFileSync("htdocs/data/" + basename + ".json", "utf8"));
            for (var attrname in data) { quotes[i][attrname] = data[attrname]; }
        }
    }
}

function setupNLP() {
    rms = {
        'text': new rita.RiMarkov(3),
        'track': new rita.RiMarkov(3),
        'album': new rita.RiMarkov(3)
    };

    for ( var i = 0 ; i < quotes.length; i++ ) {
        rms.text.loadText( quotes[i]['text'] );

        var filtered = quotes[i]['text'].replace(/[^A-Za-z\-0-9\s]/g,'');

        rms.track.loadText( filtered );
        rms.album.loadText( filtered );

        filtered = quotes[i]['track'].replace(/[^A-Za-z\-0-9\s]/g,'');
        rms.track.loadText( filtered );

        filtered = quotes[i]['album'].replace(/[^A-Za-z\-0-9\s]/g,'');
        rms.album.loadText( filtered );
    }

    if (testNLP == true) {

        for ( var i = 0; i < 25; i++ ) {

            console.log( generateString( 'text' ) );

        }

    }

}

function buildQuote() {

    return  {
                'quote': {
                            'text': generateString( 'text' ),
                            'track': generateString( 'track' ),
                            'album': generateString( 'album' ),
                            'year': Math.floor(Math.random() * (new Date().getFullYear() - 1994 + 1)) + 1994
                         }
            };

}

function generateString( aKey ) {
    var minLength = 7;
    var maxLength = 12;

    if ( aKey === 'track' ) {
        minLength = 2;
        maxLength = 6;
    } else if ( aKey == 'album' ) {
        minLength = 3;
        maxLength = 7;
    }

    var length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
    var tokenList = rms[aKey].generateTokens( length ).filter(word => word.length > 1).join(' ').toLowerCase();
    tokenList = tokenList.charAt(0).toUpperCase() + tokenList.slice(1);

    return tokenList;
}

function getBasename( aQuoteIndex ) {
    return crypto.createHash('sha1').update(quotes[aQuoteIndex].year + "-" + quotes[aQuoteIndex].album + "-" + quotes[aQuoteIndex].track).digest('hex');
}
<commit_msg>Handle OPTIONS request for CORS preflight<commit_after>var restify = require( 'restify' );
var bindIp = '0.0.0.0', bindPort = '8080';
var quotes = require('./model.js').model;
var crypto = require('crypto');
var ua = require('universal-analytics');
var visitor = ua('UA-2158627-15');
var fs = require('fs');
var rita = require('rita');
var testNLP = false;
var rms;

var { graphql, buildSchema } = require( 'graphql' );
var gqlSchema = buildSchema( fs.readFileSync( './htdocs/schema.gql', 'utf8' ) );
var gqlImpl = {
    random: function () {

        var index = Math.floor(Math.random() * (quotes.length)) ;

        visitor.pageview("/graphql/quote/" + index).send();
        visitor.event("Return Quote", "Random Quote").send()

        var quote = flattenQuote( getQuote(index) );

        return quote;
    },
    video: function () {
        var theObject = {
            "title": null,
            "link": null
        };

        var index;
        var theQuote;

        while(theObject.title === null) {
            index = Math.floor(Math.random() * (quotes.length)) ;
            theQuote = getQuote(index);

            if (theQuote.quote.videos && theQuote.quote.videos.length > 0) {
                var video = theQuote.quote.videos[Math.floor(Math.random() * (theQuote.quote.videos.length))];
                theObject.title = video.title;
                theObject.link = video.link;
            }
        }

        visitor.pageview("/json/random/video/" + index).send();
        visitor.event("Return Video", "Random Video").send()

        return theObject;
    },
    daily: function () {
        var dat = new Date();
        var index = parseInt(dat.getFullYear() + dat.getMonth() + dat.getDay()) % quotes.length;

        visitor.pageview("/graphql/quote/" + index).send();
        visitor.event("Return Quote", "Daily Quote").send()

        var quote = flattenQuote( getQuote(index) );

        return quote;

    },
    quote: function ( params ) {
        var quote = {};

        if ( typeof( quotes[ parseInt( params.id ) ] ) !== 'undefined' ) {
            visitor.pageview("/graphql/quote/" + params.id).send();
            visitor.event("Return Quote", "Fixed Quote").send()

            quote = flattenQuote( getQuote( parseInt( params.id ) ) );
        } else {
            throw new Error( "Quote doesn't exist" );
        }

        return quote;
    }
};

var server = restify.createServer( {

    name: 'howmuchisthe.fish',
    handleUpgrades: true,
    version: '1.2.0'

} );

server.use( restify.plugins.queryParser() );
server.use( restify.plugins.bodyParser() );

server.get( {path: '/json'}, noRequest );
server.get( {path: '/json/random'}, randomQuote );
server.get( {path: '/json/random/generate'}, generateQuote );
server.get( {path: '/json/random/video'}, randomVideo );
server.get( {path: '/json/daily'}, dailyQuote );
server.get( {path: '/json/perma/:quoteId'}, fixedQuote );

server.get( {path: '/graphql'}, noGql );
server.post( {path: '/graphql'}, parseGql );
server.opts( {path: '/graphql'}, optsGql );

server.get( {path: '/*'}, restify.plugins.serveStatic({
    directory: './htdocs',
    default: '/index.html'
}));

server.pre(restify.pre.userAgentConnection());

server.listen( bindPort, bindIp, function() {

    console.log( '%s listening at %s ', server.name , server.url );
    console.log( '#Quotes %s', quotes.length );
    console.log( 'Generating data for generation callback' );

    annotateModel();
    setupNLP();

    console.log( 'Ready.' );

});

function randomQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var index = Math.floor(Math.random() * (quotes.length)) ;

    visitor.pageview("/json/quote/" + index).send();
    visitor.event("Return Quote", "Random Quote").send()

    res.json( 200, getQuote(index) );
    return next();

}

function randomVideo( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var theObject = {
        "quote": {
            "videos": []
        }
    };

    var index;
    var theQuote;

    while(theObject.quote.videos.length < 1) {
        index = Math.floor(Math.random() * (quotes.length)) ;
        theQuote = getQuote(index);

        if (theQuote.quote.videos && theQuote.quote.videos.length > 0) {
            theObject.quote.videos.push(theQuote.quote.videos[Math.floor(Math.random() * (theQuote.quote.videos.length))]);
        }
    }

    visitor.pageview("/json/random/video/" + index).send();
    visitor.event("Return Video", "Random Video").send()

    res.json( 200, theObject );
    return next();

}

function dailyQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var dat = new Date();
    var index = parseInt(dat.getFullYear() + dat.getMonth() + dat.getDay()) % quotes.length;

    visitor.pageview("/json/quote/" + index).send();
    visitor.event("Return Quote", "Daily Quote").send()

    res.json( 200, getQuote(index) );
    return next();

}

function fixedQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var index = req.params.quoteId;
    if ( typeof( quotes[index] ) == 'undefined' ) {

        var error = { 'error': 'non existent' };

        visitor.event("Quote not found", "Non Existent Quote").send()

        res.send( 404, error );
        return next();

    }

    visitor.pageview("/json/quote/" + index).send();
    visitor.event("Return Quote", "Fixed Quote").send()

    res.json( 200, getQuote(index) );
    return next();

}

function generateQuote( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    visitor.pageview("/json/quote/generated").send();
    visitor.event("Return Quote", "Generate Quote").send()

    res.json( 200, buildQuote() );
    return next();

}

function getQuote( index ) {

    var output = {
        'id': index,
        'permalink': 'http://howmuchisthe.fish/json/perma/' + index,
        'hash': getBasename(index),
        'quote': quotes[index]
    };

    return output;
}

function flattenQuote( obj ) {
    var output = obj.quote;
    output.id = obj.id;
    output.permalink = obj.permalink;
    output.hash = obj.hash;

    return output;
}

function noRequest( req, res, next ) {

    res.setHeader('Access-Control-Allow-Origin','*');

    var error = { 'error': 'no method' };

    res.send( 404, error );
    return next();

}

function optsGql ( req, res, next ) {
    res.setHeader('Access-Control-Allow-Origin','*');
    res.setHeader('Access-Control-Allow-Headers','Content-Type');

    var error = { 'ok': 'ok' };

    res.send( 200, error );
    return next();
}

function noGql ( req, res, next ) {
    res.setHeader('Access-Control-Allow-Origin','*');

    var error = { 'error': 'Please POST GraphQL queries' };

    res.send( 400, error );
    return next();
}

function parseGql( req, res, next ) {
    res.setHeader('Access-Control-Allow-Origin','*');

    if ( req.body ) {
        var response = {};
        graphql(
            gqlSchema,
            req.body.query,
            gqlImpl
        ).then( result => {
            res.send( 200, result );

        });

    } else {
        res.send( 400, { "error": "Bad request" } );
    }

    return next();
}

function annotateModel() {
    for ( var i = 0 ; i < quotes.length; i++ ) {
        var basename = getBasename(i);
        if (fs.existsSync("htdocs/data/" + basename + ".json")) {
            var data = JSON.parse(fs.readFileSync("htdocs/data/" + basename + ".json", "utf8"));
            for (var attrname in data) { quotes[i][attrname] = data[attrname]; }
        }
    }
}

function setupNLP() {
    rms = {
        'text': new rita.RiMarkov(3),
        'track': new rita.RiMarkov(3),
        'album': new rita.RiMarkov(3)
    };

    for ( var i = 0 ; i < quotes.length; i++ ) {
        rms.text.loadText( quotes[i]['text'] );

        var filtered = quotes[i]['text'].replace(/[^A-Za-z\-0-9\s]/g,'');

        rms.track.loadText( filtered );
        rms.album.loadText( filtered );

        filtered = quotes[i]['track'].replace(/[^A-Za-z\-0-9\s]/g,'');
        rms.track.loadText( filtered );

        filtered = quotes[i]['album'].replace(/[^A-Za-z\-0-9\s]/g,'');
        rms.album.loadText( filtered );
    }

    if (testNLP == true) {

        for ( var i = 0; i < 25; i++ ) {

            console.log( generateString( 'text' ) );

        }

    }

}

function buildQuote() {

    return  {
                'quote': {
                            'text': generateString( 'text' ),
                            'track': generateString( 'track' ),
                            'album': generateString( 'album' ),
                            'year': Math.floor(Math.random() * (new Date().getFullYear() - 1994 + 1)) + 1994
                         }
            };

}

function generateString( aKey ) {
    var minLength = 7;
    var maxLength = 12;

    if ( aKey === 'track' ) {
        minLength = 2;
        maxLength = 6;
    } else if ( aKey == 'album' ) {
        minLength = 3;
        maxLength = 7;
    }

    var length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
    var tokenList = rms[aKey].generateTokens( length ).filter(word => word.length > 1).join(' ').toLowerCase();
    tokenList = tokenList.charAt(0).toUpperCase() + tokenList.slice(1);

    return tokenList;
}

function getBasename( aQuoteIndex ) {
    return crypto.createHash('sha1').update(quotes[aQuoteIndex].year + "-" + quotes[aQuoteIndex].album + "-" + quotes[aQuoteIndex].track).digest('hex');
}
<|endoftext|>


<commit_before>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Zynga Inc.
==================================================================================================
*/

(function(global)
{
	// Jasy is replacing this call via the kernel permutation
	var assets = core.Env.getValue("assets");

	// Dynamically unpack compact structure for better runtime performance
	if (assets)
	{
		if (assets.dirs)
		{
			var root = assets.root;
			var dirs = assets.dirs;

			var unpacked = {};

			for (var dir in dirs)
			{
				var map = dirs[dir];
				var dirslash = dir + "/";

				for (var base in map)
				{
					var id = dirslash + base;

					// if not an array we store just '1' for being short and truish
					if (map[base] !== 1)
					{
						unpacked[id] = map[base];
						map[base].unshift(root + id);
					}
					else
					{
						unpacked[id] = root + id;
					}
				}
			}

			// Replace compiled in data with unpacked one
			assets = unpacked;
		}
		else
		{
			// Shorthand
			assets = assets.files;
		}
	}

	/**
	 * Contains information about images (size, format, clipping, ...) and
	 * other assets like CSS files, local data, ...
	 */
	core.Module("core.io.Asset",
	{
		/**
		 * {Boolean} Whether the registry has information about the given asset @id {String}.
		 */
		has : function(id) {
			return assets.hasOwnProperty(id);
		},

		/**
		 * Loads the given asset @ids {String[]} and optionally executes the given
		 * @callback {Function?} (in the given @context {Object?global}) after all are completed.
		 * The behavior is tweakable by enabling @nocache {Boolean?false} to append a dynamic
		 * `GET` parameter and @type {String?} to enforce a specific loader class.
		 */
		load: function(ids, callback, context, nocache, type)
		{
			var id, uri;

			var uris = [];
			var uriToId = {};

			for (var i=0, l=ids.length; i<l; i++)
			{
				id = ids[i];
				uri = this.toUri(id);

				uris.push(uri);
				uriToId[uri] = id;
			}

			var localCallback = function(uriData)
			{
				var idData = {};
				for (var uri in uriData) {
					idData[uriToId[uri]] = uriData[uri];
				}

				context ? callback.call(context, idData) : callback(idData);
			}

			return core.io.Queue.load(uris, localCallback, null, nocache, type);
		},

		/**
		 * {Map} Returns the dimensions of the given image @id {String} with the keys `width` and `height`.
		 */
		getImageSize : function(id)
		{
			var entry = assets[id];
			if (core.Env.isSet("debug") && entry == null) {
				throw new Error("Unknown image: " + id);
			}

			return {
				width: entry[1],
				height: entry[2]
			};
		},

		/**
		 * {String} Converts the given asset @id {String} to a full qualified URI.
		 * The method throws an error whenever an asset ID is unknown.
		 */
		toUri : function(id)
		{
			var entry = assets[id];
			if (core.Env.isSet("debug") && entry == null) {
				throw new Error("Unknown asset: " + id);
			}

			if (entry.push) {
				return entry[0];
			} else {
				return entry;
			}
		}
	});
})(this);<commit_msg>Minor fix for flat structures<commit_after>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Zynga Inc.
==================================================================================================
*/

(function(global)
{
	// Jasy is replacing this call via the kernel permutation
	var assets = core.Env.getValue("assets");

	// Dynamically unpack compact structure for better runtime performance
	if (assets)
	{
		if (assets.dirs)
		{
			var root = assets.root;
			var dirs = assets.dirs;

			var unpacked = {};

			for (var dir in dirs)
			{
				var map = dirs[dir];
				var dirslash = dir ? dir + "/" : "";

				for (var base in map)
				{
					var id = dirslash + base;

					// if not an array we store just '1' for being short and truish
					if (map[base] !== 1)
					{
						unpacked[id] = map[base];
						map[base].unshift(root + id);
					}
					else
					{
						unpacked[id] = root + id;
					}
				}
			}

			// Replace compiled in data with unpacked one
			assets = unpacked;
		}
		else
		{
			// Shorthand
			assets = assets.files;
		}
	}

	/**
	 * Contains information about images (size, format, clipping, ...) and
	 * other assets like CSS files, local data, ...
	 */
	core.Module("core.io.Asset",
	{
		/**
		 * {Boolean} Whether the registry has information about the given asset @id {String}.
		 */
		has : function(id) {
			return assets.hasOwnProperty(id);
		},

		/**
		 * Loads the given asset @ids {String[]} and optionally executes the given
		 * @callback {Function?} (in the given @context {Object?global}) after all are completed.
		 * The behavior is tweakable by enabling @nocache {Boolean?false} to append a dynamic
		 * `GET` parameter and @type {String?} to enforce a specific loader class.
		 */
		load: function(ids, callback, context, nocache, type)
		{
			var id, uri;

			var uris = [];
			var uriToId = {};

			for (var i=0, l=ids.length; i<l; i++)
			{
				id = ids[i];
				uri = this.toUri(id);

				uris.push(uri);
				uriToId[uri] = id;
			}

			var localCallback = function(uriData)
			{
				var idData = {};
				for (var uri in uriData) {
					idData[uriToId[uri]] = uriData[uri];
				}

				context ? callback.call(context, idData) : callback(idData);
			}

			return core.io.Queue.load(uris, localCallback, null, nocache, type);
		},

		/**
		 * {Map} Returns the dimensions of the given image @id {String} with the keys `width` and `height`.
		 */
		getImageSize : function(id)
		{
			var entry = assets[id];
			if (core.Env.isSet("debug") && entry == null) {
				throw new Error("Unknown image: " + id);
			}

			return {
				width: entry[1],
				height: entry[2]
			};
		},

		/**
		 * {String} Converts the given asset @id {String} to a full qualified URI.
		 * The method throws an error whenever an asset ID is unknown.
		 */
		toUri : function(id)
		{
			var entry = assets[id];
			if (core.Env.isSet("debug") && entry == null) {
				throw new Error("Unknown asset: " + id);
			}

			if (entry.push) {
				return entry[0];
			} else {
				return entry;
			}
		}
	});
})(this);<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var DEAD = 0;
var LIVE = 1;

var sampleInput = "4 8\n........\n....*...\n...**...\n........\n";

var createGrid = function(s) {
  var lines = s.split('\n');
  var rc = lines[0].split(' ');
  var rows = rc[0] | 0; // 4
  var cols = rc[1] | 0; // 8

  var game = { rows: rows, cols: cols };

  for (var i=0;i<rows;++i) {
    for (var j=0;j<cols;++j) {
      var v = lines[1+i][j];
      if (game[j] === undefined) { game[j] = []; }
      game[j][i] = v === '*' ? LIVE : DEAD;
    }
  }

  return game;
};

var cellState = function(state,neighbours) {
  if (neighbours < 2 && state === LIVE) {
    return DEAD;
  }
  else if (neighbours > 3 && state === LIVE) {
    return DEAD;
  }
  else if ((neighbours === 2 || neighbours === 3) && state === LIVE) {
    return LIVE;
  }
};

describe('game of life', function() {
  describe('cell', function() {
    it('dies with underpopulation', function() {
      assert.equal(DEAD, cellState(LIVE,1));
    });

    it('dies with overcrowding', function() {
      assert.equal(DEAD, cellState(LIVE,4));
    });

    it('maintains live if 2 or three neighbours', function() {
      assert.equal(LIVE, cellState(LIVE,2));
      assert.equal(LIVE, cellState(LIVE,3));
    });
  });

  describe('grid', function() {
    it('should read input', function() {
      var grid = createGrid(sampleInput);

      assert.equal(4, grid.rows);

      assert.equal(DEAD, grid[0][0]);
      assert.equal(LIVE, grid[4][1]);
      assert.equal(LIVE, grid[3][2]);
      assert.equal(LIVE, grid[4][2]);
    });
  });
});

<commit_msg>genesis<commit_after>"use strict";

var assert = require('assert');

var DEAD = 0;
var LIVE = 1;

var sampleInput = "4 8\n........\n....*...\n...**...\n........\n";

var createGrid = function(s) {
  var lines = s.split('\n');
  var rc = lines[0].split(' ');
  var rows = rc[0] | 0; // 4
  var cols = rc[1] | 0; // 8

  var game = { rows: rows, cols: cols };

  for (var i=0;i<rows;++i) {
    for (var j=0;j<cols;++j) {
      var v = lines[1+i][j];
      if (game[j] === undefined) { game[j] = []; }
      game[j][i] = v === '*' ? LIVE : DEAD;
    }
  }

  return game;
};

var cellState = function(state,neighbours) {
  if (neighbours < 2 && state === LIVE) {
    return DEAD;
  }
  else if (neighbours > 3 && state === LIVE) {
    return DEAD;
  }
  else if ((neighbours === 2 || neighbours === 3) && state === LIVE) {
    return LIVE;
  }
};

describe('game of life', function() {
  describe('cell', function() {
    it('dies with underpopulation', function() {
      assert.equal(DEAD, cellState(LIVE,1));
    });

    it('dies with overcrowding', function() {
      assert.equal(DEAD, cellState(LIVE,4));
    });

    it('maintains live if 2 or three neighbours', function() {
      assert.equal(LIVE, cellState(LIVE,2));
      assert.equal(LIVE, cellState(LIVE,3));
    });

    it('becomes alive', function() {
      assert.equal(LIVE, cellState(DEAD,3));
    });
  });

  describe('grid', function() {
    it('should read input', function() {
      var grid = createGrid(sampleInput);

      assert.equal(4, grid.rows);

      assert.equal(DEAD, grid[0][0]);
      assert.equal(LIVE, grid[4][1]);
      assert.equal(LIVE, grid[3][2]);
      assert.equal(LIVE, grid[4][2]);
    });
  });
});

<|endoftext|>


<commit_before>// Copyright © Mapotempo, 2014
//
// This file is part of Mapotempo.
//
// Mapotempo is free software. You can redistribute it and/or
// modify since you respect the terms of the GNU Affero General
// Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Mapotempo is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.  See the Licenses for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Mapotempo. If not, see:
// <http://www.gnu.org/licenses/agpl.html>
//
function order_arrays_form() {
  $('#order_array_base_date').datepicker({
    language: defaultLocale,
    autoclose: true,
    calendarWeeks: true,
    todayHighlight: true
  });
}

function order_arrays_new(params) {
  order_arrays_form();
}

function order_arrays_edit(params) {
  var order_array_id = params.order_array_id,
    block_save_select_change = false,
    table_neeed_update = true;

  order_arrays_form();

  function filter_text(exactText, normalizedValue, filter, index) {
    return !!String(normalizedValue).match(new RegExp(filter, 'i'));
  }

  var arrows = {
    "Right": [1, 0],
    "Left": [-1, 0],
    "Up": [0, -1],
    "Down": [0, 1]
  }

  function arrow_move_focus(td, e) {
    if (e.type == "keydown" && (e.key == "Right" || e.key == "Left" || e.key == "Up" || e.key == "Down")) {
      var tr = $(td).parent();
      var target = $('#order_array table tbody tr:nth-child(' + (tr.index() + arrows[e.key][1] + 1) + ') td:nth-child(' + (td.index() + arrows[e.key][0] + 1) + ')');
      target.focus();
      e.preventDefault();
      return true;
    }
  }

  var formatNoMatches = I18n.t('web.select2.empty_result');

  function replace_fake_select2(fake) {
    // On the first click on select2-look like div, initialize select2, remove the placeholder and resend the click
    fake.hide();
    var select = $('select[name$=\\[product_ids\\]\\[\\]]', fake.parent());
    select.show();
    select.select2({
      formatNoMatches: function() {
        return formatNoMatches;
      },
      width: '100%'
    });

    select.change(function(e) {
      if (block_save_select_change) {
        return;
      }

      table_neeed_update = true;
      build_total(undefined, $('#order_array table'));

      var id = select.parent().data('id');
      var product_ids = e.val;
      $.ajax({
        type: "put",
        data: {
          product_ids: product_ids
        },
        url: '/api/0.1/order_arrays/' + order_array_id + '/orders/' + id + '.json',
        beforeSend: beforeSendWaiting,
        complete: completeWaiting,
        error: ajaxError
      });
    });
    fake.remove();
  }

  function fake_select2_key_event(e) {
    e.stopPropagation();

    if (arrow_move_focus($(this), e)) {
      $('select', $(this)).select2('close');
      return;
    } else {
      var fake = $('.fake', $(this));
      if (fake) {
        replace_fake_select2(fake);
        var input = $('input', $(this));
        input.focus();
        // var ee = jQuery.Event('keydown');
        // ee.which = e.which;
        // $('input', $(this)).trigger(ee);
      }
    }
  }

  function fake_select2_click_event(e) {
    e.stopPropagation();
    replace_fake_select2($(this));
    if (e.type == "click" && e.clientX && e.clientY) {
      $(document.elementFromPoint(e.clientX, e.clientY)).click();
    }
  }

  function active_fake_select2(selector) {
    $('.fake', selector).on('click', fake_select2_click_event);
    selector.on('keydown', fake_select2_key_event);
  }

  function build_fake_select2(container, products, product_ids) {
    data_products = [];
    $.each(products, function(i, product) {
      data_products.push({
        id: product.id,
        code: product.code,
        active: product_ids.indexOf(product.id.toString()) >= 0
      });
    });

    return container.html(SMT['order_arrays/fake_select2']({
      products: data_products
    }));
  }

  function build_total(e, table) {
    var $table = $(table),
      sum_column = [],
      grand_total = {};

    $('tbody tr', $table).each(function(i, tr) {
      var $tr = $(tr),
        sum_row = {},
        total = 0;
      $('td[data-id] select', $tr).each(function(j, select) {
        var vals = $(select).val();
        if (vals) {
          if (!sum_column[j]) {
            sum_column[j] = {
              undefined: 0
            };
          }
          $.each(vals, function(i, val) {
            sum_row[val] = (sum_row[val] || 0) + 1;
            sum_column[j][val] = (sum_column[j][val] || 0) + 1;
          });
        }
      });
      $('td[data-sum-product-id]', $tr).each(function(i, td) {
        var pid = $(td).data('sum-product-id');
        td.innerHTML = sum_row[pid] || '-';
        grand_total[pid] = (grand_total[pid] || 0) + (sum_row[pid] || 0);
        total += sum_row[pid] || 0;
      });
      $('td.total-products', $tr).html(total || '-');
    });

    // Columns
    var product_length = $('tfoot tr').length - 1;
    var row_length = $('thead:not(.tablesorter-stickyHeader) tr:first .order', $table).length;
    grand_total[undefined] = 0;
    $('tfoot tr').each(function(i, tr) {
      var $tr = $(tr),
        pid = $('th[data-product_id]', $tr).data('product_id');
      $('td:nth-child(n+4)', $(tr)).each(function(j, td) {
        sum_column[j] = sum_column[j] || {};
        td.innerHTML = sum_column[j] && sum_column[j][pid] || '-';
        sum_column[j][undefined] += sum_column[j][pid] || 0;
      });
      $('td:nth-child(' + (row_length + 4 + i) + ')', $tr).html(grand_total[pid] || '-');
      grand_total[undefined] += grand_total[pid] || 0;
    });

    table_trigger_update();
  }

  function table_trigger_update() {
    if (table_neeed_update) {
      table_neeed_update = false;
      $("#order_array table").trigger("update");
    }
  }

  var products = {};

  function display_order_array(data) {
    var container = $('#order_array div');
    $.each(data.products, function(i, product) {
      products[product.id] = product;
    });
    $.each(data.rows, function(i, row) {
      $.each(row.orders, function(i, order) {
        order.products = [];
        $.each(products, function(i, product) {
          order.products.push({
            id: product.id,
            code: product.code,
            active: order.product_ids.indexOf(product.id) >= 0
          });
        });
      });
    });
    data.i18n = mustache_i18n;
    $(container).html(SMT['order_arrays/edit'](data));

    var headers = {
      0: {
        sorter: false
      }
    };
    var filter_functions = {};
    for (var i = 0; i < data.columns.length; i++) {
      headers[i + 3] = {
        sorter: false
      };
      filter_functions[i + 3] = filter_text;
    }
    var filter_formatter = {
      0: function($cell, indx) {
        return false;
      }
    };
    for (var i = 0; i < data.products.length + 1; i++) {
      filter_formatter[i + data.columns.length + 3] = function($cell, indx) {
        return false;
      };
    }
    $("#order_array table").bind("tablesorter-initialized", build_total).tablesorter({
      textExtraction: function(node, table, cellIndex) {
        if (cellIndex >= 3 && cellIndex < data.columns.length + 3) {
          return $.map($("[name$=\\[product_ids\\]\\[\\]] :selected", node), function(e, i) {
            return e.text;
          }).join(",");
        } else {
          return $(node).text();
        }
      },
      headers: headers,
      theme: "bootstrap",
      // Show order icon v and ^
      headerTemplate: '{content} {icon}',
      widgets: ["uitheme", "stickyHeaders", "filter", "columnSelector"],
      widgetOptions: {
        filter_childRows: true,
        // class name applied to filter row and each input
        filter_cssFilter: "tablesorter-filter",
        filter_functions: filter_functions,
        filter_formatter: filter_formatter
      }
    });

    $('#order_array table thead input').focusin(table_trigger_update);
    $('#order_array table thead .tablesorter-icon').click(table_trigger_update);

    active_fake_select2($('td[data-id]'));

    function change_order(product_id, add_product, remove_product, paste, copy, selector) {
      var orders = {};
      block_save_select_change = true;
      selector.each(function(i, td) {
        var val = [];
        if (add_product || remove_product) {
          val = $('select', $(this)).val() || [];
          var index = val.indexOf(product_id);
          if (add_product) {
            if (index < 0) {
              val.push(product_id);
            }
          } else if (remove_product) {
            if (index >= 0) {
              val.splice(index, 1);
            }
          }
        } else if (paste && copy) {
          val = copy[i];
        }
        build_fake_select2($(td), products, val);

        orders[$(this).data('id')] = {
          product_ids: val
        };
      });
      active_fake_select2(selector);
      block_save_select_change = false;
      table_neeed_update = true;
      build_total(undefined, $('#order_array table'));

      $.ajax({
        type: "patch",
        data: JSON.stringify({
          orders: orders
        }),
        contentType: "application/json",
        url: '/api/0.1/order_arrays/' + order_array_id + '.json',
        beforeSend: beforeSendWaiting,
        complete: completeWaiting,
        error: ajaxError
      });
    }

    var copy_row;
    $('.copy_row').click(function(e) {
      var tr = $(this).closest('tr');
      copy_row = [];
      $.each($('td[data-id] select', tr), function(i, select) {
        copy_row.push($.map($(select).val() || ['-1'], function(n) {
          return n.toString();
        }));
      });
    });

    $('.empty_row, .paste_row, .add_product_row, .remove_product_row').click(function(e) {
      if (confirm(I18n.t('order_arrays.edit.confirm_ovewrite_row'))) {
        var tr = $(this).closest('tr'),
          add_product = $(this).hasClass('add_product_row'),
          remove_product = $(this).hasClass('remove_product_row'),
          paste = $(this).hasClass('paste_row'),
          product_id = ($(this).data('product_id') || 0).toString(),
          selector = $('td[data-id]', tr);
        change_order(product_id, add_product, remove_product, paste, copy_row, selector);
      }
    });

    var copy_column;
    $('.copy_column').click(function(e) {
      var td_index = $(this).closest('th').index('th') + 1;
      copy_column = [];
      $('tbody tr td:nth-child(' + td_index + ') select', $(this).closest('table')).each(function(i, select) {
        copy_column.push($.map($(select).val() || ['-1'], function(n) {
          return n.toString();
        }));
      });
    });

    $('.empty_column, .paste_column, .add_product_column, .remove_product_column').click(function(e) {
      if (confirm(I18n.t('order_arrays.edit.confirm_ovewrite_column'))) {
        var td_index = $(this).closest('th').index('th') + 1,
          add_product = $(this).hasClass('add_product_column'),
          remove_product = $(this).hasClass('remove_product_column'),
          paste = $(this).hasClass('paste_column'),
          product_id = ($(this).data('product_id') || 0).toString();
        selector = $('tbody tr td:nth-child(' + td_index + ')', $(this).closest('table'));
        change_order(product_id, add_product, remove_product, paste, copy_column, selector);
      }
    });

    $('.planning').click(function(e) {
      var index = $(this).closest('th').index('th') - 3,
        planning_id = $(this).data('planning_id').toString();
      $.ajax({
        type: "patch",
        contentType: "application/json",
        url: '/api/0.1/plannings/' + planning_id + '/orders/' + order_array_id + '/' + index + '.json',
        beforeSend: beforeSendWaiting,
        complete: completeWaiting,
        error: ajaxError
      });
    });
  }

  $("#dialog-loading").dialog({
    autoOpen: true,
    modal: true
  });

  $.ajax({
    url: '/order_arrays/' + order_array_id + '.json',
    beforeSend: beforeSendWaiting,
    success: display_order_array,
    complete: function(data) {
      completeWaiting(data);
      $("#dialog-loading").dialog('close');
    },
    error: ajaxError
  });
}

Paloma.controller('OrderArray').prototype.new = function() {
  order_arrays_new(this.params);
};

Paloma.controller('OrderArray').prototype.create = function() {
  order_arrays_new(this.params);
};

Paloma.controller('OrderArray').prototype.edit = function() {
  order_arrays_edit(this.params);
};

Paloma.controller('OrderArray').prototype.update = function() {
  order_arrays_edit(this.params);
};
<commit_msg>Redirect to planning after apply from order_array<commit_after>// Copyright © Mapotempo, 2014
//
// This file is part of Mapotempo.
//
// Mapotempo is free software. You can redistribute it and/or
// modify since you respect the terms of the GNU Affero General
// Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Mapotempo is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.  See the Licenses for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Mapotempo. If not, see:
// <http://www.gnu.org/licenses/agpl.html>
//
function order_arrays_form() {
  $('#order_array_base_date').datepicker({
    language: defaultLocale,
    autoclose: true,
    calendarWeeks: true,
    todayHighlight: true
  });
}

function order_arrays_new(params) {
  order_arrays_form();
}

function order_arrays_edit(params) {
  var order_array_id = params.order_array_id,
    block_save_select_change = false,
    table_neeed_update = true;

  order_arrays_form();

  function filter_text(exactText, normalizedValue, filter, index) {
    return !!String(normalizedValue).match(new RegExp(filter, 'i'));
  }

  var arrows = {
    "Right": [1, 0],
    "Left": [-1, 0],
    "Up": [0, -1],
    "Down": [0, 1]
  }

  function arrow_move_focus(td, e) {
    if (e.type == "keydown" && (e.key == "Right" || e.key == "Left" || e.key == "Up" || e.key == "Down")) {
      var tr = $(td).parent();
      var target = $('#order_array table tbody tr:nth-child(' + (tr.index() + arrows[e.key][1] + 1) + ') td:nth-child(' + (td.index() + arrows[e.key][0] + 1) + ')');
      target.focus();
      e.preventDefault();
      return true;
    }
  }

  var formatNoMatches = I18n.t('web.select2.empty_result');

  function replace_fake_select2(fake) {
    // On the first click on select2-look like div, initialize select2, remove the placeholder and resend the click
    fake.hide();
    var select = $('select[name$=\\[product_ids\\]\\[\\]]', fake.parent());
    select.show();
    select.select2({
      formatNoMatches: function() {
        return formatNoMatches;
      },
      width: '100%'
    });

    select.change(function(e) {
      if (block_save_select_change) {
        return;
      }

      table_neeed_update = true;
      build_total(undefined, $('#order_array table'));

      var id = select.parent().data('id');
      var product_ids = e.val;
      $.ajax({
        type: "put",
        data: {
          product_ids: product_ids
        },
        url: '/api/0.1/order_arrays/' + order_array_id + '/orders/' + id + '.json',
        beforeSend: beforeSendWaiting,
        complete: completeWaiting,
        error: ajaxError
      });
    });
    fake.remove();
  }

  function fake_select2_key_event(e) {
    e.stopPropagation();

    if (arrow_move_focus($(this), e)) {
      $('select', $(this)).select2('close');
      return;
    } else {
      var fake = $('.fake', $(this));
      if (fake) {
        replace_fake_select2(fake);
        var input = $('input', $(this));
        input.focus();
        // var ee = jQuery.Event('keydown');
        // ee.which = e.which;
        // $('input', $(this)).trigger(ee);
      }
    }
  }

  function fake_select2_click_event(e) {
    e.stopPropagation();
    replace_fake_select2($(this));
    if (e.type == "click" && e.clientX && e.clientY) {
      $(document.elementFromPoint(e.clientX, e.clientY)).click();
    }
  }

  function active_fake_select2(selector) {
    $('.fake', selector).on('click', fake_select2_click_event);
    selector.on('keydown', fake_select2_key_event);
  }

  function build_fake_select2(container, products, product_ids) {
    data_products = [];
    $.each(products, function(i, product) {
      data_products.push({
        id: product.id,
        code: product.code,
        active: product_ids.indexOf(product.id.toString()) >= 0
      });
    });

    return container.html(SMT['order_arrays/fake_select2']({
      products: data_products
    }));
  }

  function build_total(e, table) {
    var $table = $(table),
      sum_column = [],
      grand_total = {};

    $('tbody tr', $table).each(function(i, tr) {
      var $tr = $(tr),
        sum_row = {},
        total = 0;
      $('td[data-id] select', $tr).each(function(j, select) {
        var vals = $(select).val();
        if (vals) {
          if (!sum_column[j]) {
            sum_column[j] = {
              undefined: 0
            };
          }
          $.each(vals, function(i, val) {
            sum_row[val] = (sum_row[val] || 0) + 1;
            sum_column[j][val] = (sum_column[j][val] || 0) + 1;
          });
        }
      });
      $('td[data-sum-product-id]', $tr).each(function(i, td) {
        var pid = $(td).data('sum-product-id');
        td.innerHTML = sum_row[pid] || '-';
        grand_total[pid] = (grand_total[pid] || 0) + (sum_row[pid] || 0);
        total += sum_row[pid] || 0;
      });
      $('td.total-products', $tr).html(total || '-');
    });

    // Columns
    var product_length = $('tfoot tr').length - 1;
    var row_length = $('thead:not(.tablesorter-stickyHeader) tr:first .order', $table).length;
    grand_total[undefined] = 0;
    $('tfoot tr').each(function(i, tr) {
      var $tr = $(tr),
        pid = $('th[data-product_id]', $tr).data('product_id');
      $('td:nth-child(n+4)', $(tr)).each(function(j, td) {
        sum_column[j] = sum_column[j] || {};
        td.innerHTML = sum_column[j] && sum_column[j][pid] || '-';
        sum_column[j][undefined] += sum_column[j][pid] || 0;
      });
      $('td:nth-child(' + (row_length + 4 + i) + ')', $tr).html(grand_total[pid] || '-');
      grand_total[undefined] += grand_total[pid] || 0;
    });

    table_trigger_update();
  }

  function table_trigger_update() {
    if (table_neeed_update) {
      table_neeed_update = false;
      $("#order_array table").trigger("update");
    }
  }

  var products = {};

  function display_order_array(data) {
    var container = $('#order_array div');
    $.each(data.products, function(i, product) {
      products[product.id] = product;
    });
    $.each(data.rows, function(i, row) {
      $.each(row.orders, function(i, order) {
        order.products = [];
        $.each(products, function(i, product) {
          order.products.push({
            id: product.id,
            code: product.code,
            active: order.product_ids.indexOf(product.id) >= 0
          });
        });
      });
    });
    data.i18n = mustache_i18n;
    $(container).html(SMT['order_arrays/edit'](data));

    var headers = {
      0: {
        sorter: false
      }
    };
    var filter_functions = {};
    for (var i = 0; i < data.columns.length; i++) {
      headers[i + 3] = {
        sorter: false
      };
      filter_functions[i + 3] = filter_text;
    }
    var filter_formatter = {
      0: function($cell, indx) {
        return false;
      }
    };
    for (var i = 0; i < data.products.length + 1; i++) {
      filter_formatter[i + data.columns.length + 3] = function($cell, indx) {
        return false;
      };
    }
    $("#order_array table").bind("tablesorter-initialized", build_total).tablesorter({
      textExtraction: function(node, table, cellIndex) {
        if (cellIndex >= 3 && cellIndex < data.columns.length + 3) {
          return $.map($("[name$=\\[product_ids\\]\\[\\]] :selected", node), function(e, i) {
            return e.text;
          }).join(",");
        } else {
          return $(node).text();
        }
      },
      headers: headers,
      theme: "bootstrap",
      // Show order icon v and ^
      headerTemplate: '{content} {icon}',
      widgets: ["uitheme", "stickyHeaders", "filter", "columnSelector"],
      widgetOptions: {
        filter_childRows: true,
        // class name applied to filter row and each input
        filter_cssFilter: "tablesorter-filter",
        filter_functions: filter_functions,
        filter_formatter: filter_formatter
      }
    });

    $('#order_array table thead input').focusin(table_trigger_update);
    $('#order_array table thead .tablesorter-icon').click(table_trigger_update);

    active_fake_select2($('td[data-id]'));

    function change_order(product_id, add_product, remove_product, paste, copy, selector) {
      var orders = {};
      block_save_select_change = true;
      selector.each(function(i, td) {
        var val = [];
        if (add_product || remove_product) {
          val = $('select', $(this)).val() || [];
          var index = val.indexOf(product_id);
          if (add_product) {
            if (index < 0) {
              val.push(product_id);
            }
          } else if (remove_product) {
            if (index >= 0) {
              val.splice(index, 1);
            }
          }
        } else if (paste && copy) {
          val = copy[i];
        }
        build_fake_select2($(td), products, val);

        orders[$(this).data('id')] = {
          product_ids: val
        };
      });
      active_fake_select2(selector);
      block_save_select_change = false;
      table_neeed_update = true;
      build_total(undefined, $('#order_array table'));

      $.ajax({
        type: "patch",
        data: JSON.stringify({
          orders: orders
        }),
        contentType: "application/json",
        url: '/api/0.1/order_arrays/' + order_array_id + '.json',
        beforeSend: beforeSendWaiting,
        complete: completeWaiting,
        error: ajaxError
      });
    }

    var copy_row;
    $('.copy_row').click(function(e) {
      var tr = $(this).closest('tr');
      copy_row = [];
      $.each($('td[data-id] select', tr), function(i, select) {
        copy_row.push($.map($(select).val() || ['-1'], function(n) {
          return n.toString();
        }));
      });
    });

    $('.empty_row, .paste_row, .add_product_row, .remove_product_row').click(function(e) {
      if (confirm(I18n.t('order_arrays.edit.confirm_ovewrite_row'))) {
        var tr = $(this).closest('tr'),
          add_product = $(this).hasClass('add_product_row'),
          remove_product = $(this).hasClass('remove_product_row'),
          paste = $(this).hasClass('paste_row'),
          product_id = ($(this).data('product_id') || 0).toString(),
          selector = $('td[data-id]', tr);
        change_order(product_id, add_product, remove_product, paste, copy_row, selector);
      }
    });

    var copy_column;
    $('.copy_column').click(function(e) {
      var td_index = $(this).closest('th').index('th') + 1;
      copy_column = [];
      $('tbody tr td:nth-child(' + td_index + ') select', $(this).closest('table')).each(function(i, select) {
        copy_column.push($.map($(select).val() || ['-1'], function(n) {
          return n.toString();
        }));
      });
    });

    $('.empty_column, .paste_column, .add_product_column, .remove_product_column').click(function(e) {
      if (confirm(I18n.t('order_arrays.edit.confirm_ovewrite_column'))) {
        var td_index = $(this).closest('th').index('th') + 1,
          add_product = $(this).hasClass('add_product_column'),
          remove_product = $(this).hasClass('remove_product_column'),
          paste = $(this).hasClass('paste_column'),
          product_id = ($(this).data('product_id') || 0).toString();
        selector = $('tbody tr td:nth-child(' + td_index + ')', $(this).closest('table'));
        change_order(product_id, add_product, remove_product, paste, copy_column, selector);
      }
    });

    $('.planning').click(function(e) {
      var index = $(this).closest('th').index('th') - 3,
        planning_id = $(this).data('planning_id').toString();
      $.ajax({
        type: "patch",
        contentType: "application/json",
        url: '/api/0.1/plannings/' + planning_id + '/orders/' + order_array_id + '/' + index + '.json',
        beforeSend: beforeSendWaiting,
        success: function() {
          window.location = '/plannings/' + planning_id +'/edit';
        },
        complete: completeWaiting,
        error: ajaxError
      });
    });
  }

  $("#dialog-loading").dialog({
    autoOpen: true,
    modal: true
  });

  $.ajax({
    url: '/order_arrays/' + order_array_id + '.json',
    beforeSend: beforeSendWaiting,
    success: display_order_array,
    complete: function(data) {
      completeWaiting(data);
      $("#dialog-loading").dialog('close');
    },
    error: ajaxError
  });
}

Paloma.controller('OrderArray').prototype.new = function() {
  order_arrays_new(this.params);
};

Paloma.controller('OrderArray').prototype.create = function() {
  order_arrays_new(this.params);
};

Paloma.controller('OrderArray').prototype.edit = function() {
  order_arrays_edit(this.params);
};

Paloma.controller('OrderArray').prototype.update = function() {
  order_arrays_edit(this.params);
};
<|endoftext|>