<commit_before>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  Slots are observable values which can change over time.

  Slots are simple single-value Model-View-Controller Models, but since
  another meaning of 'Model' is already heavily used in FOAM, Slot is
  used to avoid overloading the term.

Types of Slots include:

PropertySlot

ConstantSlot

ExpressionSlot

*/
foam.CLASS({
  package: 'foam.core',
  name: 'Slot', // ???: Rename AbstractSlot or make an Interface
  extends: null,

  methods: [
    /**
    */
    function dot(name) {
      return foam.core.internal.SubSlot.create({
        parent: this,
        name:   name
      });
    },

    // TODO: remove when all code ported
    function link(other) {
      console.warn('deprecated use of link(), use linkFrom() instead');
      return this.linkFrom(other);
    },

    /**
      Link two Slots together, setting both to other's value.
      Returns a Destroyable which can be used to break the link.
    */
    function linkFrom(other) {
      var sub1 = this.follow(other);
      var sub2 = other.follow(this);

      return {
        destroy: function() {
          sub1 && sub1.destroy();
          sub2 && sub2.destroy();
          sub1 = sub2 = null;
        }
      };
    },

    function linkTo(other) {
      return other.linkFrom(this);
    },

    /**
      Have this Slot dynamically follow other's value.
      Returns a Destroyable which can be used to cancel the binding.
    */
    function follow(other) {
      var self = this;
      var l = function() { self.set(other.get()); };
      l();
      return other.sub(l);
    },

    /**
     * Maps values from one model to another.
     * @param f maps values from srcValue to dstValue
     */
    function mapFrom(other, f) {
      var self = this;
      var l = function() { self.set(f(other.get())); };
      l();
      return other.sub(l);
    },

    function mapTo(other, f) {
      return other.mapFrom(this, f);
    },

    function map(f) {
      return foam.core.ExpressionSlot.create({code: f, args: [this]});
    },

    /**
     * Relate to another Slot.
     * @param f maps from this to other
     * @param fprime maps other to this
     */
    function relateTo(other, f, fPrime) {
      var self     = this;
      var feedback = false;
      var sub      = foam.core.FObject.create();
      var l1 = function() {
        if ( feedback ) return;
        feedback = true;
        other.set(f(self.get()));
        feedback = false;
      };
      var l2 = function() {
        if ( feedback ) return;
        feedback = true;
        self.set(fPrime(other.get()));
        feedback = false;
      };

      sub.onDestroy(this.sub(l1));
      sub.onDestroy(other.sub(l2));

      l1();

      return sub;
    },

    function relateFrom(other, f, fPrime) {
      return other.relateTo(this, fPrime, f);
    }
  ]
});

/**
  PropertySlot represents object properties as Slots.
  Created with calling obj.prop$ or obj.slot('prop').
  For internal use only.
 */
foam.CLASS({
  package: 'foam.core.internal',
  name: 'PropertySlot',
  extends: 'foam.core.Slot',

  methods: [
    function initArgs() { },

    function get() {
      return this.prop.get(this.obj);
    },

    function set(value) {
      return this.prop.set(this.obj, value);
    },

    function getPrev() {
      return this.oldValue;
    },

    function setPrev(value) {
      return this.oldValue = value;
    },

    function sub(l) {
      return this.obj.sub('propertyChange', this.prop.name, l);
    },

    function unsub(l) {
      this.obj.unsub('propertyChange', this.prop.name, l);
    },

    function isDefined() {
      return this.obj.hasOwnProperty(this.prop.name);
    },

    function clear() {
      this.obj.clearProperty(this.prop.name);
    },

    function toString() {
      return 'PropertySlot(' + this.prop.name + ')';
    }
  ]
});

/**
  For internal use only.
 */
foam.CLASS({
  package: 'foam.core.internal',
  name: 'SubSlot',
  implements: [ 'foam.core.Slot' ],

  properties: [
    'parent', // parent slot, not parent object
    'name',
    'value',
    'prevSub'
  ],

  methods: [
    function init() {
      this.parent.sub(this.parentChange);
      this.parentChange();
    },

    function get() {
      return this.parent.get()[this.name];
    },

    function set(value) {
      this.parent.get()[this.name] = value;
    },

    /** Needed? **/
    function getPrev() {
      debugger;
      return this.oldValue;
    },

    /** Needed? **/
    function setPrev(value) {
      debugger;
      return this.oldValue = value;
    },

    function sub(l) {
      return this.SUPER('propertyChange', 'value', l);
    },

    function unsub(l) {
      this.SUPER('propertyChange', 'value', l);
    },

    function isDefined() {
      return this.parent.get().hasOwnProperty(this.name);
    },

    function clear() {
      this.parent.get().clearProperty(this.name);
    },

    function toString() {
      return 'SubSlot(' + this.parent + ',' + this.name + ')';
    }
  ],

  listeners: [
    function parentChange() {
      this.prevSub && this.prevSub.destroy();
      this.prevSub = this.parent.get().sub('propertyChange', this.name, this.valueChange);
      this.valueChange();
    },

    function valueChange() {
      var parentValue = this.parent.get();
      this.value = parentValue ? parentValue[this.name] : undefined;
    }
  ]
});

/** Tracks dependencies for a dynamic function and invalidates is they change. */
foam.CLASS({
  package: 'foam.core',
  name: 'ConstantSlot',
  implements: [ 'foam.core.Slot' ],

  properties: [
    {
      name: 'value',
      getter: function() { return this.value_; },
      setter: function() {}
    }
  ],

  methods: [
    function initArgs(args) { this.value_ = args && args.value; },

    function get() { return this.value; },

    function set() { /* nop */ },

    function sub(l) { /* nop */ },

    function unsub(l) { /* nop */ }
  ]
});

/**
  Tracks dependencies for a dynamic function and invalidates if they change.

foam.CLASS({name: 'Person', properties: ['fname', 'lname']});
var p = Person.create({fname: 'John', lname: 'Smith'});
var e = foam.core.ExpressionSlot.create({
  args: [ p.fname$, p.lname$ ],
  code: function(f, l) { return f + ' ' + l; }
});
log(e.get());
e.sub(log);
p.fname = 'Steve';
p.lname = 'Jones';
log(e.get());
Output:
 > John Smith
 > [object Object] propertyChange value [object Object]
 > [object Object] propertyChange value [object Object]
 > Steve Jones

var p = foam.CLASS({name: 'Person', properties: [ 'f', 'l' ]}).create({f:'John', l: 'Doe'});
var e = foam.core.ExpressionSlot.create({
  obj: p,
  code: function(f, l) { return f + ' ' + l; }
});

*/
foam.CLASS({
  package: 'foam.core',
  name: 'ExpressionSlot',
  implements: [ 'foam.core.Slot' ],

  properties: [
    'obj',
    'code',
    {
      name: 'args',
      expression: function(obj) {
        this.assert(obj, 'ExpressionSlot: "obj" or "args" required.');

        var args = foam.Function.formalArgs(this.code);
        for ( var i = 0 ; i < args.length ; i++ ) {
          args[i] = obj.slot(args[i]);
        }

        this.invalidate();
        this.subToArgs_(args);

        return args;
      },
      postSet: function(_, args) {
        this.subToArgs_(args);
      }
    },
    {
      name: 'value',
      factory: function() {
        return this.code.apply(this, this.args.map(function(a) {
          return a.get();
        }));
      }
    },
    'cleanup_', // destroyable to cleanup old subs when obj changes
  ],

  methods: [
    function init() { this.onDestroy(this.cleanup); },

    function get() { return this.value; },

    function set() { /* nop */ },

    function sub(l) {
      return arguments.length === 1 ?
        this.SUPER('propertyChange', 'value', l) :
        this.SUPER.apply(this,arguments);
    },

    function unsub(l) {
      return arguments.length === 1 ?
        this.SUPER('propertyChange', 'value', l) :
        this.SUPER.apply(this,arguments);
    },

    function subToArgs_(args) {
      this.cleanup();

      var cleanup = foam.core.FObject.create();

      for ( var i = 0 ; i < args.length ; i++ ) {
        cleanup.onDestroy(args[i].sub(this.invalidate));
      }

      this.cleanup_ = cleanup;
    }
  ],

  listeners: [
    function cleanup() { this.cleanup_ && this.cleanup_.destroy(); },
    function invalidate() { this.clearProperty('value'); }
  ]
});
<commit_msg>ExpressionSlot runs code with 'obj' this if defined.<commit_after>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  Slots are observable values which can change over time.

  Slots are simple single-value Model-View-Controller Models, but since
  another meaning of 'Model' is already heavily used in FOAM, Slot is
  used to avoid overloading the term.

Types of Slots include:

PropertySlot

ConstantSlot

ExpressionSlot

