<commit_before>/**
 * Base class for creating other shapes.
 * Each custom shape must extends from this class.
 *
 * @since 1.0.0
 * @example
 * import Shape from 'kittik-shape-basic';
 *
 * export default class Rectangle extends Shape {
 *   render(cursor) {
 *     // Implement your logic here for rendering the shape
 *   }
 * }
 */
export default class Shape {
  /**
   * @constructor
   * @param {Object} [options]
   * @param {String} [options.text] Text that will be rendered in the shape
   * @param {Number|String} [options.width] Shape width can be 100 (cells) or 100%
   * @param {Number|String} [options.height] Shape height can be 100 (cells) or 100%
   * @param {Number|String} [options.x] Absolute coordinate X can be 100 (cells), left, center, right or percents
   * @param {Number|String} [options.y] Absolute coordinate Y can be 100 (cells), top, middle, bottom or percents
   * @param {Number|String} [options.background] Background color can be number from color table or color name
   * @param {Number|String} [options.foreground] Foreground color can be number from color table or color name
   */
  constructor(options = {}) {
    this.setText(options.text);
    this.setWidth(options.width);
    this.setHeight(options.height);
    this.setX(options.x);
    this.setY(options.y);
    this.setBackground(options.background);
    this.setForeground(options.foreground);
  }

  /**
   * Get option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @returns {*}
   */
  get(path) {
    return path.split('.').reduce((obj, key) => obj && obj[key], this);
  }

  /**
   * Set new option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @param {*} value Value that need to be written to the options object
   * @returns {Shape}
   */
  set(path, value) {
    let obj = this;
    let tags = path.split('.');
    let len = tags.length - 1;

    for (let i = 0; i < len; i++) {
      if (typeof obj[tags[i]] === 'undefined') obj[tags[i]] = {};
      obj = obj[tags[i]];
    }

    obj[tags[len]] = value;

    return this;
  }

  /**
   * Get text content from this shape.
   *
   * @returns {String}
   */
  getText() {
    return this.get('text');
  }

  /**
   * Set new text content to this shape.
   *
   * @param {String} [text=''] New text
   * @returns {Shape}
   */
  setText(text = '') {
    return this.set('text', text);
  }

  /**
   * Get shape width.
   *
   * @returns {Number}
   */
  getWidth() {
    const width = this.get('width');

    if (/\d+%$/.test(width)) return Math.ceil(width.slice(0, -1) * process.stdout.columns / 100);

    return width;
  }

  /**
   * Set new shape width.
   *
   * @param {Number} [width=15] Shape width
   * @returns {Shape}
   */
  setWidth(width = 15) {
    return this.set('width', width);
  }

  /**
   * Get shape height.
   *
   * @returns {Number}
   */
  getHeight() {
    const height = this.get('height');

    if (/\d+%$/.test(height)) return Math.ceil(height.slice(0, -1) * process.stdout.rows / 100);

    return height;
  }

  /**
   * Set new shape height.
   *
   * @param {Number} [height=5] Shape height
   * @returns {Shape}
   */
  setHeight(height = 5) {
    return this.set('height', height);
  }

  /**
   * Get X coordinate.
   *
   * @returns {Number}
   */
  getX() {
    const x = this.get('x');

    if (x === 'left') return 1;
    if (x === 'center') return Math.ceil(process.stdout.columns / 2 - this.getWidth() / 2) + 1;
    if (x === 'right') return Math.ceil(process.stdout.columns - this.getWidth()) + 1;
    if (/\d+%$/.test(x)) return Math.ceil(x.slice(0, -1) * process.stdout.columns / 100) + 1;

    return x;
  }

  /**
   * Set X coordinate.
   *
   * @param {Number} [x=10]
   * @returns {Shape}
   */
  setX(x = 10) {
    return this.set('x', x);
  }

  /**
   * Get Y coordinate.
   *
   * @returns {Number}
   */
  getY() {
    const y = this.get('y');

    if (y === 'top') return 1;
    if (y === 'middle') return Math.ceil(process.stdout.rows / 2 - this.getHeight() / 2) + 1;
    if (y === 'bottom') return Math.ceil(process.stdout.rows - this.getHeight()) + 1;
    if (/\d+%$/.test(y)) return Math.ceil(y.slice(0, -1) * process.stdout.rows / 100) + 1;

    return y;
  }

  /**
   * Set Y coordinate.
   *
   * @param {Number} [y=10]
   * @returns {Shape}
   */
  setY(y = 10) {
    return this.set('y', y);
  }

  /**
   * Get background color.
   *
   * @returns {String}
   */
  getBackground() {
    return this.get('background');
  }

  /**
   * Set new background color.
   *
   * @param {String} background Background value from cursor colors or color name
   * @returns {Shape}
   */
  setBackground(background) {
    return this.set('background', background);
  }

  /**
   * Get foreground color.
   *
   * @returns {String}
   */
  getForeground() {
    return this.get('foreground');
  }

  /**
   * Set new foreground color.
   *
   * @param {String} foreground Foreground value from cursor colors or color name
   * @returns {Shape}
   */
  setForeground(foreground) {
    return this.set('foreground', foreground);
  }

  /**
   * Base render method that must be implemented in childes.
   *
   * @abstract
   * @param {Cursor} cursor Cursor instance which you can use for render the shape
   * @throws {Error} Throws error if method will not be overridden
   */
  render(cursor) {
    throw new Error('render() method must be implemented');
  }

  /**
   * Returns Object representation of the shape.
   * This representation consists of all options fields.
   *
   * @returns {Object}
   */
  toObject() {
    return {
      type: this.constructor.name,
      options: {
        text: this.get('text'),
        width: this.get('width'),
        height: this.get('height'),
        x: this.get('x'),
        y: this.get('y'),
        background: this.get('background'),
        foreground: this.get('foreground')
      }
    };
  }

  /**
   * Returns JSON representation of the shape.
   *
   * @returns {String}
   */
  toJSON() {
    return JSON.stringify(this.toObject());
  }

  /**
   * Wrapper around `new Shape()`.
   *
   * @static
   * @param {*} args
   * @returns {Shape}
   */
  static create(...args) {
    return new this(...args);
  }

  /**
   * Creates new Shape instance from Object representation.
   *
   * @static
   * @param {Object} obj Object that you got from {@link Shape.toObject}
   * @returns {Shape}
   */
  static fromObject(obj) {
    if (!obj.type || !obj.options) throw new Error('It looks like it is not an Object representation of the shape');
    if (obj.type !== this.name) throw new Error(`${obj.type} is not an Object representation of the ${this.name}`);

    return this.create(obj.options);
  }

  /**
   * Creates new Shape instance from JSON representation.
   *
   * @static
   * @param {String} json JSON string that you got from {@link Shape.toJSON}
   * @returns {Shape}
   */
  static fromJSON(json) {
    return this.fromObject(JSON.parse(json));
  }
}
<commit_msg>feat(shape): Update kittik-cursor allowing to optimize rendering<commit_after>/**
 * Base class for creating other shapes.
 * Each custom shape must extends from this class.
 *
 * @since 1.0.0
 * @example
 * import Shape from 'kittik-shape-basic';
 *
 * export default class Rectangle extends Shape {
 *   render(cursor) {
 *     // Implement your logic here for rendering the shape
 *   }
 * }
 */
export default class Shape {
  /**
   * @constructor
   * @param {Object} [options]
   * @param {String} [options.text] Text that will be rendered in the shape
   * @param {Number|String} [options.width] Shape width can be 100 (cells) or 100%
   * @param {Number|String} [options.height] Shape height can be 100 (cells) or 100%
   * @param {Number|String} [options.x] Absolute coordinate X can be 100 (cells), left, center, right or percents
   * @param {Number|String} [options.y] Absolute coordinate Y can be 100 (cells), top, middle, bottom or percents
   * @param {Number|String} [options.background] Background color can be number from color table or color name
   * @param {Number|String} [options.foreground] Foreground color can be number from color table or color name
   */
  constructor(options = {}) {
    this.setText(options.text);
    this.setWidth(options.width);
    this.setHeight(options.height);
    this.setX(options.x);
    this.setY(options.y);
    this.setBackground(options.background);
    this.setForeground(options.foreground);
  }

  /**
   * Get option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @returns {*}
   */
  get(path) {
    return path.split('.').reduce((obj, key) => obj && obj[key], this);
  }

  /**
   * Set new option value.
   *
   * @param {String} path Path can be set with dot-notation
   * @param {*} value Value that need to be written to the options object
   * @returns {Shape}
   */
  set(path, value) {
    let obj = this;
    let tags = path.split('.');
    let len = tags.length - 1;

    for (let i = 0; i < len; i++) {
      if (typeof obj[tags[i]] === 'undefined') obj[tags[i]] = {};
      obj = obj[tags[i]];
    }

    obj[tags[len]] = value;

    return this;
  }

  /**
   * Get text content from this shape.
   *
   * @returns {String}
   */
  getText() {
    return this.get('text');
  }

  /**
   * Set new text content to this shape.
   *
   * @param {String} [text=''] New text
   * @returns {Shape}
   */
  setText(text = '') {
    return this.set('text', text);
  }

  /**
   * Get shape width.
   *
   * @returns {Number}
   */
  getWidth() {
    const width = this.get('width');

    if (/\d+%$/.test(width)) return Math.floor(width.slice(0, -1) * process.stdout.columns / 100);

    return width;
  }

  /**
   * Set new shape width.
   *
   * @param {Number} [width=15] Shape width
   * @returns {Shape}
   */
  setWidth(width = 15) {
    return this.set('width', width);
  }

  /**
   * Get shape height.
   *
   * @returns {Number}
   */
  getHeight() {
    const height = this.get('height');

    if (/\d+%$/.test(height)) return Math.floor(height.slice(0, -1) * process.stdout.rows / 100);

    return height;
  }

  /**
   * Set new shape height.
   *
   * @param {Number} [height=5] Shape height
   * @returns {Shape}
   */
  setHeight(height = 5) {
    return this.set('height', height);
  }

  /**
   * Get X coordinate.
   *
   * @returns {Number}
   */
  getX() {
    const x = this.get('x');

    if (x === 'left') return 0;
    if (x === 'center') return Math.floor(process.stdout.columns / 2 - this.getWidth() / 2);
    if (x === 'right') return Math.floor(process.stdout.columns - this.getWidth());
    if (/\d+%$/.test(x)) return Math.floor(x.slice(0, -1) * process.stdout.columns / 100);

    return x;
  }

  /**
   * Set X coordinate.
   *
   * @param {Number} [x=10]
   * @returns {Shape}
   */
  setX(x = 10) {
    return this.set('x', x);
  }

  /**
   * Get Y coordinate.
   *
   * @returns {Number}
   */
  getY() {
    const y = this.get('y');

    if (y === 'top') return 0;
    if (y === 'middle') return Math.floor(process.stdout.rows / 2 - this.getHeight() / 2);
    if (y === 'bottom') return Math.floor(process.stdout.rows - this.getHeight());
    if (/\d+%$/.test(y)) return Math.floor(y.slice(0, -1) * process.stdout.rows / 100);

    return y;
  }

  /**
   * Set Y coordinate.
   *
   * @param {Number} [y=10]
   * @returns {Shape}
   */
  setY(y = 10) {
    return this.set('y', y);
  }

  /**
   * Get background color.
   *
   * @returns {String}
   */
  getBackground() {
    return this.get('background');
  }

  /**
   * Set new background color.
   *
   * @param {String} [background=false] Background value from cursor colors or color name
   * @returns {Shape}
   */
  setBackground(background = false) {
    return this.set('background', background);
  }

  /**
   * Get foreground color.
   *
   * @returns {String}
   */
  getForeground() {
    return this.get('foreground');
  }

  /**
   * Set new foreground color.
   *
   * @param {String} [foreground = false] Foreground value from cursor colors or color name
   * @returns {Shape}
   */
  setForeground(foreground = false) {
    return this.set('foreground', foreground);
  }

  /**
   * Base render method that must be implemented in childes.
   *
   * @abstract
   * @param {Cursor} cursor Cursor instance which you can use for render the shape
   * @throws {Error} Throws error if method will not be overridden
   */
  render(cursor) {
    throw new Error('render() method must be implemented');
  }

  /**
   * Returns Object representation of the shape.
   * This representation consists of all options fields.
   *
   * @returns {Object}
   */
  toObject() {
    return {
      type: this.constructor.name,
      options: {
        text: this.get('text'),
        width: this.get('width'),
        height: this.get('height'),
        x: this.get('x'),
        y: this.get('y'),
        background: this.get('background'),
        foreground: this.get('foreground')
      }
    };
  }

  /**
   * Returns JSON representation of the shape.
   *
   * @returns {String}
   */
  toJSON() {
    return JSON.stringify(this.toObject());
  }

  /**
   * Wrapper around `new Shape()`.
   *
   * @static
   * @param {*} args
   * @returns {Shape}
   */
  static create(...args) {
    return new this(...args);
  }

  /**
   * Creates new Shape instance from Object representation.
   *
   * @static
   * @param {Object} obj Object that you got from {@link Shape.toObject}
   * @returns {Shape}
   */
  static fromObject(obj) {
    if (!obj.type || !obj.options) throw new Error('It looks like it is not an Object representation of the shape');
    if (obj.type !== this.name) throw new Error(`${obj.type} is not an Object representation of the ${this.name}`);

    return this.create(obj.options);
  }

  /**
   * Creates new Shape instance from JSON representation.
   *
   * @static
   * @param {String} json JSON string that you got from {@link Shape.toJSON}
   * @returns {Shape}
   */
  static fromJSON(json) {
    return this.fromObject(JSON.parse(json));
  }
}
<|endoftext|>


<commit_before>/*#ifndef(UMD)*/
"use strict";
/*global _GpfClassDefinition*/ // GPF class definition
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfEmptyFunc*/ // An empty function
/*global _gpfFunc*/ // Create a new function using the source
/*global _gpfGenDefHandler*/ // Class handler for class types (interfaces...)
/*global _gpfGetClassDefinition*/ // Get GPF class definition for a constructor
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*exported _gpfA*/
/*exported _gpfAttribute*/
/*exported _gpfAttributesAdd*/
/*exported _gpfDefAttr*/
/*#endif*/

// @property {gpf.attributes.Map|null} Attributes of this class (null if none)
_GpfClassDefinition.prototype._attributes = null;

var
    // gpf.attributes shortcut
    _gpfA = gpf.attributes = {},

    /**
     * Used for empty members
     *
     * @type {gpf.attributes.Array}
     */
    _gpfEmptyMemberArray = 0;

/**
 * Generates a factory capable of creating a new instance of a class
 *
 * @param {Function} objectClass Object constructor
 * @param {String} name Alias name (will be prefixed by $)
 * @closure
 */
function _gpfAlias (objectClass, name) {
    name = "$" + name;
    gpf[name] = (function(){
        var Proxy = (_gpfFunc("return function " + name + "(args) {this.constructor.apply(this, args);};"))();
        Proxy.prototype = objectClass.prototype;
        return function() {
            return new Proxy(arguments);
        };
    }());
}

/**
 * gpf.define handler for attributes
 *
 * @type {Function}
 */
var _gpfDefAttrBase = _gpfGenDefHandler("gpf.attributes", "Attribute");

/**
 * gpf.define for attributes
 *
 * @param {String} name Attribute name. If it contains a dot, it is treated as absolute contextual.
 * Otherwise, it is relative to "gpf.attributes". If starting with $ (and no dot), the contextual name will be the
 * "gpf.attributes." + name(without $) + "Attribute" and an alias is automatically created
 * @param {Function|string} [base=undefined] base Base attribute (or contextual name)
 * @param {Object} [definition=undefined] definition Attribute definition
 * @return {Function}
 */
function _gpfDefAttr (name, base, definition) {
    var
        isAlias = name.charAt(0) === "$",
        fullName,
        result;
    if (isAlias) {
        name = name.substr(1);
        fullName = name + "Attribute";
    } else {
        fullName = name;
    }
    result = _gpfDefAttrBase(fullName, base, definition);
    if (isAlias) {
        _gpfAlias(result, name);
    }
    return result;
}

/**
 * Base class for any attribute
 *
 * @class gpf.attributes.Attribute
 */
var _gpfAttribute = _gpfDefine("gpf.attributes.Attribute", Object, {
    protected: {

        // Name of the member the attribute is associated to
        _member: "",

        /**
         * This method is the implementation of the attribute: it receives the prototype to alter
         * NOTE: this is called *after* all declared members are set
         *
         * @param {Object} objPrototype Class prototype
         */
        _alterPrototype: function (objPrototype) {
            _gpfIgnore(objPrototype);
        }

    },
    public: {

        getMemberName: function () {
            return this._member;
        }

    }
});

/*#ifdef(DEBUG)*/

// DEBUG specifics

gpf.ASSERT_ATTRIBUTE_CLASS_ONLY = function (value) {
    gpf.ASSERTS({
        "Expected a class parameter": "function" === typeof value,
        "Expected an Attribute-like class parameter": value.prototype instanceof _gpfAttribute
    });
};

gpf.ASSERT_ATTRIBUTE_ONLY = function (value) {
    gpf.ASSERT(value instanceof _gpfAttribute, "Expected an Attribute-like parameter");
};

if (!gpf.ASSERT_ATTRIBUTE_CLASS_ONLY) {

/*#else*/

    /*gpf:nop*/ gpf.ASSERT_ATTRIBUTE_CLASS_ONLY = _gpfEmptyFunc;
    /*gpf:nop*/ gpf.ASSERT_ATTRIBUTE_ONLY = _gpfEmptyFunc;

/*#endif*/

/*#ifdef(DEBUG)*/

}

/**
 * Attribute array, generally used to list attributes on a class member
 *
 * @class gpf.attributes.Array
 */
_gpfDefine("gpf.attributes.Array", Object, {
    private: {

        // @property {gpf.attributes.Attribute[]}
        _array: []

    },
    public: {

        constructor: function () {
            this._array = []; // Create a new instance of the array
        },

        /**
         * @inheritdoc gpf.interfaces.IReadOnlyArray#getItem
         *
         * NOTE: will be replaced with IReadOnlyArray implementation (once i_array.js is loaded)
         */
        getItemsCount: function () {
            return this._array.length;
        },

        /**
         * @inheritdoc gpf.interfaces.IReadOnlyArray#getItem
         *
         * NOTE: will be replaced with IReadOnlyArray implementation (once i_array.js is loaded)
         */
        getItem: function (index) {
            return this._array[index];
        },

        /**
         * Return the first occurrence of the expected class
         *
         * @param {gpf.attributes.Attribute} expectedClass the class to match
         * @return {Boolean}
         */
        has: function (expectedClass) {
            gpf.ASSERT_ATTRIBUTE_CLASS_ONLY(expectedClass);
            /*gpf:inline(array)*/ return !this._array.every(function (attribute) {
                return !(attribute instanceof expectedClass);
            });
        },

        /**
         * Returns a new array with all attributes matching the expected class
         *
         * @param {Function} expectedClass the class to match
         * @return {gpf.attributes.Array}
         */
        filter: function (expectedClass) {
            gpf.ASSERT_ATTRIBUTE_CLASS_ONLY(expectedClass);
            var result = new _gpfA.Array();
            /*gpf:inline(array)*/ result._array = this._array.filter(function (attribute) {
                return attribute instanceof expectedClass;
            });
            return result;
        },

        // [].forEach
        forEach: function (callback, thisArg) {
            /*gpf:inline(array)*/ this._array.forEach(callback, thisArg);
        },

        // [].every
        every: function (callback, thisArg) {
            return /*gpf:inline(array)*/ this._array.every(callback, thisArg);
        }

    }

});