*/
foam.CLASS({
  package: 'foam.core',
  name: 'Slot', // ???: Rename AbstractSlot or make an Interface
  extends: null,

  methods: [
    /**
    */
    function dot(name) {
      return foam.core.internal.SubSlot.create({
        parent: this,
        name:   name
      });
    },

    // TODO: remove when all code ported
    function link(other) {
      console.warn('deprecated use of link(), use linkFrom() instead');
      return this.linkFrom(other);
    },

    /**
      Link two Slots together, setting both to other's value.
      Returns a Destroyable which can be used to break the link.
    */
    function linkFrom(other) {
      var sub1 = this.follow(other);
      var sub2 = other.follow(this);

      return {
        destroy: function() {
          sub1 && sub1.destroy();
          sub2 && sub2.destroy();
          sub1 = sub2 = null;
        }
      };
    },

    function linkTo(other) {
      return other.linkFrom(this);
    },

    /**
      Have this Slot dynamically follow other's value.
      Returns a Destroyable which can be used to cancel the binding.
    */
    function follow(other) {
      var self = this;
      var l = function() { self.set(other.get()); };
      l();
      return other.sub(l);
    },

    /**
     * Maps values from one model to another.
     * @param f maps values from srcValue to dstValue
     */
    function mapFrom(other, f) {
      var self = this;
      var l = function() { self.set(f(other.get())); };
      l();
      return other.sub(l);
    },

    function mapTo(other, f) {
      return other.mapFrom(this, f);
    },

    function map(f) {
      return foam.core.ExpressionSlot.create({code: f, args: [this]});
    },

    /**
     * Relate to another Slot.
     * @param f maps from this to other
     * @param fprime maps other to this
     */
    function relateTo(other, f, fPrime) {
      var self     = this;
      var feedback = false;
      var sub      = foam.core.FObject.create();
      var l1 = function() {
        if ( feedback ) return;
        feedback = true;
        other.set(f(self.get()));
        feedback = false;
      };
      var l2 = function() {
        if ( feedback ) return;
        feedback = true;
        self.set(fPrime(other.get()));
        feedback = false;
      };

      sub.onDestroy(this.sub(l1));
      sub.onDestroy(other.sub(l2));

      l1();

      return sub;
    },

    function relateFrom(other, f, fPrime) {
      return other.relateTo(this, fPrime, f);
    }
  ]
});

/**
  PropertySlot represents object properties as Slots.
  Created with calling obj.prop$ or obj.slot('prop').
  For internal use only.
 */
foam.CLASS({
  package: 'foam.core.internal',
  name: 'PropertySlot',
  extends: 'foam.core.Slot',

  methods: [
    function initArgs() { },

    function get() {
      return this.prop.get(this.obj);
    },

    function set(value) {
      return this.prop.set(this.obj, value);
    },

    function getPrev() {
      return this.oldValue;
    },

    function setPrev(value) {
      return this.oldValue = value;
    },

    function sub(l) {
      return this.obj.sub('propertyChange', this.prop.name, l);
    },

    function unsub(l) {
      this.obj.unsub('propertyChange', this.prop.name, l);
    },

    function isDefined() {
      return this.obj.hasOwnProperty(this.prop.name);
    },

    function clear() {
      this.obj.clearProperty(this.prop.name);
    },

    function toString() {
      return 'PropertySlot(' + this.prop.name + ')';
    }
  ]
});

/**
  For internal use only.
 */
foam.CLASS({
  package: 'foam.core.internal',
  name: 'SubSlot',
  implements: [ 'foam.core.Slot' ],

  properties: [
    'parent', // parent slot, not parent object
    'name',
    'value',
    'prevSub'
  ],

  methods: [
    function init() {
      this.parent.sub(this.parentChange);
      this.parentChange();
    },

    function get() {
      return this.parent.get()[this.name];
    },

    function set(value) {
      this.parent.get()[this.name] = value;
    },

    /** Needed? **/
    function getPrev() {
      debugger;
      return this.oldValue;
    },

    /** Needed? **/
    function setPrev(value) {
      debugger;
      return this.oldValue = value;
    },

    function sub(l) {
      return this.SUPER('propertyChange', 'value', l);
    },

    function unsub(l) {
      this.SUPER('propertyChange', 'value', l);
    },

    function isDefined() {
      return this.parent.get().hasOwnProperty(this.name);
    },

    function clear() {
      this.parent.get().clearProperty(this.name);
    },

    function toString() {
      return 'SubSlot(' + this.parent + ',' + this.name + ')';
    }
  ],

  listeners: [
    function parentChange() {
      this.prevSub && this.prevSub.destroy();
      this.prevSub = this.parent.get().sub('propertyChange', this.name, this.valueChange);
      this.valueChange();
    },

    function valueChange() {
      var parentValue = this.parent.get();
      this.value = parentValue ? parentValue[this.name] : undefined;
    }
  ]
});

/** Tracks dependencies for a dynamic function and invalidates is they change. */
foam.CLASS({
  package: 'foam.core',
  name: 'ConstantSlot',
  implements: [ 'foam.core.Slot' ],

  properties: [
    {
      name: 'value',
      getter: function() { return this.value_; },
      setter: function() {}
    }
  ],

  methods: [
    function initArgs(args) { this.value_ = args && args.value; },

    function get() { return this.value; },

    function set() { /* nop */ },

    function sub(l) { /* nop */ },

    function unsub(l) { /* nop */ }
  ]
});

/**
  Tracks dependencies for a dynamic function and invalidates if they change.

foam.CLASS({name: 'Person', properties: ['fname', 'lname']});
var p = Person.create({fname: 'John', lname: 'Smith'});
var e = foam.core.ExpressionSlot.create({
  args: [ p.fname$, p.lname$ ],
  code: function(f, l) { return f + ' ' + l; }
});
log(e.get());
e.sub(log);
p.fname = 'Steve';
p.lname = 'Jones';
log(e.get());
Output:
 > John Smith
 > [object Object] propertyChange value [object Object]
 > [object Object] propertyChange value [object Object]
 > Steve Jones

var p = foam.CLASS({name: 'Person', properties: [ 'f', 'l' ]}).create({f:'John', l: 'Doe'});
var e = foam.core.ExpressionSlot.create({
  obj: p,
  code: function(f, l) { return f + ' ' + l; }
});

*/
foam.CLASS({
  package: 'foam.core',
  name: 'ExpressionSlot',
  implements: [ 'foam.core.Slot' ],

  properties: [
    'obj',
    'code',
    {
      name: 'args',
      expression: function(obj) {
        this.assert(obj, 'ExpressionSlot: "obj" or "args" required.');

        var args = foam.Function.formalArgs(this.code);
        for ( var i = 0 ; i < args.length ; i++ ) {
          args[i] = obj.slot(args[i]);
        }

        this.invalidate();
        this.subToArgs_(args);

        return args;
      },
      postSet: function(_, args) {
        this.subToArgs_(args);
      }
    },
    {
      name: 'value',
      factory: function() {
        return this.code.apply(this.obj || this, this.args.map(function(a) {
          return a.get();
        }));
      }
    },
    'cleanup_', // destroyable to cleanup old subs when obj changes
  ],

  methods: [
    function init() { this.onDestroy(this.cleanup); },

    function get() { return this.value; },

    function set() { /* nop */ },

    function sub(l) {
      return arguments.length === 1 ?
        this.SUPER('propertyChange', 'value', l) :
        this.SUPER.apply(this,arguments);
    },

    function unsub(l) {
      return arguments.length === 1 ?
        this.SUPER('propertyChange', 'value', l) :
        this.SUPER.apply(this,arguments);
    },

    function subToArgs_(args) {
      this.cleanup();

      var cleanup = foam.core.FObject.create();

      for ( var i = 0 ; i < args.length ; i++ ) {
        cleanup.onDestroy(args[i].sub(this.invalidate));
      }

      this.cleanup_ = cleanup;
    }
  ],

  listeners: [
    function cleanup() { this.cleanup_ && this.cleanup_.destroy(); },
    function invalidate() { this.clearProperty('value'); }
  ]
});
<|endoftext|>


<commit_before>'use strict'

const Fragment = require('./fragment')
const util = require('./util')
const ops = require('./ops')

module.exports = class Row {
  constructor(doc, parent) {
    Fragment.prototype._init.call(this, doc, parent)

    this._pending = []
    this._rotated = 0
    this._y = 0
    this._startX = 0
    this._endY = null
    // TODO: do not hardcode widths
    this._widths = [200, 200, 200, 200]
    this._columns = 0
  }

  async _pageBreak(level) {
    this._pending.splice(this._pending.length - 2, 0 , this._pending.shift())

    // -1 because of row._end
    // -1 because we want a page break ont he last column
    // = -2
    if (this._rotated < this._pending.length - 2) {
      this._rotated++

      this.nextColumn()

      return false
    } else {
      this._rotated = 0
      await this._parent._pageBreak(level + 1)

      this._y = this._cursor.y
      this._cursor.startX = this._startX
    }
  }

  nextColumn() {
    // this._cursor.startX += 200
    // this._cursor.x = this._cursor.startX
    this._cursor.y = this._y
  }

  async _end() {
    // reset cursor
    this._cursor.startX = this._cursor.x = this._startX
    this._cursor.y = this._endY
  }

  end() {
    return Fragment.prototype.end.call(this)
  }

  async _start(text, opts) {
    if (!this._doc._currentPage) {
      await this._doc._startPage()
    }

    this._pending.forEach(p => {
      if (Array.isArray(p)) {
        p.push(() => {
          if (this._endY === null || this._cursor.y < this._endY) {
            this._endY = this._cursor.y
          }
          this.nextColumn()
          return Promise.resolve()
        })
      }
    })

    this._y = this._cursor.y
    this._startX = this._cursor.startX
  }

  startCell(text, opts) {
    if (text !== null && typeof text === 'object') {
      opts = text
      text = ''
    }
    if (!opts || typeof opts !== 'object') {
      opts = {}
    }

    const Cell = require('./cell')

    const column = this._columns++
    const ctx = new Cell(this._doc, this, Object.assign({}, opts, {
      width: this._widths[column]
    }))

    ctx._cursor.startX += this._widths[column - 1] || 0
    ctx._pending.push(() => ctx._start())

    this._pending.push(ctx._pending)

    return ctx
  }
}
<commit_msg>align height of row cells<commit_after>'use strict'

const Fragment = require('./fragment')
const util = require('./util')
const ops = require('./ops')