function _encodeMember(member) {
    if ("constructor" === member) {
        return "constructor ";
    }
    return member;
}

function _decodeMember(member) {
    if ("constructor " === member) {
        return "constructor";
    }
    return member;
}

/**
 * Attribute map, generally used to list attributes of a class.
 *
 * There are two known particular cases:
 * - constructor: added as "constructor " to avoid collisions with JS member (wscript)
 * - Class: that represents class attributes
 *
 * @class gpf.attributes.Map
 */
_gpfDefine("gpf.attributes.Map", Object, {
    private: {

        // Dictionary of attributes per member
        _members: {},

        // Attributes count
        _count: 0,

        /**
         * Copy the content of this map to a new one
         *
         * @param {gpf.attributes.Map} to recipient of the copy
         * @param {Function} [callback=undefined] callback function to test if the mapping should be added
         * @param {*} [param=undefined] param additional parameter for the callback
         * @return {gpf.attributes.Map} to
         */
        _copy: function (to, callback, param) {
            if (this._count) {
                /*gpf:inline(object)*/ _gpfObjectForEach(this._members, function (attributeArray, member) {
                    member = _decodeMember(member);
                    /*gpf:inline(array)*/ attributeArray._array.forEach(function (attribute) {
                        if (!callback || callback(member, attribute, param)) {
                            to.add(member, attribute);
                        }
                    });
                });
            }
            return to;
        },

        /**
         * Callback for _copy, test if attribute is of a given class
         *
         * @param {String} member
         * @param {gpf.attributes.Attribute} attribute
         * @param {Function} expectedClass
         * @return {Boolean}
         */
        _filterCallback: function (member, attribute, expectedClass) {
            _gpfIgnore(member);
            return attribute instanceof expectedClass;
        }

    },
    public: {

        // @param {Object|Function} [object=undefined] object Object or constructor to read attributes from
        constructor: function (object) {
            this._members = {};
            if (object instanceof Function) {
                this.fillFromClassDef(_gpfGetClassDefinition(object));
            } else if (undefined !== object) {
                this.fillFromObject(object);
            }
        },

        /**
         * Fill the map using class definition object
         *
         * @param {gpf.classDef} classDef class definition
         * @return {Number} number of attributes in the resulting map
         */
        fillFromClassDef: function (classDef) {
            var attributes,
                Super;
            while (classDef) { // !undefined && !null
                attributes = classDef._attributes;
                if (attributes) {
                    attributes._copy(this);
                }
                Super = classDef._Super;
                if (Super !== Object) { // Can't go upper
                    classDef = _gpfGetClassDefinition(Super);
                } else {
                    break;
                }
            }
            return this._count;
        },

        /**
         * Fill the map using object's attributes
         *
         * @param {Object} object object to get attributes from
         * @return {Number} number of attributes in the resulting map
         */
        fillFromObject: function (object) {
            var classDef = _gpfGetClassDefinition(object.constructor);
            return this.fillFromClassDef(classDef);
        },

        getCount: function () {
            return this._count;
        },

        /**
         * Associate an attribute to a member
         *
         * @param {String} member member name
         * @param {gpf.attributes.Attribute} attribute attribute to map
         */
        add: function (member, attribute) {
            gpf.ASSERT_ATTRIBUTE_ONLY(attribute);
            member = _encodeMember(member);
            var array = this._members[member];
            if (undefined === array) {
                array = this._members[member] = new _gpfA.Array();
            }
            array._array.push(attribute);
            ++this._count;
        },

        /**
         * Creates a new map that contains only instances of the given
         * attribute class
         *
         * @param {Function} expectedClass
         * @return {gpf.attributes.Map}
         */
        filter: function (expectedClass) {
            gpf.ASSERT_ATTRIBUTE_CLASS_ONLY(expectedClass);
            return this._copy(new _gpfA.Map(), this._filterCallback, expectedClass);
        },

        /**
         * Returns the array of attributes associated to a member
         *
         * @param {String} member
         * @return {gpf.attributes.Array}
         */
        getMemberAttributes: function (member) {
            member = _encodeMember(member);
            var result = this._members[member];
            if (undefined === result || !(result instanceof _gpfA.Array)) {
                if (0 === _gpfEmptyMemberArray) {
                    _gpfEmptyMemberArray = new _gpfA.Array();
                }
                result = _gpfEmptyMemberArray;
            }
            return result;
        },

        /**
         * Returns the list of members stored in this map
         *
         * @return {String[]}
         */
        getMembers: function () {
            var result = [];
            /*gpf:inline(object)*/ _gpfObjectForEach(this._members, function (attributes, member) {
                _gpfIgnore(attributes);
                result.push(_decodeMember(member));
            });
            return result;
        },

        /**
         * The forEach() method executes a provided function once per member
         *
         * @param {Function} callback function to execute for each element, taking three arguments
         * - {gpf.attributes.Array} array
         * - {String} member
         * - {Object} dictionary
         * @param {Object} [thisArg=undefined] thisArg value to use as this when executing callback
         */
        forEach: function (callback, thisArg) {
            /*gpf:inline(object)*/ _gpfObjectForEach(this._members, function (attributes, member, dictionary) {
                callback.apply(thisArg, [attributes, _decodeMember(member), dictionary]);
            });
        }

    }
});

/**
 * Add the attribute list to the given prototype associated with the provided member name
 *
 * @param {Function} objectClass class constructor
 * @param {String} name member name
 * @param {gpf.attributes.Array|gpf.attributes.Attribute|gpf.attributes.Attribute[]} attributes
 */
var _gpfAttributesAdd = _gpfA.add = function (objectClass, name, attributes) {
    // Check attributes parameter
    if (attributes instanceof _gpfA.Array) {
        attributes = attributes._array;
    } else if (!(attributes instanceof Array)) {
        attributes = [attributes];
    }
    var
        classDef = _gpfGetClassDefinition(objectClass),
        objectClassOwnAttributes = classDef._attributes,
        len,
        idx,
        attribute;
    if (!objectClassOwnAttributes) {
        objectClassOwnAttributes = classDef._attributes = new _gpfA.Map();
    }
    len = attributes.length;
    for (idx = 0; idx < len; ++idx) {
        attribute = attributes[idx];
        gpf.ASSERT(attribute instanceof _gpfAttribute, "Expected attribute");
        attribute._member = name; // Assign member name
        objectClassOwnAttributes.add(name, attribute);
        attribute._alterPrototype(objectClass.prototype);
    }
};
<commit_msg>Chainable fillFromClassDef<commit_after>/*#ifndef(UMD)*/
"use strict";
/*global _GpfClassDefinition*/ // GPF class definition
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfEmptyFunc*/ // An empty function
/*global _gpfFunc*/ // Create a new function using the source
/*global _gpfGenDefHandler*/ // Class handler for class types (interfaces...)
/*global _gpfGetClassDefinition*/ // Get GPF class definition for a constructor
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*exported _gpfA*/
/*exported _gpfAttribute*/
/*exported _gpfAttributesAdd*/
/*exported _gpfDefAttr*/
/*#endif*/

// @property {gpf.attributes.Map|null} Attributes of this class (null if none)
_GpfClassDefinition.prototype._attributes = null;

var
    // gpf.attributes shortcut
    _gpfA = gpf.attributes = {},

    /**
     * Used for empty members
     *
     * @type {gpf.attributes.Array}
     */
    _gpfEmptyMemberArray = 0;

/**
 * Generates a factory capable of creating a new instance of a class
 *
 * @param {Function} objectClass Object constructor
 * @param {String} name Alias name (will be prefixed by $)
 * @closure
 */
function _gpfAlias (objectClass, name) {
    name = "$" + name;
    gpf[name] = (function(){
        var Proxy = (_gpfFunc("return function " + name + "(args) {this.constructor.apply(this, args);};"))();
        Proxy.prototype = objectClass.prototype;
        return function() {
            return new Proxy(arguments);
        };
    }());
}

/**
 * gpf.define handler for attributes
 *
 * @type {Function}
 */
var _gpfDefAttrBase = _gpfGenDefHandler("gpf.attributes", "Attribute");

/**
 * gpf.define for attributes
 *
 * @param {String} name Attribute name. If it contains a dot, it is treated as absolute contextual.
 * Otherwise, it is relative to "gpf.attributes". If starting with $ (and no dot), the contextual name will be the
 * "gpf.attributes." + name(without $) + "Attribute" and an alias is automatically created
 * @param {Function|string} [base=undefined] base Base attribute (or contextual name)
 * @param {Object} [definition=undefined] definition Attribute definition
 * @return {Function}
 */
function _gpfDefAttr (name, base, definition) {
    var
        isAlias = name.charAt(0) === "$",
        fullName,
        result;
    if (isAlias) {
        name = name.substr(1);
        fullName = name + "Attribute";
    } else {
        fullName = name;
    }
    result = _gpfDefAttrBase(fullName, base, definition);
    if (isAlias) {
        _gpfAlias(result, name);
    }
    return result;
}

/**
 * Base class for any attribute
 *
 * @class gpf.attributes.Attribute
 */
var _gpfAttribute = _gpfDefine("gpf.attributes.Attribute", Object, {
    protected: {

        // Name of the member the attribute is associated to
        _member: "",

        /**
         * This method is the implementation of the attribute: it receives the prototype to alter
         * NOTE: this is called *after* all declared members are set
         *
         * @param {Object} objPrototype Class prototype
         */
        _alterPrototype: function (objPrototype) {
            _gpfIgnore(objPrototype);
        }

    },
    public: {

        getMemberName: function () {
            return this._member;
        }

    }
});

/*#ifdef(DEBUG)*/

// DEBUG specifics

gpf.ASSERT_ATTRIBUTE_CLASS_ONLY = function (value) {
    gpf.ASSERTS({
        "Expected a class parameter": "function" === typeof value,
        "Expected an Attribute-like class parameter": value.prototype instanceof _gpfAttribute
    });
};

gpf.ASSERT_ATTRIBUTE_ONLY = function (value) {
    gpf.ASSERT(value instanceof _gpfAttribute, "Expected an Attribute-like parameter");
};

if (!gpf.ASSERT_ATTRIBUTE_CLASS_ONLY) {

/*#else*/

    /*gpf:nop*/ gpf.ASSERT_ATTRIBUTE_CLASS_ONLY = _gpfEmptyFunc;
    /*gpf:nop*/ gpf.ASSERT_ATTRIBUTE_ONLY = _gpfEmptyFunc;

/*#endif*/

/*#ifdef(DEBUG)*/

}

/**
 * Attribute array, generally used to list attributes on a class member
 *
 * @class gpf.attributes.Array
 */
_gpfDefine("gpf.attributes.Array", Object, {
    private: {

        // @property {gpf.attributes.Attribute[]}
        _array: []

    },
    public: {

        constructor: function () {
            this._array = []; // Create a new instance of the array
        },

        /**
         * @inheritdoc gpf.interfaces.IReadOnlyArray#getItem
         *
         * NOTE: will be replaced with IReadOnlyArray implementation (once i_array.js is loaded)
         */
        getItemsCount: function () {
            return this._array.length;
        },

        /**
         * @inheritdoc gpf.interfaces.IReadOnlyArray#getItem
         *
         * NOTE: will be replaced with IReadOnlyArray implementation (once i_array.js is loaded)
         */
        getItem: function (index) {
            return this._array[index];
        },

        /**
         * Return the first occurrence of the expected class
         *
         * @param {gpf.attributes.Attribute} expectedClass the class to match
         * @return {Boolean}
         */
        has: function (expectedClass) {
            gpf.ASSERT_ATTRIBUTE_CLASS_ONLY(expectedClass);
            /*gpf:inline(array)*/ return !this._array.every(function (attribute) {
                return !(attribute instanceof expectedClass);
            });
        },

        /**
         * Returns a new array with all attributes matching the expected class
         *
         * @param {Function} expectedClass the class to match
         * @return {gpf.attributes.Array}
         */
        filter: function (expectedClass) {
            gpf.ASSERT_ATTRIBUTE_CLASS_ONLY(expectedClass);
            var result = new _gpfA.Array();
            /*gpf:inline(array)*/ result._array = this._array.filter(function (attribute) {
                return attribute instanceof expectedClass;
            });
            return result;
        },

        // [].forEach
        forEach: function (callback, thisArg) {
            /*gpf:inline(array)*/ this._array.forEach(callback, thisArg);
        },

        // [].every
        every: function (callback, thisArg) {
            return /*gpf:inline(array)*/ this._array.every(callback, thisArg);
        }

    }

});

function _encodeMember(member) {
    if ("constructor" === member) {
        return "constructor ";
    }
    return member;
}

function _decodeMember(member) {
    if ("constructor " === member) {
        return "constructor";
    }
    return member;
}

/**
 * Attribute map, generally used to list attributes of a class.
 *
 * There are two known particular cases:
 * - constructor: added as "constructor " to avoid collisions with JS member (wscript)
 * - Class: that represents class attributes
 *
 * @class gpf.attributes.Map
 */
_gpfDefine("gpf.attributes.Map", Object, {
    private: {

        // Dictionary of attributes per member
        _members: {},

        // Attributes count
        _count: 0,

        /**
         * Copy the content of this map to a new one
         *
         * @param {gpf.attributes.Map} to recipient of the copy
         * @param {Function} [callback=undefined] callback function to test if the mapping should be added
         * @param {*} [param=undefined] param additional parameter for the callback
         * @return {gpf.attributes.Map} to
         */
        _copy: function (to, callback, param) {
            if (this._count) {
                /*gpf:inline(object)*/ _gpfObjectForEach(this._members, function (attributeArray, member) {
                    member = _decodeMember(member);
                    /*gpf:inline(array)*/ attributeArray._array.forEach(function (attribute) {
                        if (!callback || callback(member, attribute, param)) {
                            to.add(member, attribute);
                        }
                    });
                });
            }
            return to;
        },

        /**
         * Callback for _copy, test if attribute is of a given class
         *
         * @param {String} member
         * @param {gpf.attributes.Attribute} attribute
         * @param {Function} expectedClass
         * @return {Boolean}
         */
        _filterCallback: function (member, attribute, expectedClass) {
            _gpfIgnore(member);
            return attribute instanceof expectedClass;
        }

    },
    public: {

        // @param {Object|Function} [object=undefined] object Object or constructor to read attributes from
        constructor: function (object) {
            this._members = {};
            if (object instanceof Function) {
                this.fillFromClassDef(_gpfGetClassDefinition(object));
            } else if (undefined !== object) {
                this.fillFromObject(object);
            }
        },

        /**
         * Fill the map using class definition object
         *
         * @param {gpf.classDef} classDef class definition
         * @return {gpf.attributes.Map}
         * @chainable
         */
        fillFromClassDef: function (classDef) {
            var attributes,
                Super;
            while (classDef) { // !undefined && !null
                attributes = classDef._attributes;
                if (attributes) {
                    attributes._copy(this);
                }
                Super = classDef._Super;
                if (Super !== Object) { // Can't go upper
                    classDef = _gpfGetClassDefinition(Super);
                } else {
                    break;
                }
            }
            return this;
        },

        /**
         * Fill the map using object's attributes
         *
         * @param {Object} object object to get attributes from
         * @return {gpf.attributes.Map}
         * @chainable
         */
        fillFromObject: function (object) {
            var classDef = _gpfGetClassDefinition(object.constructor);
            return this.fillFromClassDef(classDef);
        },

        getCount: function () {
            return this._count;
        },

        /**
         * Associate an attribute to a member
         *
         * @param {String} member member name
         * @param {gpf.attributes.Attribute} attribute attribute to map
         */
        add: function (member, attribute) {
            gpf.ASSERT_ATTRIBUTE_ONLY(attribute);
            member = _encodeMember(member);
            var array = this._members[member];
            if (undefined === array) {
                array = this._members[member] = new _gpfA.Array();
            }
            array._array.push(attribute);
            ++this._count;
        },

        /**
         * Creates a new map that contains only instances of the given
         * attribute class
         *
         * @param {Function} expectedClass
         * @return {gpf.attributes.Map}
         */
        filter: function (expectedClass) {
            gpf.ASSERT_ATTRIBUTE_CLASS_ONLY(expectedClass);
            return this._copy(new _gpfA.Map(), this._filterCallback, expectedClass);
        },

        /**
         * Returns the array of attributes associated to a member
         *
         * @param {String} member
         * @return {gpf.attributes.Array}
         */
        getMemberAttributes: function (member) {
            member = _encodeMember(member);
            var result = this._members[member];
            if (undefined === result || !(result instanceof _gpfA.Array)) {
                if (0 === _gpfEmptyMemberArray) {
                    _gpfEmptyMemberArray = new _gpfA.Array();
                }
                result = _gpfEmptyMemberArray;
            }
            return result;
        },

        /**
         * Returns the list of members stored in this map
         *
         * @return {String[]}
         */
        getMembers: function () {
            var result = [];
            /*gpf:inline(object)*/ _gpfObjectForEach(this._members, function (attributes, member) {
                _gpfIgnore(attributes);
                result.push(_decodeMember(member));
            });
            return result;
        },

        /**
         * The forEach() method executes a provided function once per member
         *
         * @param {Function} callback function to execute for each element, taking three arguments
         * - {gpf.attributes.Array} array
         * - {String} member
         * - {Object} dictionary
         * @param {Object} [thisArg=undefined] thisArg value to use as this when executing callback
         */
        forEach: function (callback, thisArg) {
            /*gpf:inline(object)*/ _gpfObjectForEach(this._members, function (attributes, member, dictionary) {
                callback.apply(thisArg, [attributes, _decodeMember(member), dictionary]);
            });
        }

    }
});

/**
 * Add the attribute list to the given prototype associated with the provided member name
 *
 * @param {Function} objectClass class constructor
 * @param {String} name member name
 * @param {gpf.attributes.Array|gpf.attributes.Attribute|gpf.attributes.Attribute[]} attributes
 */
var _gpfAttributesAdd = _gpfA.add = function (objectClass, name, attributes) {
    // Check attributes parameter
    if (attributes instanceof _gpfA.Array) {
        attributes = attributes._array;
    } else if (!(attributes instanceof Array)) {
        attributes = [attributes];
    }
    var
        classDef = _gpfGetClassDefinition(objectClass),
        objectClassOwnAttributes = classDef._attributes,
        len,
        idx,
        attribute;
    if (!objectClassOwnAttributes) {
        objectClassOwnAttributes = classDef._attributes = new _gpfA.Map();
    }
    len = attributes.length;
    for (idx = 0; idx < len; ++idx) {
        attribute = attributes[idx];
        gpf.ASSERT(attribute instanceof _gpfAttribute, "Expected attribute");
        attribute._member = name; // Assign member name
        objectClassOwnAttributes.add(name, attribute);
        attribute._alterPrototype(objectClass.prototype);
    }
};
<|endoftext|>


<commit_before>var util = require('util'),
    extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
    Relation = require('./Relation'),
    HtmlRelation = require('./HtmlRelation');

function HtmlStyle(config) {
    HtmlRelation.call(this, config);
}

util.inherits(HtmlStyle, HtmlRelation);