module.exports = class Row {
  constructor(doc, parent) {
    Fragment.prototype._init.call(this, doc, parent)

    this._pending = []
    this._rotated = 0
    this._y = 0
    this._startX = 0
    this._endY = null
    // TODO: do not hardcode widths
    this._widths = [200, 200, 200, 200]
    this._columns = 0
  }

  async _pageBreak(level) {
    this._pending.splice(this._pending.length - 2, 0 , this._pending.shift())

    // -1 because of row._end
    // -1 because we want a page break ont he last column
    // = -2
    if (this._rotated < this._pending.length - 2) {
      this._rotated++

      this.nextColumn()

      return false
    } else {
      this._rotated = 0
      await this._parent._pageBreak(level + 1)

      this._y = this._cursor.y
      this._cursor.startX = this._startX
    }
  }

  nextColumn() {
    // this._cursor.startX += 200
    // this._cursor.x = this._cursor.startX
    this._cursor.y = this._y
  }

  async _end() {
    // reset cursor
    this._cursor.startX = this._cursor.x = this._startX
    this._cursor.y = this._endY
  }

  end() {
    return Fragment.prototype.end.call(this)
  }

  async _start(text, opts) {
    if (!this._doc._currentPage) {
      await this._doc._startPage()
    }

    this._pending.forEach(p => {
      if (Array.isArray(p)) {
        p.push(() => {
          if (this._endY === null || this._cursor.y < this._endY) {
            this._endY = this._cursor.y
          }
          this.nextColumn()
          return Promise.resolve()
        })
      }
    })

    this._y = this._cursor.y
    this._startX = this._cursor.startX
  }

  startCell(text, opts) {
    if (text !== null && typeof text === 'object') {
      opts = text
      text = ''
    }
    if (!opts || typeof opts !== 'object') {
      opts = {}
    }

    const Cell = require('./cell')

    const column = this._columns++
    const ctx = new Cell(this._doc, this, Object.assign({}, opts, {
      width: this._widths[column]
    }))

    ctx._cursor.startX += this._widths[column - 1] || 0
    ctx._pending.push(() => ctx._start())

    // override cell's _end to take align all the row's cells bottom
    ctx._end = endCell.bind(ctx, this)

    this._pending.push(ctx._pending)

    return ctx
  }
}

async function endCell(row) {
  // apply bottom padding (but only for the most inner cell or or last background)
  this._cursor.y -= this.padding

  // create final createBackground
  // await this._createBackground()
  row._pending.splice(row._pending.length - 1, 0, () => {
    this._cursor.y = row._endY
    return this._createBackground()
  })
}
<|endoftext|>


<commit_before>BrowserPolicy.content.allowOriginForAll("*.googleapis.com");
BrowserPolicy.content.allowOriginForAll("*.gstatic.com");
BrowserPolicy.content.allowOriginForAll("*.bootstrapcdn.com");
BrowserPolicy.content.allowOriginForAll("*.box.com");
BrowserPolicy.content.allowOriginForAll("*.boxcdn.net");

BrowserPolicy.content.allowFontDataUrl();
<commit_msg>Updated security policy to allow Box webapp integration to put app in a frame<commit_after>BrowserPolicy.content.allowOriginForAll('*.googleapis.com');
BrowserPolicy.content.allowOriginForAll('*.gstatic.com');
BrowserPolicy.content.allowOriginForAll('*.bootstrapcdn.com');
BrowserPolicy.content.allowOriginForAll('*.box.com');
BrowserPolicy.content.allowOriginForAll('*.boxcdn.net');

BrowserPolicy.content.allowFontDataUrl();

BrowserPolicy.framing.restrictToOrigin('*.box.com');
<|endoftext|>


<commit_before>var Game;