extendWithGettersAndSetters(HtmlStyle.prototype, {
    get href() {
        if (this.node.nodeName.toLowerCase() === 'link') {
            return this.node.getAttribute('href');
        }
        // Else return undefined
    },

    set href(href) {
        if (this.node.nodeName === 'link') {
            this.node.setAttribute('href', href);
        } else {
            var document = this.node.ownerDocument,
                link = document.createElement('link'),
                existingMediaAttributeValue = this.node.getAttribute('media');
            if (existingMediaAttributeValue) {
                link.setAttribute('media', existingMediaAttributeValue);
            }
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', href);
            this.node.parentNode.replaceChild(link, this.node);
            this.node = link;
        }
    },

    inline: function () {
        Relation.prototype.inline.call(this);
        if (this.node.nodeName.toLowerCase() === 'style') {
            while (this.node.firstChild) {
                this.node.removeChild(this.node.firstChild);
            }
            this.node.appendChild(this.from.parseTree.createTextNode(this.to.text));
        } else {
            var style = this.from.parseTree.createElement('style'),
                existingMediaAttributeValue = this.node.getAttribute('media');
            style.setAttribute('type', 'text/css');
            if (existingMediaAttributeValue) {
                style.setAttribute('media', existingMediaAttributeValue);
            }
            style.appendChild(this.from.parseTree.createTextNode(this.to.text));
            this.node.parentNode.replaceChild(style, this.node);
            this.node = style;
        }
        this.from.markDirty();
        return this;
    },

    attach: function (asset, position, adjacentRelation) {
        var parseTree = asset.parseTree,
            found = false;

        this.node = parseTree.createElement('link');
        this.node.setAttribute('rel', 'stylesheet');
        if (position === 'first') {
            if (parseTree.head) {
                for (var i = 0 ; i < parseTree.head.childNodes.length ; i += 1) {
                    var childNode = parseTree.head.childNodes[i],
                        nodeName = childNode.nodeName.toLowerCase();

                    if (nodeName === 'style' || (nodeName === 'link' && /^stylesheet$/i.test(childNode.getAttribute('rel')))) {
                        found = true;
                        parseTree.head.insertBefore(this.node, childNode);
                        break;
                    }
                }
                if (!found) {
                    parseTree.head.appendChild(this.node);
                }
            } else {
                // No <head>, maybe the body of a conditional comment.
                parseTree.insertBefore(this.node, parseTree.firstChild);
            }
        } else {
            this.attachNodeBeforeOrAfter(position, adjacentRelation);
        }
        return HtmlRelation.prototype.attach.call(this, asset, position, adjacentRelation);
    }
});

module.exports = HtmlStyle;
<commit_msg>HtmlStyle: Preserve all extra attributes when converting back and forth between <link> and <style>.<commit_after>var util = require('util'),
    extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
    Relation = require('./Relation'),
    HtmlRelation = require('./HtmlRelation');

function HtmlStyle(config) {
    HtmlRelation.call(this, config);
}

util.inherits(HtmlStyle, HtmlRelation);

extendWithGettersAndSetters(HtmlStyle.prototype, {
    get href() {
        if (this.node.nodeName.toLowerCase() === 'link') {
            return this.node.getAttribute('href');
        }
        // Else return undefined
    },

    set href(href) {
        if (this.node.nodeName === 'link') {
            this.node.setAttribute('href', href);
        } else {
            var document = this.node.ownerDocument,
                link = document.createElement('link');
            for (var i = 0 ; i < this.node.attributes.length ; i += 1) {
                var attribute = this.node.attributes[i];
                if (attribute.name !== 'type') {
                    link.setAttribute(attribute.name, attribute.value);
                }
            }
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', href);
            this.node.parentNode.replaceChild(link, this.node);
            this.node = link;
        }
    },

    inline: function () {
        Relation.prototype.inline.call(this);
        if (this.node.nodeName.toLowerCase() === 'style') {
            while (this.node.firstChild) {
                this.node.removeChild(this.node.firstChild);
            }
            this.node.appendChild(this.from.parseTree.createTextNode(this.to.text));
        } else {
            var style = this.from.parseTree.createElement('style');
            style.setAttribute('type', 'text/css');
            for (var i = 0 ; i < this.node.attributes.length ; i += 1) {
                var attribute = this.node.attributes[i];
                if (attribute.name !== 'type' && attribute.name !== 'href' && attribute.name !== 'rel') {
                    style.setAttribute(attribute.name, attribute.value);
                }
            }

            style.appendChild(this.from.parseTree.createTextNode(this.to.text));
            this.node.parentNode.replaceChild(style, this.node);
            this.node = style;
        }
        this.from.markDirty();
        return this;
    },

    attach: function (asset, position, adjacentRelation) {
        var parseTree = asset.parseTree,
            found = false;

        this.node = parseTree.createElement('link');
        this.node.setAttribute('rel', 'stylesheet');
        if (position === 'first') {
            if (parseTree.head) {
                for (var i = 0 ; i < parseTree.head.childNodes.length ; i += 1) {
                    var childNode = parseTree.head.childNodes[i],
                        nodeName = childNode.nodeName.toLowerCase();

                    if (nodeName === 'style' || (nodeName === 'link' && /^stylesheet$/i.test(childNode.getAttribute('rel')))) {
                        found = true;
                        parseTree.head.insertBefore(this.node, childNode);
                        break;
                    }
                }
                if (!found) {
                    parseTree.head.appendChild(this.node);
                }
            } else {
                // No <head>, maybe the body of a conditional comment.
                parseTree.insertBefore(this.node, parseTree.firstChild);
            }
        } else {
            this.attachNodeBeforeOrAfter(position, adjacentRelation);
        }
        return HtmlRelation.prototype.attach.call(this, asset, position, adjacentRelation);
    }
});

module.exports = HtmlStyle;
<|endoftext|>


<commit_before>// ========================================================================
// Copyright (c) 2008-2009, Metaweb Technologies, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY METAWEB TECHNOLOGIES AND CONTRIBUTORS
// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL METAWEB
// TECHNOLOGIES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// ========================================================================

/*
**  Automatic reconciliation
*/
var manualQueue = {};
var automaticQueue = new AutomaticQueue();

/** @constructor
  * @param {Array.<tEntity>=} initialValues
  */
function AutomaticQueue(initialValues) {
    /** @const
        @type {Array.<tEntity>}*/
    this.internalQueue = initialValues || [];
    /** @type {number} */
    this.length = this.internalQueue.length;
}

/** @param {tEntity} entity
  */
AutomaticQueue.prototype.push = function(entity) {
    entity['/rec_ui/rec_begun'] = true;
    this.length++;
    this.internalQueue.push(entity);
    updateUnreconciledCount();
    if (reconciliationBegun && !autoreconciling)
        autoReconcile();
}

/** @return {tEntity} */
AutomaticQueue.prototype.peek = function() {
    return this.internalQueue[0];
}

/** @return {tEntity|undefined} */
AutomaticQueue.prototype.shift = function() {
    if (this.length === 0)
        return undefined;
    this.length--;
    updateUnreconciledCount();
    return this.internalQueue.shift();
}

function beginAutoReconciliation() {
    $(".nowReconciling").show();
    $(".notReconciling").hide();
    $("#gettingInput").remove();
    autoReconcile();
}

var autoreconciling = false;
function finishedAutoReconciling() {
    $(".nowReconciling").hide();
    $('.notReconciling').show();
    autoreconciling = false;
}

function autoReconcile() {
    autoreconciling = true;
    if (automaticQueue.length == 0) {
        finishedAutoReconciling();
        return;
    }

    var entity = automaticQueue.peek();
    if (entity.getID() !== undefined) {
        automaticQueue.shift();
        addTimeout(autoReconcile, 0);
        if (!internalReconciler.getRecGroup(entity).shouldMerge)
            addReviewItem(entity);
        return;
    }
    getCandidates(entity, autoReconcileResults, function(){automaticQueue.shift();autoReconcile();});
}

/** @param {!tEntity} entity
    @param {boolean=} typeless
*/
function constructReconciliationQuery(entity, typeless) {
    var query = {}
    var headers = entity["/rec_ui/headers"];
    for (var i = 0; i < headers.length; i++) {
        var prop = headers[i];
        if (prop.charAt(0) != "/") continue;
        var parts = prop.split(":");
        $.each($.makeArray(entity.getChainedProperty(prop)),function(j, value) {
            var slot = query;
            if (value == undefined || value == "")
                return;
            if (parts.length === 1){
                slot[prop] = slot[prop] || [];
                slot[prop][j] = constructQueryPart(value);
                return;
            }
            slot[parts[0]]    = slot[parts[0]]    || [];
            slot[parts[0]][j] = slot[parts[0]][j] || {};
            slot = slot[parts[0]][j];
            $.each(parts.slice(1,parts.length-1), function(k,part) {
                slot[part] = slot[part] || {};
                slot = slot[part];
            });
            var lastPart = parts[parts.length-1];
            slot[lastPart] = constructQueryPart(value);
        })
    }
    if (typeless || !query['/type/object/type'])
        query['/type/object/type'] = ['/common/topic'];
    query = cleanup(query);
    entity['/rec_ui/recon_query'] = query;
    return query;

    function constructQueryPart(value) {
        if (value instanceof tEntity && !Arr.contains([undefined, "", "None", "None (merged"], value.getID()))
            return {"id":value.getID(), "name":value["/type/object/name"]}
        if (value['/rec_ui/id'] !== undefined)
            return $.makeArray(value["/type/object/name"])[0];
        return value;
    }

    /* Removes undefined values, nulls, empty lists, empty objects,
       and collapses singleton arrays down to their single values
       to better feed the reconciliation service.  */
    function cleanup(value) {
        switch(getType(value)){
        case "array":
            value = $.map(value, function(v) {
                return cleanup(v);
            });
            if (value.length === 0)
                return null;
            if (value.length === 1)
                return value[0];
            return value;
        case "object":
            var clone = {};
            for (var key in value) {
                var v = cleanup(value[key]);
                if (v === null || v === undefined)
                    continue;
                clone[key] = v;
            }
            if (isObjectEmpty(clone))
                return null;
            return clone;
        default:
            return value;
        }
    }
}

/**
 *  @param {tEntity} entity
 *  @param {function(tEntity)} callback
 *  @param {function(...)} onError
 *  @param {boolean=} typeless
 */
function getCandidates(entity, callback, onError,typeless) {
    function handler(results) {
        entity.reconResults = results;
        callback(entity);
    }
    var defaultLimit = 4;
    var limit = defaultLimit;
    if (entity.reconResults)
        limit = entity.reconResults.length * 2;
    if (!entity.typelessRecon && typeless){
        entity.typelessRecon = true;
        limit = defaultLimit;
    }
    var query = constructReconciliationQuery(entity,typeless);
    getJSON(reconciliation_url + "query?jsonp=?", {q:JSON.stringify(query), limit:limit}, handler, onError);
}

/** @param {tEntity} entity */
function autoReconcileResults(entity) {
    automaticQueue.shift();
    // no results, set to None:
    if(entity.reconResults.length == 0) {
        if (!entity.typelessRecon)
            getCandidates(entity,autoReconcileResults,function(){automaticQueue.shift();autoReconcile();},true);

        entity.reconcileWith("None", true);
        warn("No candidates found for the object:");
        warn(entity);
        addColumnRecCases(entity);
    }
    // match found:
    else if(entity.reconResults[0]["match"] == true) {
        var matchedResult = entity.reconResults[0];
        if (require_exact_name_match && !Arr.contains($.makeArray(entity['/type/object/name']),$.makeArray(matchedResult.name)[0])) {
            addToManualQueue(entity);
        }
        else {
            entity.reconcileWith(matchedResult.id, true);
            entity["/rec_ui/freebase_name"] = matchedResult.name;
            addColumnRecCases(entity);
        }
    }
    else
        addToManualQueue(entity)
    autoReconcile();
}<commit_msg>Don't auto create new an item that may appear multiple times<commit_after>// ========================================================================
// Copyright (c) 2008-2009, Metaweb Technologies, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY METAWEB TECHNOLOGIES AND CONTRIBUTORS
// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL METAWEB
// TECHNOLOGIES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// ========================================================================

/*
**  Automatic reconciliation
*/
var manualQueue = {};
var automaticQueue = new AutomaticQueue();

/** @constructor
  * @param {Array.<tEntity>=} initialValues
  */
function AutomaticQueue(initialValues) {
    /** @const
        @type {Array.<tEntity>}*/
    this.internalQueue = initialValues || [];
    /** @type {number} */
    this.length = this.internalQueue.length;
}

/** @param {tEntity} entity
  */
AutomaticQueue.prototype.push = function(entity) {
    entity['/rec_ui/rec_begun'] = true;
    this.length++;
    this.internalQueue.push(entity);
    updateUnreconciledCount();
    if (reconciliationBegun && !autoreconciling)
        autoReconcile();
}

/** @return {tEntity} */
AutomaticQueue.prototype.peek = function() {
    return this.internalQueue[0];
}

/** @return {tEntity|undefined} */
AutomaticQueue.prototype.shift = function() {
    if (this.length === 0)
        return undefined;
    this.length--;
    updateUnreconciledCount();
    return this.internalQueue.shift();
}

function beginAutoReconciliation() {
    $(".nowReconciling").show();
    $(".notReconciling").hide();
    $("#gettingInput").remove();
    autoReconcile();
}

var autoreconciling = false;
function finishedAutoReconciling() {
    $(".nowReconciling").hide();
    $('.notReconciling').show();
    autoreconciling = false;
}

function autoReconcile() {
    autoreconciling = true;
    if (automaticQueue.length == 0) {
        finishedAutoReconciling();
        return;
    }

    var entity = automaticQueue.peek();
    if (entity.getID() !== undefined) {
        automaticQueue.shift();
        addTimeout(autoReconcile, 0);
        if (!internalReconciler.getRecGroup(entity).shouldMerge)
            addReviewItem(entity);
        return;
    }
    getCandidates(entity, autoReconcileResults, function(){automaticQueue.shift();autoReconcile();});
}

/** @param {!tEntity} entity
    @param {boolean=} typeless
*/
function constructReconciliationQuery(entity, typeless) {
    var query = {}
    var headers = entity["/rec_ui/headers"];
    for (var i = 0; i < headers.length; i++) {
        var prop = headers[i];
        if (prop.charAt(0) != "/") continue;
        var parts = prop.split(":");
        $.each($.makeArray(entity.getChainedProperty(prop)),function(j, value) {
            var slot = query;
            if (value == undefined || value == "")
                return;
            if (parts.length === 1){
                slot[prop] = slot[prop] || [];
                slot[prop][j] = constructQueryPart(value);
                return;
            }
            slot[parts[0]]    = slot[parts[0]]    || [];
            slot[parts[0]][j] = slot[parts[0]][j] || {};
            slot = slot[parts[0]][j];
            $.each(parts.slice(1,parts.length-1), function(k,part) {
                slot[part] = slot[part] || {};
                slot = slot[part];
            });
            var lastPart = parts[parts.length-1];
            slot[lastPart] = constructQueryPart(value);
        })
    }
    if (typeless || !query['/type/object/type'])
        query['/type/object/type'] = ['/common/topic'];
    query = cleanup(query);
    entity['/rec_ui/recon_query'] = query;
    return query;

    function constructQueryPart(value) {
        if (value instanceof tEntity && !Arr.contains([undefined, "", "None", "None (merged"], value.getID()))
            return {"id":value.getID(), "name":value["/type/object/name"]}
        if (value['/rec_ui/id'] !== undefined)
            return $.makeArray(value["/type/object/name"])[0];
        return value;
    }

    /* Removes undefined values, nulls, empty lists, empty objects,
       and collapses singleton arrays down to their single values
       to better feed the reconciliation service.  */
    function cleanup(value) {
        switch(getType(value)){
        case "array":
            value = $.map(value, function(v) {
                return cleanup(v);
            });
            if (value.length === 0)
                return null;
            if (value.length === 1)
                return value[0];
            return value;
        case "object":
            var clone = {};
            for (var key in value) {
                var v = cleanup(value[key]);
                if (v === null || v === undefined)
                    continue;
                clone[key] = v;
            }
            if (isObjectEmpty(clone))
                return null;
            return clone;
        default:
            return value;
        }
    }
}

/**
 *  @param {tEntity} entity
 *  @param {function(tEntity)} callback
 *  @param {function(...)} onError
 *  @param {boolean=} typeless
 */
function getCandidates(entity, callback, onError,typeless) {
    function handler(results) {
        entity.reconResults = results;
        callback(entity);
    }
    var defaultLimit = 4;
    var limit = defaultLimit;
    if (entity.reconResults)
        limit = entity.reconResults.length * 2;
    if (!entity.typelessRecon && typeless){
        entity.typelessRecon = true;
        limit = defaultLimit;
    }
    var query = constructReconciliationQuery(entity,typeless);
    getJSON(reconciliation_url + "query?jsonp=?", {q:JSON.stringify(query), limit:limit}, handler, onError);
}

/** @param {tEntity} entity */
function autoReconcileResults(entity) {
    automaticQueue.shift();
    // no results, set to None:
    if(entity.reconResults.length == 0) {
        if (!entity.typelessRecon)
            getCandidates(entity,autoReconcileResults,function(){automaticQueue.shift();autoReconcile();},true);

        //If this entity is identical in name to several others then
        //it's not safe to automatically create new here, we need the
        //user to do internal reconciliation or duplicate entities could
        //be created
        if (internalReconciler.getRecGroup(entity).members.length > 1) {
            addToManualQueue(entity);
            return;
        }
        entity.reconcileWith("None", true);
        warn("No candidates found for the object:");
        warn(entity);
        addColumnRecCases(entity);
    }
    // match found:
    else if(entity.reconResults[0]["match"] == true) {
        var matchedResult = entity.reconResults[0];
        if (require_exact_name_match && !Arr.contains($.makeArray(entity['/type/object/name']),$.makeArray(matchedResult.name)[0])) {
            addToManualQueue(entity);
        }
        else {
            entity.reconcileWith(matchedResult.id, true);
            entity["/rec_ui/freebase_name"] = matchedResult.name;
            addColumnRecCases(entity);
        }
    }
    else
        addToManualQueue(entity)
    autoReconcile();
}<|endoftext|>


<commit_before>import path from "path";
import url from "url";
import log from "electron-log";
import settings from "electron-settings";
import { app, Menu } from "electron";
import { devMenuTemplate } from "./menu/dev_menu_template";
import { editMenuTemplate } from "./menu/edit_menu_template";
import createWindow from "./helpers/window";
import registerShortcuts from "./helpers/shortcuts";

// Special module holding environment variables which you declared
// in config/env_xxx.json file.
import env from "./env";

const setApplicationMenu = () => {
    const menus = [];
    if (env.name === "development") {
        menus.push(editMenuTemplate);
        menus.push(devMenuTemplate);
    }
    Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
};

// Save userData in separate folders for each environment.
// Thanks to this you can use production and development versions of the app
// on same machine like those are two separate apps.
if (env.name !== "production") {
    const userDataPath = app.getPath("userData");
    app.setPath("userData", `${userDataPath} (${env.name})`);
}

// setup the logger
log.transports.file.appName = "BunqDesktop";
log.transports.file.level = env.name === "development" ? "debug" : "warn";
log.transports.file.format = "{h}:{i}:{s}:{ms} {text}";
log.transports.file.file = `${__dirname}/${log.transports.file
    .appName}.log.txt`;