(function() {

/*
 * Game
 * ====
 *
 * The main Game constructor
 */

Game = function(options, plugins, categories) {
    if(!options) {
        var options = {};
        console.warn(warnings.noOptions);
    }
    if(!plugins) {
        var plugins = [];
        console.warn(warnings.noPluginsConstructor);
    }

    this.options    = options;

    this.Assets     = null;
    this.Camera     = null;
    this.Colliders  = null;
    this.Draw       = null;
    this.Sound      = null;
    this.World      = null;
    this.Categories = new Categories(this, categories);
    this.Plugins    = new Plugins(this, plugins);

    this.initTime   = null;
    this.startTime  = null;
    this.pauseTime  = null;
    this.stopTime   = null;

    this.playing    = false;
    this.timer      = null;
    this.frames     = 0;

    return this;
}

Game.prototype.start = function() {
    if(!this.playing) {
        this.startTime = this.Utils.time();
        if(!this.initTime) {
            this.init();
        }
        this.timer = this.requestAnimationFrame(this.Loop);
        this.Loop();
    }

    return this;
}

Game.prototype.pause = function() {
    if(this.playing) {
        this.pauseTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
    }
}

Game.prototype.stop = function() {
    if(this.playing) {
        this.stopTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
        this.reset();
    }

    return this;
}

Game.prototype.init = function() {
    // Throw an Error if a plugin is missing.
    // If Game[plugin] is null, that plugin is missing
    if(!this.Assets || !this.Camera || !this.Colliders || !this.Draw || !this.Sound || !this.World) {
        throw new Error("Missing plugin.");
    }
}

Game.prototype.Loop = function() {
    this.frames++;

    this.World.Update(this);
    this.Camera.Update(this);
    this.World.Draw(this);

    return this;
}

Game.prototype.Utils = {
    capitalize: function(str) {
        return str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
    },
    toRadians: function(deg) {
        return (deg * Math.PI) / 180;
    },
    toDegrees: function(rad) {
        return (rad * 180) / Math.PI;
    }
}

/*
 * Plugins
 * =======
 *
 * The Plugins constructor
 * Provides the plugin system for Game
 */

var Plugins = function(game, plugins) {
    //Save Game for later
    this.game = game;
    //Set up some basic plugin types
    this.plugins = {"Assets": [], "Camera": [], "Colliders": [], "Draw": [], "Sound": [], "World": []}

    //Add already provided plugins
    for(var i = 0, len = plugins.length; i < len; i++) {
        this.add(plugins[i]);
    }

    return this;
}

Plugins.prototype.add = function(plugin, override) {
    var type   = plugin.type = this.game.Utils.capitalize(plugin.type);
    var name   = plugin.name;
    var Plugin = this.get(name);

    if(!/^(add|remove|get|use|loop)$/.test(name)) {
        //This type is not yet registered
        if(!this.plugins[type]) {
            this.plugins[type] = [];
        }

        //A plugin with this name already exists, but may be overridden
        if(Plugin && override) {
            this.remove(name);
            this.add(plugin);
        }
        else if(!Plugin) {
            //Push to plugin pool
            this.plugins[type].push(plugin);
            //Use the new plugin. If one already exists, it will not be overridden.
            this.use(name);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }

    return this;
}

Plugins.prototype.remove = function(name) {
    var Plugin = this.get(name);

    if(Plugin) {
        var type = Plugin.type;

        //Remove from plugin pool
        this.loop(function(plugin, index, array) {
            if(plugin.name === name) {
                array.splice(index, 1);
            }
        });
        //Remove from Game
        if(Plugin === this.game[type]) {
            this.game[type] = null;

            //Replace by last added plugin of the same type (if any)
            var plugins = this.plugins[type];
            if(plugins && plugins.length) {
                this.game[type] = plugins[ plugins.length-1 ];
            }
        }
    }

    return this;
}

Plugins.prototype.get = function(name, warn) {
    var Plugin = null;

    //Loop through all the plugins and get the right one out
    this.loop(function(plugin, index, array) {
        if(plugin.name === name) {
            Plugin = plugin;
            return true;
        }
    });

    //No plugin with this name exists
    if(!Plugin && warn) {
        console.warn(warnings.pluginDoesNotExist, name);
        console.trace();
    }

    return Plugin;
}

Plugins.prototype.use = function(name, override) {
    var plugin = this.get(name);

    //Plugin exists
    if(plugin) {
        var type = plugin.type;

        //Only proceed when no plugin of this type is in use, or the plugin in use may be overridden
        if(!this.game[type] || override) {
            this.game[type] = plugin;
            if(this.game.initTime && plugin.Init) {
                plugin.Init(this.game);
            }
        }
        else if(this.game[type]) {
            console.warn(warnings.useAlreadySet, name);
        }
    }
    else {
        console.warn(warnings.useNonExistentPlugin, name);
    }

    return this;
}

Plugins.prototype.loop = function(cb) {
    //Loop through types
    outer:
    for(type in this.plugins) {
        var plugins = this.plugins[type];

        //Loop through plugins (per type)
        for(var i = 0; i < plugins.length; i++) {
            var br = cb(plugins[i], i, plugins);
            // Break loop if `cb` returns `true` (or another truthy value)
            if(br) { break outer; }
        }
    }

    return this;
}

Plugins.prototype.Init = function() {
    this.loop(function(plugin) {
        if(plugin.Init) {
            plugin.Init(this.game);
        }
    });

    return this;
}

//Attach to Game
Game.prototype.plugin = Plugins.prototype.add;

/*
 * Categories
 * ==========
 *
 * The main Categories constructor
 * Provides the categories system for Game
 */

var Categories = function(game, categories) {
    //Save Game for later
    this.game = game;

    //Add already provided catgories
    for(var i = 0, len = categories.length; i < len; i++) {
        this.add(categories[i]);
    }

    return this;
}

Categories.prototype.add = function(category) {
    var name = category.name;

    if(!/^(add|remove|get|loop)$/.test(name)) {
        this[name] = category;
        if(this.game.initTime && category.Init) {
            category.Init(this.game);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }
}

/*
 * VARS
 * ====
 *
 * Some variables used  by Game.
 */

    var warnings = {
        "noOptions": "WARN: No options passed to the constructor. Default options will be used.",
        "noPluginsConstructor": "WARN: No plugins passed to the constructor. Make sure to add plugins with 'Game.plugin(plugin);'.",
        "pluginDoesNotExist": "WARN: A plugin with the name '%s' does not exist.",
        "useNonExistentPlugin": "WARN: You are trying to use() a non-existent plugin, named '%s'.",
        "useAlreadySet": "WARN: You are trying to use() a plugin, named '%s', but a plugin of this type is already in use. Please use Plugins.use(name, true) to override any plugins already in use.",
        "invalidName": "WARN: The name '%s' is invalid."
    }

})();<commit_msg>Some tweaks and bug fixes<commit_after>var Game;

(function() {

/*
 * Game
 * ====
 *
 * The main Game constructor
 */

Game = function(options, plugins, categories) {
    if(!options) {
        var options = {};
        console.warn(warnings.noOptionsConstructor);
    }
    if(!plugins) {
        var plugins = [];
        console.warn(warnings.noPluginsConstructor);
    }
    if(!categories) {
        var categories = [];
        console.warn(warnings.noCategoriesConstructor);
    }

    this.options    = options;

    this.Assets     = null;
    this.Camera     = null;
    this.Colliders  = null;
    this.Draw       = null;
    this.Sound      = null;
    this.World      = null;
    this.Plugins    = new Plugins(this, plugins);
    this.Categories = new Categories(this, categories);

    this.initTime   = null;
    this.startTime  = null;
    this.pauseTime  = null;
    this.stopTime   = null;

    this.playing    = false;
    this.timer      = null;
    this.frames     = 0;

    return this;
}

Game.prototype.start = function() {
    if(!this.playing) {
        this.startTime = this.Utils.time();
        if(!this.initTime) {
            this.init();
        }
        this.timer = this.requestAnimationFrame(this.Loop);
        this.Loop();
    }

    return this;
}

Game.prototype.pause = function() {
    if(this.playing) {
        this.pauseTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
    }
}

Game.prototype.stop = function() {
    if(this.playing) {
        this.stopTime = this.Utils.time();
        this.cancelAnimationFrame(this.timer);
        this.reset();
    }

    return this;
}

Game.prototype.init = function() {
    // Throw an Error if a plugin is missing.
    // If Game[plugin] is null, that plugin is missing
    if(!this.Assets || !this.Camera || !this.Colliders || !this.Draw || !this.Sound || !this.World) {
        throw new Error("Missing plugin.");
    }
}

Game.prototype.Loop = function() {
    this.frames++;

    this.World.Update(this);
    this.Camera.Update(this);
    this.World.Draw(this);

    return this;
}

Game.prototype.Utils = {
    capitalize: function(str) {
        return str.substr(0,1).toUpperCase() + str.substr(1).toLowerCase();
    },
    toRadians: function(deg) {
        return (deg * Math.PI) / 180;
    },
    toDegrees: function(rad) {
        return (rad * 180) / Math.PI;
    }
}

Game.prototype.plugin = function(plugin) {
    this.Plugins.add(plugin);

    return this;
}

Game.prototype.category = function(category) {
    this.Categories.add(category);

    return this;
}

/*
 * Plugins
 * =======
 *
 * The Plugins constructor
 * Provides the plugin system for Game
 */

var Plugins = function(game, plugins) {
    //Save Game for later
    this.game = game;
    //Set up some basic plugin types
    this.plugins = {"Assets": [], "Camera": [], "Colliders": [], "Draw": [], "Sound": [], "World": []}

    //Add already provided plugins
    if(plugins instanceof Array) {
        for(var i = 0, len = plugins.length; i < len; i++) {
            this.add(plugins[i]);
        }
    }

    return this;
}

Plugins.prototype.add = function(plugin, override) {
    var type   = plugin.type = this.game.Utils.capitalize(plugin.type);
    var name   = plugin.name;
    var Plugin = this.get(name);

    if(!/^(add|remove|get|use|loop)$/.test(name)) {
        //This type is not yet registered
        if(!this.plugins[type]) {
            this.plugins[type] = [];
        }

        //A plugin with this name already exists, but may be overridden
        if(Plugin && override) {
            this.remove(name);
            this.add(plugin);
        }
        else if(!Plugin) {
            //Push to plugin pool
            this.plugins[type].push(plugin);
            //Use the new plugin. If one already exists, it will not be overridden.
            this.use(name);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }

    return this;
}

Plugins.prototype.remove = function(name) {
    var Plugin = this.get(name);

    if(Plugin) {
        var type = Plugin.type;

        //Remove from plugin pool
        this.loop(function(plugin, index, array) {
            if(plugin.name === name) {
                array.splice(index, 1);
            }
        });
        //Remove from Game
        if(Plugin === this.game[type]) {
            this.game[type] = null;

            //Replace by last added plugin of the same type (if any)
            var plugins = this.plugins[type];
            if(plugins && plugins.length) {
                this.game[type] = plugins[ plugins.length-1 ];
            }
        }
    }

    return this;
}

Plugins.prototype.get = function(name, warn) {
    var Plugin = null;

    //Loop through all the plugins and get the right one out
    this.loop(function(plugin, index, array) {
        if(plugin.name === name) {
            Plugin = plugin;
            return true;
        }
    });

    //No plugin with this name exists
    if(!Plugin && warn) {
        console.warn(warnings.pluginDoesNotExist, name);
        console.trace();
    }

    return Plugin;
}

Plugins.prototype.use = function(name, override) {
    var plugin = this.get(name);

    //Plugin exists
    if(plugin) {
        var type = plugin.type;

        //Only proceed when no plugin of this type is in use, or the plugin in use may be overridden
        if(!this.game[type] || override) {
            this.game[type] = plugin;
            if(this.game.initTime && plugin.Init) {
                plugin.Init(this.game);
            }
        }
        else if(this.game[type]) {
            console.warn(warnings.useAlreadySet, name);
        }
    }
    else {
        console.warn(warnings.useNonExistentPlugin, name);
    }

    return this;
}

Plugins.prototype.loop = function(cb) {
    //Loop through types
    outer:
    for(type in this.plugins) {
        var plugins = this.plugins[type];

        //Loop through plugins (per type)
        for(var i = 0; i < plugins.length; i++) {
            var br = cb(plugins[i], i, plugins);
            // Break loop if `cb` returns `true` (or another truthy value)
            if(br) { break outer; }
        }
    }

    return this;
}

Plugins.prototype.Init = function() {
    this.loop(function(plugin) {
        if(plugin.Init) {
            plugin.Init(this.game);
        }
    });

    return this;
}

/*
 * Categories
 * ==========
 *
 * The main Categories constructor
 * Provides the categories system for Game
 */

var Categories = function(game, categories) {
    //Save Game for later
    this.game = game;

    //Add already provided catgories
    if(categories instanceof Array) {
        for(var i = 0, len = categories.length; i < len; i++) {
            this.add(categories[i]);
        }
    }

    return this;
}

Categories.prototype.add = function(category) {
    var name = category.name = this.game.Utils.capitalize(category.name);

    if(!/^(add|remove|get|loop)$/.test(name)) {
        this[name] = category;
        if(this.game.initTime && category.Init) {
            category.Init(this.game);
        }
    }
    else {
        console.warn(warnings.invalidName, name);
    }
}

Categories.prototype.remove = function(name) {
    var name = this.game.Utils.capitalize(name);

    this[name] = undefined;

    return this;
}

Categories.prototype.get = function(name) {
    var name = this.game.Utils.capitalize(name);

    return this[name] || null;
}

Categories.prototype.loop = function(cb) {
    for(name in this) {
        if(!/^(add|remove|get|loop)$/.test(name)) {
            cb(this[name], name);
        }
    }
}

/*
 * VARS
 * ====
 *
 * Some variables used  by Game.
 */

    var warnings = {
        "noOptionsConstructor": "WARN: No options passed to the constructor. Default options will be used.",
        "noPluginsConstructor": "WARN: No plugins passed to the constructor. Make sure to add plugins with 'Game.plugin(plugin);'.",
        "noCategoriesConstructor": "WARN: No categories passed to the constructor. Make sure to add categories with 'Game.category(category);'.",
        "pluginDoesNotExist": "WARN: A plugin with the name '%s' does not exist.",
        "useNonExistentPlugin": "WARN: You are trying to use() a non-existent plugin, named '%s'.",
        "useAlreadySet": "WARN: You are trying to use() a plugin, named '%s', but a plugin of this type is already in use. Please use Plugins.use(name, true) to override any plugins already in use.",
        "invalidName": "WARN: The name '%s' is invalid.",
        "noPluginPassed": "WARN: No plugin passed to Plugin.add().",
        "noCategoryPassed": "WARN: No category passed to Categories.add()."
    }

})();<|endoftext|>


<commit_before>// vim: sw=4 et
// All Records example of 'foo_sth'
// _rts:foo_sth:5m:12345 sum 100
// _rts:foo_sth:5m:12345 count 100
// _rts:foo_sth:5m:12345 avg 100
// _rts:foo_sth:5m:12345 min 100
// _rts:foo_sth:5m:12345 max 100
// _rts:foo_sth:aggr.h:23 sum 100
// _rts:foo_sth:aggr.d:6 avg 100

var CHANNEL = '_rts_.record';
var util = require('./util');
var fs = require('fs');
var scripts = {};
['avg', 'max', 'min', 'update_pf'].forEach(function(key) {
        scripts[key] = fs.readFileSync(__dirname + '/' + key + '.lua', 'utf-8');
})

var aggrvals = {
    h: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]
  , d: [0,1,2,3,4,5,6]
}

function log(err, results) {
    if(err) console.log(err.stack || err);
}

/**
 * Create a rts instance
 *
 * @param {Object} options
 *          redis:
 *          gran: comma seprate string
 *          points: how many points store
 *          interval: update interval for pflog
 *          prefix: a prefix for all keys
 *
 */
var exports = module.exports = function rts(options) {
    var redis = options.redis
      , granularities = options.gran || '5m, 1h, 1d, 1w'
      , prefix = options.prefix || ''
      , points = options.points || 500
      , interval = options.interval || 60000;
      ;

    prefix = '_rts_' + prefix;

    var keyPFKeys = prefix + ':pfkeys';

    granularities = granularities.split(',').map(util.getUnitDesc);
    var granMap = {};
    granularities.forEach(function(granInfo) {
            granMap[granInfo[1]] = granInfo;
    });

    function getGranKey(name, gran, timestamp) {
        var granId = util.getGranId(gran, timestamp);
        return [prefix, name, gran[1], granId].join(':');
    }

    function getAggrGruopKey(name, aggr, timestamp) {
        var gid = util.getAggrGroupId(aggr, timestamp);
        return [prefix, name, 'aggr', gid].join(':');
    }

    function getAggrKey(name, aggr, timestamp) {
        var aggrId = util.getAggrId(aggr, timestamp);
        return getAggrGruopKey(name, aggr, timestamp) + '.' + aggrId;
    }

    // some thing's some statics value in some granularity at some time
    function writePoint(name, stat, value, gran, timestamp, callback) {
        var key = getGranKey(name, gran, timestamp);
        redis.hset(key, stat, value, callback)
    }

    /**
     * record behavior.
     * @param statistics {Array} sum, max, min, count, avg
     * @param aggregations {Array} dy (day in week each year), hm(hour of day each month).
     *
     */
    function record(name, num, statistics, aggregations, timestamp, callback) {
        timestamp = timestamp || Date.now();
        function recordStats(key) {
            statistics && statistics.forEach(function(stat) {
                    if(stat == 'sum') {
                        multi.hincrby(key, 'sum', num)
                    } else {
                        multi.eval(scripts[stat], 2, key, num, function(err, result) {
                                multi.discard();
                                if(err) throw err;
                                if(result && result.indexOf('ERR') == 0) {
                                    // console.log(scripts[stat]);
                                    throw new Error(result);
                                }
                        });
                    }
            });
        }
        if(!statistics) statistics = ['sum'];
        if(num == undefined) num = 1;

        var multi = redis.multi();

        granularities.forEach(function(gran) {
                var key = getGranKey(name, gran, timestamp);
                recordStats(key);
                var unitPeriod = gran[0];
                multi.expire(key, points * unitPeriod / 1000);
        });

        if(aggregations) for(var i = 0; i < aggregations.length; i++) {
            var aggr = aggregations[i];
            var key = getAggrKey(name, aggr, timestamp);
            recordStats(key);
        }

        multi.exec(callback || log);
    }

    /**
     * record unique access, like unique user of a period time.
     * @param {string | Array} uniqueId one or some uniqueId to be stats
     */
    function recordUnique(name, uniqueId, statistics, aggregations, timestamp, callback) {
        timestamp = timestamp || Date.now();
        // normal record
        if(statistics) {
            var num = Array.isArray(uniqueId) ? uniqueId.length : 1;
            record(name, num, statistics, aggregations, timestamp);
        }
        // record unique
        var multi = redis.multi();
        granularities.forEach(function(gran) {
                var key = getGranKey(name, gran, timestamp);
                var expireTime = util.getExpireTime(gran, timestamp);
                var pfkey = key + ':pf';
                multi.hset(keyPFKeys, pfkey, expireTime);
                if(Array.isArray(uniqueId)) {
                    multi.pfadd.apply(multi, [pfkey].concat(uniqueId));
                } else {
                    multi.pfadd(pfkey, uniqueId);
                }
                // recordStats(key);
                var unitPeriod = gran[0];
                multi.hincrby(key, 'uni', 0);
                multi.expire(key, points * unitPeriod / 1000);
        });
        multi.exec(callback || log);
    }

    /**
     * get results of the stats
     *
     * @param {String} type sum, min, max, avg, count, uni
     */
    function stat(type, name, granCode, fromDate, toDate, callback) {
        if(!granCode) throw new Error('granCode is required');
        if(!callback && typeof toDate == 'function') {
            callback = toDate;
            toDate = Date.now();
        }
        var gran = granMap[granCode] || util.getUnitDesc(granCode);
        if(!gran) throw new Error('Granualrity is not defined ' + granCode);
        if(fromDate instanceof Date) fromDate = fromDate.getTime();
        if(toDate instanceof Date) toDate = toDate.getTime();

        toDate = toDate || Date.now()
        fromDate = fromDate || (toDate - util.getTimePeriod(gran, points));

        var unitPeriod = gran[0];
        var multi = redis.multi();
        var _points = [];
        for(var d = fromDate; d <= toDate; d += unitPeriod) {
            var key = getGranKey(name, gran, d);
            _points.push(util.getKeyTime(gran, d));
            multi.hget(key, type);
        }
        multi.exec(function(err, results) {
                if(err) return callback(err);
                var merged = [];
                for (var i = 0, l = _points.length, p; i < l; i ++) {
                    p = _points[i];
                    merged[i] = [p, Number(results[i])];
                }
                callback(null, {
                        step: unitPeriod
                      , unitType: gran[3]
                      , data: merged
                });
        });
    }

    function aggrstat(type, name, aggr, date, callback) {
        if(!callback && typeof date == 'function') {
            callback = date;
            date = Date.now();
        }
        var vals = aggrvals[aggr[0]];
        var multi = redis.multi();
        var gkey = getAggrGruopKey(name, aggr, date);
        for (var i = 0, l = vals.length; i < l; i ++) {
            var key = gkey + '.' + vals[i];
            multi.hget(key, type);
        }
        multi.exec(function(err, results) {
                if(err) return callback(err);
                callback(null, results.map(function(result, i) {
                            return [i, Number(result)]
                }));
        });
    }

    function updateHyperLogLog() {
        redis.hgetall(keyPFKeys, function(err, result) {
                if(err) return log(err);
                var now = Date.now();
                var multi = redis.multi();
                var expireTime, key;
                for(var pfkey in result) {
                    expireTime = Number(result[pfkey]);
                    key = pfkey.substring(0, pfkey.length - 3);
                    multi.eval(scripts.update_pf, 2, pfkey, key);
                    if(expireTime < now) {
                        multi.hdel(keyPFKeys, pfkey);
                        multi.del(pfkey);
                    }
                }
                multi.exec(log);
        });
    }

    setInterval(updateHyperLogLog, interval);

    return {
        record : record
      , recordUnique: recordUnique
      , stat: stat
      , aggrstat: aggrstat
      , unique: stat.bind(null, 'uni')
      , uni: stat.bind(null, 'uni')
      , sum: stat.bind(null, 'sum')
      , count: stat.bind(null, 'count')
      , avg: stat.bind(null, 'avg')
      , max: stat.bind(null, 'max')
      , min: stat.bind(null, 'min')
      , aggrsum: aggrstat.bind(null, 'sum')
      , aggrcount: aggrstat.bind(null, 'count')
      , aggravg: aggrstat.bind(null, 'avg')
      , aggrmax: aggrstat.bind(null, 'max')
      , aggrmin: aggrstat.bind(null, 'min')
      , writePoint: writePoint
      , getTimePeriod: util.getTimePeriod
    }
}

exports.getTimePeriod = util.getTimePeriod;
<commit_msg>add setValue<commit_after>// vim: sw=4 et
// All Records example of 'foo_sth'
// _rts:foo_sth:5m:12345 sum 100
// _rts:foo_sth:5m:12345 count 100
// _rts:foo_sth:5m:12345 avg 100
// _rts:foo_sth:5m:12345 min 100
// _rts:foo_sth:5m:12345 max 100
// _rts:foo_sth:aggr.h:23 sum 100
// _rts:foo_sth:aggr.d:6 avg 100

var CHANNEL = '_rts_.record';
var util = require('./util');
var fs = require('fs');
var scripts = {};
['avg', 'max', 'min', 'update_pf'].forEach(function(key) {
        scripts[key] = fs.readFileSync(__dirname + '/' + key + '.lua', 'utf-8');
})

var aggrvals = {
    h: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]
  , d: [0,1,2,3,4,5,6]
}

function log(err, results) {
    if(err) console.log(err.stack || err);
}

/**
 * Create a rts instance
 *
 * @param {Object} options
 *          redis:
 *          gran: comma seprate string
 *          points: how many points store
 *          interval: update interval for pflog
 *          prefix: a prefix for all keys
 *
 */
var exports = module.exports = function rts(options) {
    var redis = options.redis
      , granularities = options.gran || '5m, 1h, 1d, 1w'
      , prefix = options.prefix || ''
      , points = options.points || 500
      , interval = options.interval || 60000;
      ;

    prefix = '_rts_' + prefix;

    var keyPFKeys = prefix + ':pfkeys';

    granularities = granularities.split(',').map(util.getUnitDesc);
    var granMap = {};
    granularities.forEach(function(granInfo) {
            granMap[granInfo[1]] = granInfo;
    });

    function getGranKey(name, gran, timestamp) {
        var granId = util.getGranId(gran, timestamp);
        return [prefix, name, gran[1], granId].join(':');
    }

    function getAggrGruopKey(name, aggr, timestamp) {
        var gid = util.getAggrGroupId(aggr, timestamp);
        return [prefix, name, 'aggr', gid].join(':');
    }

    function getAggrKey(name, aggr, timestamp) {
        var aggrId = util.getAggrId(aggr, timestamp);
        return getAggrGruopKey(name, aggr, timestamp) + '.' + aggrId;
    }

    // some thing's some statics value in some granularity at some time
    function writePoint(name, stat, value, gran, timestamp, callback) {
        var key = getGranKey(name, gran, timestamp);
        redis.hset(key, stat, value, callback)
    }

    /**
     * record behavior.
     * @param statistics {Array} sum, max, min, count, avg
     * @param aggregations {Array} dy (day in week each year), hm(hour of day each month).
     *
     */
    function record(name, num, statistics, aggregations, timestamp, callback) {
        timestamp = timestamp || Date.now();
        function recordStats(key) {
            statistics && statistics.forEach(function(stat) {
                    if(stat == 'sum') {
                        multi.hincrby(key, 'sum', num)
                    } else {
                        multi.eval(scripts[stat], 2, key, num, function(err, result) {
                                multi.discard();
                                if(err) throw err;
                                if(result && result.indexOf('ERR') == 0) {
                                    // console.log(scripts[stat]);
                                    throw new Error(result);
                                }
                        });
                    }
            });
        }
        if(!statistics) statistics = ['sum'];
        if(num == undefined) num = 1;

        var multi = redis.multi();

        granularities.forEach(function(gran) {
                var key = getGranKey(name, gran, timestamp);
                recordStats(key);
                var unitPeriod = gran[0];
                multi.expire(key, points * unitPeriod / 1000);
        });

        if(aggregations) for(var i = 0; i < aggregations.length; i++) {
            var aggr = aggregations[i];
            var key = getAggrKey(name, aggr, timestamp);
            recordStats(key);
        }

        multi.exec(callback || log);
    }

    /**
     * record unique access, like unique user of a period time.
     * @param {string | Array} uniqueId one or some uniqueId to be stats
     */
    function recordUnique(name, uniqueId, statistics, aggregations, timestamp, callback) {
        timestamp = timestamp || Date.now();
        // normal record
        if(statistics) {
            var num = Array.isArray(uniqueId) ? uniqueId.length : 1;
            record(name, num, statistics, aggregations, timestamp);
        }
        // record unique
        var multi = redis.multi();
        granularities.forEach(function(gran) {
                var key = getGranKey(name, gran, timestamp);
                var expireTime = util.getExpireTime(gran, timestamp);
                var pfkey = key + ':pf';
                multi.hset(keyPFKeys, pfkey, expireTime);
                if(Array.isArray(uniqueId)) {
                    multi.pfadd.apply(multi, [pfkey].concat(uniqueId));
                } else {
                    multi.pfadd(pfkey, uniqueId);
                }
                // recordStats(key);
                var unitPeriod = gran[0];
                multi.hincrby(key, 'uni', 0);
                multi.expire(key, points * unitPeriod / 1000);
        });
        multi.exec(callback || log);
    }

    /**
     * get results of the stats
     *
     * @param {String} type sum, min, max, avg, count, uni
     */
    function stat(type, name, granCode, fromDate, toDate, callback) {
        if(!granCode) throw new Error('granCode is required');
        if(!callback && typeof toDate == 'function') {
            callback = toDate;
            toDate = Date.now();
        }
        var gran = granMap[granCode] || util.getUnitDesc(granCode);
        if(!gran) throw new Error('Granualrity is not defined ' + granCode);
        if(fromDate instanceof Date) fromDate = fromDate.getTime();
        if(toDate instanceof Date) toDate = toDate.getTime();

        toDate = toDate || Date.now()
        fromDate = fromDate || (toDate - util.getTimePeriod(gran, points));

        var unitPeriod = gran[0];
        var multi = redis.multi();
        var _points = [];
        for(var d = fromDate; d <= toDate; d += unitPeriod) {
            var key = getGranKey(name, gran, d);
            _points.push(util.getKeyTime(gran, d));
            multi.hget(key, type);
        }
        multi.exec(function(err, results) {
                if(err) return callback(err);
                var merged = [];
                for (var i = 0, l = _points.length, p; i < l; i ++) {
                    p = _points[i];
                    merged[i] = [p, Number(results[i])];
                }
                callback(null, {
                        step: unitPeriod
                      , unitType: gran[3]
                      , data: merged
                });
        });
    }

    function aggrstat(type, name, aggr, date, callback) {
        if(!callback && typeof date == 'function') {
            callback = date;
            date = Date.now();
        }
        var vals = aggrvals[aggr[0]];
        var multi = redis.multi();
        var gkey = getAggrGruopKey(name, aggr, date);
        for (var i = 0, l = vals.length; i < l; i ++) {
            var key = gkey + '.' + vals[i];
            multi.hget(key, type);
        }
        multi.exec(function(err, results) {
                if(err) return callback(err);
                callback(null, results.map(function(result, i) {
                            return [i, Number(result)]
                }));
        });
    }

    function updateHyperLogLog() {
        redis.hgetall(keyPFKeys, function(err, result) {
                if(err) return log(err);
                var now = Date.now();
                var multi = redis.multi();
                var expireTime, key;
                for(var pfkey in result) {
                    expireTime = Number(result[pfkey]);
                    key = pfkey.substring(0, pfkey.length - 3);
                    multi.eval(scripts.update_pf, 2, pfkey, key);
                    if(expireTime < now) {
                        multi.hdel(keyPFKeys, pfkey);
                        multi.del(pfkey);
                    }
                }
                multi.exec(log);
        });
    }

    setInterval(updateHyperLogLog, interval);

    return {
        record : record
      , recordUnique: recordUnique
      , stat: stat
      , aggrstat: aggrstat
      , unique: stat.bind(null, 'uni')
      , uni: stat.bind(null, 'uni')
      , sum: stat.bind(null, 'sum')
      , count: stat.bind(null, 'count')
      , avg: stat.bind(null, 'avg')
      , max: stat.bind(null, 'max')
      , min: stat.bind(null, 'min')
      , aggrsum: aggrstat.bind(null, 'sum')
      , aggrcount: aggrstat.bind(null, 'count')
      , aggravg: aggrstat.bind(null, 'avg')
      , aggrmax: aggrstat.bind(null, 'max')
      , aggrmin: aggrstat.bind(null, 'min')
      , writePoint: writePoint
      , setValue: writePoint
      , getTimePeriod: util.getTimePeriod
    }
}

exports.getTimePeriod = util.getTimePeriod;
<|endoftext|>


<commit_before>var _ = require('lodash'),
    EventEmitter = require('eventemitter3'),
    runtime = require('postman-runtime'),
    transformer = require('postman-collection-transformer'),
    RunSummary = require('./summary'),
    config = require('./config'),
    util = require('./util'),
    Collection = require('postman-collection').Collection,
    SerialiseError = require('serialised-error'),

    runtimeEvents = {
        start: [],
        beforeIteration: [],
        beforeItem: ['item'],
        beforePrerequest: ['events', 'item'],
        prerequest: ['executions', 'item'],
        beforeRequest: ['request', 'item'],
        request: ['response', 'request', 'item'],
        beforeTest: ['events', 'item'],
        test: ['executions', 'item'],
        item: ['item'],
        iteration: [],
        beforeScript: ['script', 'event', 'item'],
        script: ['execution', 'script', 'event', 'item']
    },

    /**
     * Accepts an object, and extracts the property inside an object which is supposed to contain the required data.
     * In case of variables, it also extracts them into plain JS objects.
     *
     * @param {Object} source
     * @param {String} type - "environment" or "globals", etc
     *
     * @returns {Object}
     */
    extractModel = function (source, type) {
        source = source[type] || source; // extract object that holds variable. these usually come from cloud API
        if (!_.isObject(source)) {
            return undefined;
        }

        // ensure we unbox the JSON if it comes from cloud-api or similar sources
        !source.values && _.isObject(source[type]) && (source = source[type]);

        // ensure we handle environment sent in form of array of values
        (source.name && _.isArray(source.values)) && (source = source.values);

        // we ensure that environment passed as array is converted to plain object. runtime does this too, but we do it
        // here for consistency of options passed to reporters
        return _.isArray(source) ? _(source).keyBy('key').mapValues('value').value() : source;
    },

    /**
     * Loads the given data of type from a specified external location
     *
     * @param {String} type - The type of data to load.
     * @param {String} location - The location to load from (path or URL)
     * @param {function} cb
     * @returns {*}
     */
    externalLoader = function (type, location, cb) {
        return _.isString(location) ? util.fetch(location, function (err, data) {
            if (err) {
                return cb(err);
            }
            return cb(null, extractModel(data, type));
        }) : cb(null, location);
    },

    /**
     * Custom configuration loaders for the required configuration keys.
     *
     * @type {Object}
     */
    configLoaders = {
        collection: function (value, callback) {
            externalLoader('collection', value, function (err, data) {
                if (err) {
                    return callback(err);
                }
                if (!_.get(data, 'info.schema')){
                    return transformer.convert(data, { inputVersion: '1.0.0', outputVersion: '2.0.0' }, callback)
                }
                callback(null, data);
            });
        },
        environment: externalLoader.bind(this, 'environment'),
        globals: externalLoader.bind(this, 'globals'),
        data: externalLoader // todo: use a csv parser to allow csv files.
    };

/**
 * @param {Object} options
 * @param {Collection|Object} options.collection
 * @param {Object} options.environment
 * @param {Array|String} options.reporters
 * @param {Function} callback
 *
 * @returns {EventEmitter}
 */
module.exports = function (options, callback) {
    // validate all options. it is to be notet that `options` parameter is option and is polymorphic
    (!callback && _.isFunction(options)) && (
        (callback = options),
            (options = {})
    );
    !_.isFunction(callback) && (callback = _.noop);

    var emitter = new EventEmitter(), // @todo: create a new inherited constructor
        runner = new runtime.Runner();

    // get the configuration from various sources
    config.get(options, { loaders: configLoaders, command: 'run' }, function (err, options) {
        if (err) {
            return callback(err);
        }

        // ensure that the collection option is present before starting a run
        if (!_.isObject(options.collection)) {
            callback(new Error('newman: expecting a collection to run'));
            return emitter;
        }

        // create a collection in case it is not one. user can send v2 JSON as a source and that will be converted
        // to a collection
        if (_.isPlainObject(options.collection) && !Collection.isCollection(options.collection)) {
            options.collection = new Collection(options.collection);
        }

        // store summary object and other relevant information inside the emitter
        emitter.summary = new RunSummary(emitter, options);

        options.collection && runner.run(options.collection, {
            abortOnFailure: options.abortOnError, // todo: could be a better name, especially in the CLI.
            iterationCount: options.iterationCount,
            environment: options.environment,
            globals: options.globals,
            entrypoint: options.folder,
            data: options.data, // todo: should this have a better name?
            // todo: add support for more types of timeouts, currently only request is supported
            timeout: options.timeoutRequest ? { request: options.timeoutRequest } : undefined,
            requester: {
                followRedirects: _.has(options, 'avoidRedirects') ? !options.avoidRedirects : undefined,
                strictSSL: _.has(options, 'insecure') ? !options.insecure : undefined
            }
        }, function (err, run) {
            var callbacks = {},
                // ensure that the reporter option type polymorphism is handled
                reporters = _.isString(options.reporters) ? [options.reporters] : options.reporters;

            // emit events for all the callbacks triggered by the runtime
            _.each(runtimeEvents, function (definition, eventName) {

                // intercept each runtime.* callback and expose a global object based event
                callbacks[eventName] = function (err, cursor) {
                    var args = arguments,
                        obj = { cursor: cursor };

                    // convert the arguments into an object by taking the key name reference from the definition
                    // object
                    _.each(definition, function (key, index) {
                        obj[key] = args[index + 2]; // first two are err, cursor
                    });

                    args = [eventName, err, obj];
                    emitter.emit.apply(emitter, args); // eslint-disable-line prefer-spread
                };
            });

            // two runtime callbacks do not follow pattern, define them separately
            callbacks.console = emitter.emit.bind(emitter, 'console');
            callbacks.exception = function (cursor, err) {
                emitter.emit('exception', err, {
                    cursor: cursor
                });
            };

            // override the `done` event to fire the end callback
            callbacks.done = function (err) { // @todo - do some meory cleanup here?
                emitter.emit('done', err, emitter.summary); // we now trigger actual done event which we had overridden
                callback(err, emitter.summary);
            };

            // generate pseudo assertion events since runtime does not trigger assertion events yet.
            // without this, all reporters would needlessly need to extract assertions and create an error object
            // out of it
            emitter.on('script', function (err, o) {
                // we iterate on each test asserion to trigger an evemt. during this, we create a pseudo error object
                // for the assertion
                var index = 0,
                    type = o && o.event && o.event.listen;

                _.each(_.get(o.execution, 'globals.tests'), function (passed, assertion) {
                    emitter.emit('assertion', passed ? null : (new SerialiseError({
                        name: 'AssertionFailure',
                        message: assertion,
                        stack: 'AssertionFailure: Expected tests["' + assertion + '"] to be truthy\n' +
                        '   at Object.eval test.js:' + (index + 1) + ':' + (o.cursor.position + 1) + ')',
                        index: index // @todo find better way to notify the same
                    }, true)), {
                        cursor: o.cursor,
                        assertion: assertion,
                        event: o.event,
                        item: o.item
                    });
                    index += 1;
                });

                // bubble special script name based events
                type && emitter.emit(type + 'Script', err, o);
            });

            emitter.on('beforeScript', function (err, o) {
                // bubble special script name based events
                o && o.event && o.event && emitter.emit(_.camelCase('before-' + o.event.listen + 'Script'), err, o);
            });

            // initialise all the reporters
            _.isArray(reporters) && _.each(reporters, function (reporterName) {
                // @todo validate reporter name to exist
                // @todo look up for node module "newman-reporter-" as reporters if one is not found locally
                // @todo sanitise the reporter name for security reasons
                (emitter.reporters || (emitter.reporters = {}))[reporterName] =
                    new (require('./reporters/' + reporterName))(emitter, options);
            });

            // we ensure that everything is async to comply with event paradigm and start the run
            setImmediate(function () {
                run.start(callbacks);
            });
        });
    });

    return emitter;
};
<commit_msg>code style + extra check before transforming collections<commit_after>var _ = require('lodash'),
    EventEmitter = require('eventemitter3'),
    runtime = require('postman-runtime'),
    transformer = require('postman-collection-transformer'),
    RunSummary = require('./summary'),
    config = require('./config'),
    util = require('./util'),
    Collection = require('postman-collection').Collection,
    SerialiseError = require('serialised-error'),

    runtimeEvents = {
        start: [],
        beforeIteration: [],
        beforeItem: ['item'],
        beforePrerequest: ['events', 'item'],
        prerequest: ['executions', 'item'],
        beforeRequest: ['request', 'item'],
        request: ['response', 'request', 'item'],
        beforeTest: ['events', 'item'],
        test: ['executions', 'item'],
        item: ['item'],
        iteration: [],
        beforeScript: ['script', 'event', 'item'],
        script: ['execution', 'script', 'event', 'item']
    },

    /**
     * Accepts an object, and extracts the property inside an object which is supposed to contain the required data.
     * In case of variables, it also extracts them into plain JS objects.
     *
     * @param {Object} source
     * @param {String} type - "environment" or "globals", etc
     *
     * @returns {Object}
     */
    extractModel = function (source, type) {
        source = source[type] || source; // extract object that holds variable. these usually come from cloud API
        if (!_.isObject(source)) {
            return undefined;
        }

        // ensure we unbox the JSON if it comes from cloud-api or similar sources
        !source.values && _.isObject(source[type]) && (source = source[type]);

        // ensure we handle environment sent in form of array of values
        (source.name && _.isArray(source.values)) && (source = source.values);

        // we ensure that environment passed as array is converted to plain object. runtime does this too, but we do it
        // here for consistency of options passed to reporters
        return _.isArray(source) ? _(source).keyBy('key').mapValues('value').value() : source;
    },

    /**
     * Loads the given data of type from a specified external location
     *
     * @param {String} type - The type of data to load.
     * @param {String} location - The location to load from (path or URL)
     * @param {function} cb
     * @returns {*}
     */
    externalLoader = function (type, location, cb) {
        return _.isString(location) ? util.fetch(location, function (err, data) {
            if (err) {
                return cb(err);
            }
            return cb(null, extractModel(data, type));
        }) : cb(null, location);
    },

    /**
     * Custom configuration loaders for the required configuration keys.
     *
     * @type {Object}
     */
    configLoaders = {
        collection: function (value, callback) {
            externalLoader('collection', value, function (err, data) {
                if (err) {
                    return callback(err);
                }
                if (_.isObject(data) && !_.get(data, 'info.schema')) {
                    return transformer.convert(data, { inputVersion: '1.0.0', outputVersion: '2.0.0' }, callback);
                }
                callback(null, data);
            });
        },
        environment: externalLoader.bind(this, 'environment'),
        globals: externalLoader.bind(this, 'globals'),
        data: externalLoader // todo: use a csv parser to allow csv files.
    };

/**
 * @param {Object} options
 * @param {Collection|Object} options.collection
 * @param {Object} options.environment
 * @param {Array|String} options.reporters
 * @param {Function} callback
 *
 * @returns {EventEmitter}
 */
module.exports = function (options, callback) {
    // validate all options. it is to be notet that `options` parameter is option and is polymorphic
    (!callback && _.isFunction(options)) && (
        (callback = options),
            (options = {})
    );
    !_.isFunction(callback) && (callback = _.noop);

    var emitter = new EventEmitter(), // @todo: create a new inherited constructor
        runner = new runtime.Runner();

    // get the configuration from various sources
    config.get(options, { loaders: configLoaders, command: 'run' }, function (err, options) {
        if (err) {
            return callback(err);
        }

        // ensure that the collection option is present before starting a run
        if (!_.isObject(options.collection)) {
            callback(new Error('newman: expecting a collection to run'));
            return emitter;
        }

        // create a collection in case it is not one. user can send v2 JSON as a source and that will be converted
        // to a collection
        if (_.isPlainObject(options.collection) && !Collection.isCollection(options.collection)) {
            options.collection = new Collection(options.collection);
        }

        // store summary object and other relevant information inside the emitter
        emitter.summary = new RunSummary(emitter, options);

        options.collection && runner.run(options.collection, {
            abortOnFailure: options.abortOnError, // todo: could be a better name, especially in the CLI.
            iterationCount: options.iterationCount,
            environment: options.environment,
            globals: options.globals,
            entrypoint: options.folder,
            data: options.data, // todo: should this have a better name?
            // todo: add support for more types of timeouts, currently only request is supported
            timeout: options.timeoutRequest ? { request: options.timeoutRequest } : undefined,
            requester: {
                followRedirects: _.has(options, 'avoidRedirects') ? !options.avoidRedirects : undefined,
                strictSSL: _.has(options, 'insecure') ? !options.insecure : undefined
            }
        }, function (err, run) {
            var callbacks = {},
                // ensure that the reporter option type polymorphism is handled
                reporters = _.isString(options.reporters) ? [options.reporters] : options.reporters;

            // emit events for all the callbacks triggered by the runtime
            _.each(runtimeEvents, function (definition, eventName) {

                // intercept each runtime.* callback and expose a global object based event
                callbacks[eventName] = function (err, cursor) {
                    var args = arguments,
                        obj = { cursor: cursor };

                    // convert the arguments into an object by taking the key name reference from the definition
                    // object
                    _.each(definition, function (key, index) {
                        obj[key] = args[index + 2]; // first two are err, cursor
                    });

                    args = [eventName, err, obj];
                    emitter.emit.apply(emitter, args); // eslint-disable-line prefer-spread
                };
            });

            // two runtime callbacks do not follow pattern, define them separately
            callbacks.console = emitter.emit.bind(emitter, 'console');
            callbacks.exception = function (cursor, err) {
                emitter.emit('exception', err, {
                    cursor: cursor
                });
            };

            // override the `done` event to fire the end callback
            callbacks.done = function (err) { // @todo - do some meory cleanup here?
                emitter.emit('done', err, emitter.summary); // we now trigger actual done event which we had overridden
                callback(err, emitter.summary);
            };

            // generate pseudo assertion events since runtime does not trigger assertion events yet.
            // without this, all reporters would needlessly need to extract assertions and create an error object
            // out of it
            emitter.on('script', function (err, o) {
                // we iterate on each test asserion to trigger an evemt. during this, we create a pseudo error object
                // for the assertion
                var index = 0,
                    type = o && o.event && o.event.listen;

                _.each(_.get(o.execution, 'globals.tests'), function (passed, assertion) {
                    emitter.emit('assertion', passed ? null : (new SerialiseError({
                        name: 'AssertionFailure',
                        message: assertion,
                        stack: 'AssertionFailure: Expected tests["' + assertion + '"] to be truthy\n' +
                        '   at Object.eval test.js:' + (index + 1) + ':' + (o.cursor.position + 1) + ')',
                        index: index // @todo find better way to notify the same
                    }, true)), {
                        cursor: o.cursor,
                        assertion: assertion,
                        event: o.event,
                        item: o.item
                    });
                    index += 1;
                });

                // bubble special script name based events
                type && emitter.emit(type + 'Script', err, o);
            });

            emitter.on('beforeScript', function (err, o) {
                // bubble special script name based events
                o && o.event && o.event && emitter.emit(_.camelCase('before-' + o.event.listen + 'Script'), err, o);
            });

            // initialise all the reporters
            _.isArray(reporters) && _.each(reporters, function (reporterName) {
                // @todo validate reporter name to exist
                // @todo look up for node module "newman-reporter-" as reporters if one is not found locally
                // @todo sanitise the reporter name for security reasons
                (emitter.reporters || (emitter.reporters = {}))[reporterName] =
                    new (require('./reporters/' + reporterName))(emitter, options);
            });

            // we ensure that everything is async to comply with event paradigm and start the run
            setImmediate(function () {
                run.start(callbacks);
            });
        });
    });

    return emitter;
};
<|endoftext|>


<commit_before>module.exports = {
  voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'],

  configDefaults: {
      'closeTagOn </': true
  },

  activate: function(state) {
    var _this = this;

    atom.workspaceView.eachEditorView(function(editorView) {
        editorView.editor.buffer.on('changed', function(e) {
          _this.bufferChanged(e);
        });
    });

    return atom.workspaceView.command("tag:close-tag", (function(_this) {
      return function() {
        return _this.closeTag();
      };
    })(this));
  },

  closeTag: function() {
    var editor = atom.workspace.getActiveEditor();
    if (editor === undefined) {
      return;
    }

    var cursorPos = editor.getCursor().getBufferPosition();
    var buffer = editor.getTextInBufferRange([[0, 0], cursorPos]);
    editor.insertText(this.getClosingTag(buffer));
  },

  bufferChanged: function(e) {
    var editor = atom.workspace.getActiveEditor();

    if (editor === undefined || !atom.config.get('tag.closeTagOn </') || e.newText !== '/') {
      return;
    }

    var grammar = editor.getGrammar().name;
    if (
      grammar.length == 0 ||
      (grammar.indexOf('HTML') < 0 && grammar.indexOf('XML') < 0 && grammar.indexOf('Handlebars') < 0)
    ) {
      return;
    }

    var cursorPos = editor.getCursor().getBufferPosition();
    var translatedPos = cursorPos.translate([0, -2]);
    var lastTwo = editor.getTextInBufferRange([translatedPos, cursorPos]);

    if (lastTwo !== '</') {
      return;
    }

    var buffer = editor.getTextInBufferRange([[0, 0], translatedPos]);
    var closingTag = this.getClosingTag(buffer);
    if (closingTag.length > 0) {
      // Temporary workaround for a funny cursor bug
      setTimeout(function() {
        editor.insertText(closingTag.substring(2));
      }, 1);
    }
    return;
  },

  getClosingTag: function (str) {
    var _this = this;
    var tokens = str.split('<');

    if (tokens.length == 0) {
      return '';
    }
    tokens.reverse();

    var getTag = function (token) {
      var tag = token.substring(0, token.search('>')).trim();

      if (tag.substr(tag.length - 1) == '/') {
        return tag;
      } else if (tag.indexOf(' ') < 0) {
        return tag;
      } else {
        return tag.substring(0, token.search(' '));
      }
    }

    var isVoidElement = function(tag) {
      tag = tag.trim();
      var firstChar = tag.substr(0, 1);
      var lastChar = tag.substr(tag.length - 1);
      if (['?', '%'].indexOf(firstChar) >= 0) {
        return true;
      }
      if (['/'].indexOf(lastChar) >= 0) {
        return true;
      }

      tag = tag.toLowerCase();
      if (_this.voidElements.indexOf(tag) >= 0) {
        return true;
      }

      return false;
    }

    var skip = [];
    for (var i in tokens) {
      var token = tokens[i];
      var tag = getTag(token);
      if (tag !== undefined && tag.length > 0 && !isVoidElement(tag)) {
        if (tag.substring(0, 1) != '/') {
          if (skip.length === 0) {
            return '</' + tag + '>';
          } else if (skip[skip.length - 1] == tag) {
            skip.pop();
          }
        } else {
          skip.push(tag.substring(1));
        }
      }
    }
    return '';
  }
};
<commit_msg>use command registery instead of workspaceView.commands<commit_after>module.exports = {
  voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'],

  configDefaults: {
      'closeTagOn </': true
  },

  activate: function(state) {
    var _this = this;

    atom.workspaceView.eachEditorView(function(editorView) {
        editorView.editor.buffer.on('changed', function(e) {
          _this.bufferChanged(e);
        });
    });

    return atom.commands.add("atom-workspace", "tag:close-tag", (function(_this) {
      return function() {
        return _this.closeTag();
      };
    })(this));
  },

  closeTag: function() {
    var editor = atom.workspace.getActiveEditor();
    if (editor === undefined) {
      return;
    }

    var cursorPos = editor.getCursor().getBufferPosition();
    var buffer = editor.getTextInBufferRange([[0, 0], cursorPos]);
    editor.insertText(this.getClosingTag(buffer));
  },

  bufferChanged: function(e) {
    var editor = atom.workspace.getActiveEditor();

    if (editor === undefined || !atom.config.get('tag.closeTagOn </') || e.newText !== '/') {
      return;
    }

    var grammar = editor.getGrammar().name;
    if (
      grammar.length == 0 ||
      (grammar.indexOf('HTML') < 0 && grammar.indexOf('XML') < 0 && grammar.indexOf('Handlebars') < 0)
    ) {
      return;
    }

    var cursorPos = editor.getCursor().getBufferPosition();
    var translatedPos = cursorPos.translate([0, -2]);
    var lastTwo = editor.getTextInBufferRange([translatedPos, cursorPos]);

    if (lastTwo !== '</') {
      return;
    }

    var buffer = editor.getTextInBufferRange([[0, 0], translatedPos]);
    var closingTag = this.getClosingTag(buffer);
    if (closingTag.length > 0) {
      // Temporary workaround for a funny cursor bug
      setTimeout(function() {
        editor.insertText(closingTag.substring(2));
      }, 1);
    }
    return;
  },

  getClosingTag: function (str) {
    var _this = this;
    var tokens = str.split('<');

    if (tokens.length == 0) {
      return '';
    }
    tokens.reverse();

    var getTag = function (token) {
      var tag = token.substring(0, token.search('>')).trim();

      if (tag.substr(tag.length - 1) == '/') {
        return tag;
      } else if (tag.indexOf(' ') < 0) {
        return tag;
      } else {
        return tag.substring(0, token.search(' '));
      }
    }

    var isVoidElement = function(tag) {
      tag = tag.trim();
      var firstChar = tag.substr(0, 1);
      var lastChar = tag.substr(tag.length - 1);
      if (['?', '%'].indexOf(firstChar) >= 0) {
        return true;
      }
      if (['/'].indexOf(lastChar) >= 0) {
        return true;
      }

      tag = tag.toLowerCase();
      if (_this.voidElements.indexOf(tag) >= 0) {
        return true;
      }

      return false;
    }

    var skip = [];
    for (var i in tokens) {
      var token = tokens[i];
      var tag = getTag(token);
      if (tag !== undefined && tag.length > 0 && !isVoidElement(tag)) {
        if (tag.substring(0, 1) != '/') {
          if (skip.length === 0) {
            return '</' + tag + '>';
          } else if (skip[skip.length - 1] == tag) {
            skip.pop();
          }
        } else {
          skip.push(tag.substring(1));
        }
      }
    }
    return '';
  }
};
<|endoftext|>