app.on("ready", () => {
    setApplicationMenu();

    settings.setPath(`${__dirname}/settings.json`);

    const USE_NATIVE_FRAME_STORED = settings.get("USE_NATIVE_FRAME");
    const USE_NATIVE_FRAME =
        USE_NATIVE_FRAME_STORED !== undefined &&
        USE_NATIVE_FRAME_STORED === true;

    const mainWindow = createWindow("main", {
        frame: USE_NATIVE_FRAME,
        webPreferences: { webSecurity: false },
        width: 1000,
        height: 800
    });

    mainWindow.loadURL(
        url.format({
            pathname: path.join(__dirname, "app.html"),
            protocol: "file:",
            slashes: true
        })
    );

    mainWindow.setMenu(null);
    registerShortcuts(mainWindow, app);

    if (env.name === "development") {
        mainWindow.openDevTools();
    } else {
        // remove the menu in production
        mainWindow.setMenu(null);
    }
});

app.on("window-all-closed", () => {
    app.quit();
});
<commit_msg><commit_after>import path from "path";
import url from "url";
import log from "electron-log";
import settings from "electron-settings";
import { app, Menu } from "electron";
import { devMenuTemplate } from "./menu/dev_menu_template";
import { editMenuTemplate } from "./menu/edit_menu_template";
import createWindow from "./helpers/window";
import registerShortcuts from "./helpers/shortcuts";

// Special module holding environment variables which you declared
// in config/env_xxx.json file.
import env from "./env";

const setApplicationMenu = () => {
    const menus = [];
    if (env.name === "development") {
        menus.push(editMenuTemplate);
        menus.push(devMenuTemplate);
    }
    Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
};

// Save userData in separate folders for each environment.
// Thanks to this you can use production and development versions of the app
// on same machine like those are two separate apps.
if (env.name !== "production") {
    const userDataPath = app.getPath("userData");
    app.setPath("userData", `${userDataPath} (${env.name})`);
}

// setup the logger
log.transports.file.appName = "BunqDesktop";
log.transports.file.level = env.name === "development" ? "debug" : "warn";
log.transports.file.format = "{h}:{i}:{s}:{ms} {text}";
log.transports.file.file = `${__dirname}/${log.transports.file
    .appName}.log.txt`;

app.on("ready", () => {
    setApplicationMenu();

    settings.setPath(`${__dirname}/settings.json`);

    const USE_NATIVE_FRAME_STORED = settings.get("USE_NATIVE_FRAME");
    const USE_NATIVE_FRAME =
        USE_NATIVE_FRAME_STORED !== undefined &&
        USE_NATIVE_FRAME_STORED === true;

    const mainWindow = createWindow("main", {
        frame: USE_NATIVE_FRAME,
        webPreferences: { webSecurity: false },
        width: 1000,
        height: 800
    });

    mainWindow.loadURL(
        url.format({
            pathname: path.join(__dirname, "app.html"),
            protocol: "file:",
            slashes: true
        })
    );

    registerShortcuts(mainWindow, app);

    if (env.name === "development") {
        mainWindow.openDevTools();
    } else {
        // remove the menu in production
        mainWindow.setMenu(null);
    }
});

app.on("window-all-closed", () => {
    app.quit();
});
<|endoftext|>


<commit_before>var isPlainObject = require('./isPlainObject');
var deepMerge = require('./deepMerge');
var backends = require('./backends');

var formatDeclarationCache;
var styleId;

function assertValidIdentifier(id) {
    if (!/^[a-z](?:[a-z0-9_-]*[a-z0-9])$/i.test(id)) {
        throw new Error('Invalid identifier: ' + id);
    }
}

function formatDeclaration(property, value) {
    assertValidIdentifier(property);
    if (!(property in formatDeclarationCache)) {
        formatDeclarationCache[property] = property.replace(/([A-Z])/g, '-$1');
    }
    if (Array.isArray(value)) {
        value = value.join(' ');
    }
    return formatDeclarationCache[property] + ':' + value + ';';
}

function formatDeclarations(selector, declaration, cb, media) {
    var result = '';

    var subRules = [];
    var property, value;
    for (property in declaration) {

        value = declaration[property];
        if (isPlainObject(value)) {
            subRules.push(property);
        }
        else {
            result += formatDeclaration(property, value);
        }

    }

    if (result) {

        result = selector + '{' + result + '}';

        if (media) {
            result = media + '{' + result + '}';
        }

        cb(result);

    }

    var i, l;
    for (i = 0, l = subRules.length; i < l; i++) {
        property = subRules[i];
        value = declaration[property];
        if (property.slice(0, 6) === 'media ') {
            formatDeclarations(selector, value, cb, '@' + property);
        }
        else {
            formatDeclarations(selector + ':' + property, value, cb);
        }
    }
}

function Style(name, declarations) {

    if (typeof name !== 'string') {
        declarations = name;
        name = 'style';
    }
    else {
        assertValidIdentifier(name);
    }

    if (!isPlainObject(declarations)) {
        throw new Error('Declarations should be a plain object');
    }

    styleId += 1;
    var id = name + '-' + styleId;

    var directParents =
        declarations.inherit ?
            Array.isArray(declarations.inherit) ?
                declarations.inherit :
                [declarations.inherit] :
            [];

    var this_ = this;
    var idTree = Object.create(null);
    var parents = [];
    function add(parent) {
        if (!idTree[parent._id]) {
            idTree[parent._id] = true;
            parent._children.push(this_);
            parents.push(parent);
        }
    }
    directParents.forEach(function (p) {
        p._parents.forEach(add);
        add(p);
    });

    this._id = id;
    this._parents = parents;
    this._children = [];
    this._ids = parents.map(function (p) { return p._id; });
    this._ids.push(id);
    this._injectedInBackend = false;
    this._declarations = deepMerge({}, declarations);
    delete this._declarations.inherit;
}

Style.prototype = {

    constructor: Style,

    toString: function () {
        this.inject();
        return this._ids.join(' ');
    },

    get injected() {
        return Boolean(this._injectedInBackend);
    },

    inject: function () {
        var backend = backends.current;
        if (this._injectedInBackend) {
            if (this._injectedInBackend === backend) return;
            else throw new Error('A style can\'t be injected in two backends');
        }

        this._parents.forEach(function (p) {
            p.inject();
        });

        var rules = [];
        this.iterRules(function (rule) {
            rules.push(rule);
        });

        backend.add(this._id, rules);
        this._injectedInBackend = backend;
    },

    remove: function () {
        if (!this._injectedInBackend) return;

        this._children.forEach(function (c) {
            c.remove();
        });

        this._injectedInBackend.remove(this._id);
        this._injectedInBackend = false;
    },

    iterRules: function (fn) {
        var selector = '.' + this._id;
        formatDeclarations(selector, this._declarations, fn);
    },

    render: function () {
        var result = '';
        this.iterRules(function (ruleSet) {
            result += ruleSet;
        });
        return result;
    },

    apply: function (element) {
        this.inject();
        element.classList.add.apply(element.classList, this._ids);
    },
};

function reset() {
    formatDeclarationCache = Object.create(null);
    styleId = 0;
}
reset();

/* istanbul ignore else */
if (process.env.NODE_ENV === 'test') {
    Style.__test_reset = reset;
}

module.exports = Style;

<commit_msg>Cleaner Style reset<commit_after>var isPlainObject = require('./isPlainObject');
var deepMerge = require('./deepMerge');
var backends = require('./backends');

var formatDeclarationCache = Object.create(null);
var styleId = 0;

/* istanbul ignore else */
if (process.env.NODE_ENV === 'test') {
    Style.__test_reset = function () {
        formatDeclarationCache = Object.create(null);
        styleId = 0;
    };
}

function assertValidIdentifier(id) {
    if (!/^[a-z](?:[a-z0-9_-]*[a-z0-9])$/i.test(id)) {
        throw new Error('Invalid identifier: ' + id);
    }
}

function formatDeclaration(property, value) {
    assertValidIdentifier(property);
    if (!(property in formatDeclarationCache)) {
        formatDeclarationCache[property] = property.replace(/([A-Z])/g, '-$1');
    }
    if (Array.isArray(value)) {
        value = value.join(' ');
    }
    return formatDeclarationCache[property] + ':' + value + ';';
}

function formatDeclarations(selector, declaration, cb, media) {
    var result = '';

    var subRules = [];
    var property, value;
    for (property in declaration) {

        value = declaration[property];
        if (isPlainObject(value)) {
            subRules.push(property);
        }
        else {
            result += formatDeclaration(property, value);
        }

    }

    if (result) {

        result = selector + '{' + result + '}';

        if (media) {
            result = media + '{' + result + '}';
        }

        cb(result);

    }

    var i, l;
    for (i = 0, l = subRules.length; i < l; i++) {
        property = subRules[i];
        value = declaration[property];
        if (property.slice(0, 6) === 'media ') {
            formatDeclarations(selector, value, cb, '@' + property);
        }
        else {
            formatDeclarations(selector + ':' + property, value, cb);
        }
    }
}

function Style(name, declarations) {

    if (typeof name !== 'string') {
        declarations = name;
        name = 'style';
    }
    else {
        assertValidIdentifier(name);
    }

    if (!isPlainObject(declarations)) {
        throw new Error('Declarations should be a plain object');
    }

    styleId += 1;
    var id = name + '-' + styleId;

    var directParents =
        declarations.inherit ?
            Array.isArray(declarations.inherit) ?
                declarations.inherit :
                [declarations.inherit] :
            [];

    var this_ = this;
    var idTree = Object.create(null);
    var parents = [];
    function add(parent) {
        if (!idTree[parent._id]) {
            idTree[parent._id] = true;
            parent._children.push(this_);
            parents.push(parent);
        }
    }
    directParents.forEach(function (p) {
        p._parents.forEach(add);
        add(p);
    });

    this._id = id;
    this._parents = parents;
    this._children = [];
    this._ids = parents.map(function (p) { return p._id; });
    this._ids.push(id);
    this._injectedInBackend = false;
    this._declarations = deepMerge({}, declarations);
    delete this._declarations.inherit;
}

Style.prototype = {

    constructor: Style,

    toString: function () {
        this.inject();
        return this._ids.join(' ');
    },

    get injected() {
        return Boolean(this._injectedInBackend);
    },

    inject: function () {
        var backend = backends.current;
        if (this._injectedInBackend) {
            if (this._injectedInBackend === backend) return;
            else throw new Error('A style can\'t be injected in two backends');
        }

        this._parents.forEach(function (p) {
            p.inject();
        });

        var rules = [];
        this.iterRules(function (rule) {
            rules.push(rule);
        });

        backend.add(this._id, rules);
        this._injectedInBackend = backend;
    },

    remove: function () {
        if (!this._injectedInBackend) return;

        this._children.forEach(function (c) {
            c.remove();
        });

        this._injectedInBackend.remove(this._id);
        this._injectedInBackend = false;
    },

    iterRules: function (fn) {
        var selector = '.' + this._id;
        formatDeclarations(selector, this._declarations, fn);
    },

    render: function () {
        var result = '';
        this.iterRules(function (ruleSet) {
            result += ruleSet;
        });
        return result;
    },

    apply: function (element) {
        this.inject();
        element.classList.add.apply(element.classList, this._ids);
    },
};

module.exports = Style;

<|endoftext|>


<commit_before>/**
 * @fileoverview Comma spacing - validates spacing before and after comma
 * @author Vignesh Anand aka vegetableman.
 */
"use strict";

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        type: "layout",

        docs: {
            description: "enforce consistent spacing before and after commas",
            category: "Stylistic Issues",
            recommended: false,
            url: "https://eslint.org/docs/rules/comma-spacing"
        },

        fixable: "whitespace",

        schema: [
            {
                type: "object",
                properties: {
                    before: {
                        type: "boolean",
                        default: false
                    },
                    after: {
                        type: "boolean",
                        default: true
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            missing: "A space is required {{loc}} ','.",
            unexpected: "There should be no space {{loc}} ','."
        }
    },

    create(context) {

        const sourceCode = context.getSourceCode();
        const tokensAndComments = sourceCode.tokensAndComments;

        const options = {
            before: context.options[0] ? context.options[0].before : false,
            after: context.options[0] ? context.options[0].after : true
        };

        //--------------------------------------------------------------------------
        // Helpers
        //--------------------------------------------------------------------------

        // list of comma tokens to ignore for the check of leading whitespace
        const commaTokensToIgnore = [];

        /**
         * Reports a spacing error with an appropriate message.
         * @param {ASTNode} node The binary expression node to report.
         * @param {string} loc Is the error "before" or "after" the comma?
         * @param {ASTNode} otherNode The node at the left or right of `node`
         * @returns {void}
         * @private
         */
        function report(node, loc, otherNode) {
            context.report({
                node,
                fix(fixer) {
                    if (options[loc]) {
                        if (loc === "before") {
                            return fixer.insertTextBefore(node, " ");
                        }
                        return fixer.insertTextAfter(node, " ");

                    }
                    let start, end;
                    const newText = "";

                    if (loc === "before") {
                        start = otherNode.range[1];
                        end = node.range[0];
                    } else {
                        start = node.range[1];
                        end = otherNode.range[0];
                    }

                    return fixer.replaceTextRange([start, end], newText);

                },
                messageId: options[loc] ? "missing" : "unexpected",
                data: {
                    loc
                }
            });
        }

        /**
         * Validates the spacing around a comma token.
         * @param {Object} tokens The tokens to be validated.
         * @param {Token} tokens.comma The token representing the comma.
         * @param {Token} [tokens.left] The last token before the comma.
         * @param {Token} [tokens.right] The first token after the comma.
         * @param {Token|ASTNode} reportItem The item to use when reporting an error.
         * @returns {void}
         * @private
         */
        function validateCommaItemSpacing(tokens, reportItem) {
            if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
                    (options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
            ) {
                report(reportItem, "before", tokens.left);
            }

            if (tokens.right && astUtils.isClosingParenToken(tokens.right)) {
                return;
            }

            if (tokens.right && !options.after && tokens.right.type === "Line") {
                return;
            }

            if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
                    (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
            ) {
                report(reportItem, "after", tokens.right);
            }
        }

        /**
         * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
         * @param {ASTNode} node An ArrayExpression or ArrayPattern node.
         * @returns {void}
         */
        function addNullElementsToIgnoreList(node) {
            let previousToken = sourceCode.getFirstToken(node);

            node.elements.forEach(element => {
                let token;

                if (element === null) {
                    token = sourceCode.getTokenAfter(previousToken);

                    if (astUtils.isCommaToken(token)) {
                        commaTokensToIgnore.push(token);
                    }
                } else {
                    token = sourceCode.getTokenAfter(element);
                }

                previousToken = token;
            });
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {
            "Program:exit"() {
                tokensAndComments.forEach((token, i) => {

                    if (!astUtils.isCommaToken(token)) {
                        return;
                    }

                    if (token && token.type === "JSXText") {
                        return;
                    }

                    const previousToken = tokensAndComments[i - 1];
                    const nextToken = tokensAndComments[i + 1];

                    validateCommaItemSpacing({
                        comma: token,
                        left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
                        right: astUtils.isCommaToken(nextToken) ? null : nextToken
                    }, token);
                });
            },
            ArrayExpression: addNullElementsToIgnoreList,
            ArrayPattern: addNullElementsToIgnoreList

        };

    }
};
<commit_msg>Chore: use includes instead of indexOf (#14607)<commit_after>/**
 * @fileoverview Comma spacing - validates spacing before and after comma
 * @author Vignesh Anand aka vegetableman.
 */
"use strict";

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        type: "layout",

        docs: {
            description: "enforce consistent spacing before and after commas",
            category: "Stylistic Issues",
            recommended: false,
            url: "https://eslint.org/docs/rules/comma-spacing"
        },

        fixable: "whitespace",

        schema: [
            {
                type: "object",
                properties: {
                    before: {
                        type: "boolean",
                        default: false
                    },
                    after: {
                        type: "boolean",
                        default: true
                    }
                },
                additionalProperties: false
            }
        ],

        messages: {
            missing: "A space is required {{loc}} ','.",
            unexpected: "There should be no space {{loc}} ','."
        }
    },

    create(context) {

        const sourceCode = context.getSourceCode();
        const tokensAndComments = sourceCode.tokensAndComments;

        const options = {
            before: context.options[0] ? context.options[0].before : false,
            after: context.options[0] ? context.options[0].after : true
        };

        //--------------------------------------------------------------------------
        // Helpers
        //--------------------------------------------------------------------------

        // list of comma tokens to ignore for the check of leading whitespace
        const commaTokensToIgnore = [];

        /**
         * Reports a spacing error with an appropriate message.
         * @param {ASTNode} node The binary expression node to report.
         * @param {string} loc Is the error "before" or "after" the comma?
         * @param {ASTNode} otherNode The node at the left or right of `node`
         * @returns {void}
         * @private
         */
        function report(node, loc, otherNode) {
            context.report({
                node,
                fix(fixer) {
                    if (options[loc]) {
                        if (loc === "before") {
                            return fixer.insertTextBefore(node, " ");
                        }
                        return fixer.insertTextAfter(node, " ");

                    }
                    let start, end;
                    const newText = "";

                    if (loc === "before") {
                        start = otherNode.range[1];
                        end = node.range[0];
                    } else {
                        start = node.range[1];
                        end = otherNode.range[0];
                    }

                    return fixer.replaceTextRange([start, end], newText);

                },
                messageId: options[loc] ? "missing" : "unexpected",
                data: {
                    loc
                }
            });
        }

        /**
         * Validates the spacing around a comma token.
         * @param {Object} tokens The tokens to be validated.
         * @param {Token} tokens.comma The token representing the comma.
         * @param {Token} [tokens.left] The last token before the comma.
         * @param {Token} [tokens.right] The first token after the comma.
         * @param {Token|ASTNode} reportItem The item to use when reporting an error.
         * @returns {void}
         * @private
         */
        function validateCommaItemSpacing(tokens, reportItem) {
            if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
                    (options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
            ) {
                report(reportItem, "before", tokens.left);
            }

            if (tokens.right && astUtils.isClosingParenToken(tokens.right)) {
                return;
            }

            if (tokens.right && !options.after && tokens.right.type === "Line") {
                return;
            }

            if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
                    (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
            ) {
                report(reportItem, "after", tokens.right);
            }
        }

        /**
         * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
         * @param {ASTNode} node An ArrayExpression or ArrayPattern node.
         * @returns {void}
         */
        function addNullElementsToIgnoreList(node) {
            let previousToken = sourceCode.getFirstToken(node);

            node.elements.forEach(element => {
                let token;

                if (element === null) {
                    token = sourceCode.getTokenAfter(previousToken);

                    if (astUtils.isCommaToken(token)) {
                        commaTokensToIgnore.push(token);
                    }
                } else {
                    token = sourceCode.getTokenAfter(element);
                }

                previousToken = token;
            });
        }

        //--------------------------------------------------------------------------
        // Public
        //--------------------------------------------------------------------------

        return {
            "Program:exit"() {
                tokensAndComments.forEach((token, i) => {

                    if (!astUtils.isCommaToken(token)) {
                        return;
                    }

                    if (token && token.type === "JSXText") {
                        return;
                    }

                    const previousToken = tokensAndComments[i - 1];
                    const nextToken = tokensAndComments[i + 1];

                    validateCommaItemSpacing({
                        comma: token,
                        left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.includes(token) ? null : previousToken,
                        right: astUtils.isCommaToken(nextToken) ? null : nextToken
                    }, token);
                });
            },
            ArrayExpression: addNullElementsToIgnoreList,
            ArrayPattern: addNullElementsToIgnoreList

        };

    }
};
<|endoftext|>