Fix inlineMemcpy and use it to copy structs

--- src/type.coffee
+++ src/type.coffee
@@ -343,23 +343,37 @@
   new Value ty.view, [new Index new Op '>>', offsetOp, new Literal shiftBy ty.size]

 # Generate an inline memcpy.
-inlineMemcpy = (dest, src, n, ret, o) ->
+inlineMemcpy = (dest, src, ty, o) ->
   scope = o.scope
   stmts = []
   # Store the pointer to the struct we're copying to.
-  destPtr = new Value new Literal scope.freeVariable 'dest'
-  stmts.push new Assign destPtr, dest
+  destPtr = new Literal scope.freeVariable 'dest'
+  stmts.push new Assign new Value(destPtr), dest
   # Store the pointer to the struct we're copying from.
-  srcPtr = new Value new Literal scope.freeVariable 'src'
-  stmts.push new Assign srcPtr, src
-  # Loop over the bytes and copy.
-  endVal = new Op '+', destPtr, new Literal n
-  loopBody = new Assign new Value(HEAP, [new Index new Op '++', destPtr, null, true]),
-                        new Value(HEAP, [new Index new Op '++', srcPtr, null, true])
-  memcpy = new While new Op('<', destPtr, endVal)
-  memcpy.addBody new Block [loopBody]
-  stmts.push memcpy
-  stmts.push ret
+  srcPtr = new Literal scope.freeVariable 'src'
+  stmts.push new Assign new Value(srcPtr), src
+  # Unroll the copy loop over the bytes.
+  if ty instanceof StructType
+    # We can do better than byte-by-byte if we're memcpying a struct. We need
+    # to manually set the computedType so they get transformed properly in
+    # Value::transform.
+    for field in ty.fields
+      access = new Access new Literal field.name
+      access.computedField = field
+      fty = field.type
+      destProp = new Value destPtr, [access]
+      destProp.computedType = fty
+      srcProp = new Value srcPtr, [access]
+      srcProp.computedType = fty
+      assign = new Assign destProp, srcProp
+      assign.computedType = fty
+      stmts.push assign
+  else
+    for i in [0...ty.size]
+      offset = new Value new Literal i
+      stmts.push new Assign new Value(HEAP, [new Index new Op '+', new Value(destPtr), offset]),
+                            new Value(HEAP, [new Index new Op '+', new Value(srcPtr), offset])
+  stmts.push new Value destPtr
   new Value new Parens new Block stmts

 # Reflect a heap-allocated struct as a JavaScript object literal.
@@ -371,7 +385,7 @@
     # Use transform here to get a struct literal out, since we have to compute
     # the offset for the field. We have to manually set the types, kind of
     # gross.
-    access = new Access(new Literal fname)
+    access = new Access new Literal fname
     access.computedField = field
     propValue = new Value ptr, [access]
     propValue.computedType = field.type
@@ -410,7 +424,7 @@
   lval = @variable
   ty = @computedType
   return unless lval instanceof Deref and ty instanceof StructType
-  new Call MEMCPY, [HEAPV, lval.expr, @value, new Literal ty.size]
+  inlineMemcpy lval.expr, @value, ty, o

 # Typed new and delete are transformed to malloc and free, and pointer
 # arithmetic also needs to be transformed.


Fix errors in re-observing properties for previously cleaned up weak references. [#441, #442]

--- src/adapter.coffee
+++ src/adapter.coffee
@@ -1,5 +1,5 @@
 # The default `.` adapter thats comes with Rivets.js. Allows subscribing to
-# properties on POJSOs, implemented in ES5 natives using
+# properties on plain objects, implemented in ES5 natives using
 # `Object.defineProperty`.
 Rivets.public.adapters['.'] =
   id: '_rv'
@@ -60,19 +60,22 @@

     unless callbacks[keypath]?
       callbacks[keypath] = []
-      value = obj[keypath]
+      desc = Object.getOwnPropertyDescriptor obj, keypath

-      Object.defineProperty obj, keypath,
-        enumerable: true
-        get: -> value
-        set: (newValue) =>
-          if newValue isnt value
-            @unobserveMutations value, obj[@id], keypath
-            value = newValue
+      unless desc?.get or desc?.set
+        value = obj[keypath]

-            if callbacks[keypath]
-              callback() for callback in callbacks[keypath].slice() when callback in callbacks[keypath]
-            @observeMutations newValue, obj[@id], keypath
+        Object.defineProperty obj, keypath,
+          enumerable: true
+          get: -> value
+          set: (newValue) =>
+            if newValue isnt value
+              @unobserveMutations value, obj[@id], keypath
+              value = newValue
+
+              if callbacks[keypath]
+                callback() for callback in callbacks[keypath].slice() when callback in callbacks[keypath]
+              @observeMutations newValue, obj[@id], keypath

     unless callback in callbacks[keypath]
       callbacks[keypath].push callback
@@ -80,17 +83,16 @@
     @observeMutations obj[keypath], obj[@id], keypath

   unobserve: (obj, keypath, callback) ->
-    map = @weakmap[obj[@id]]
-    callbacks = map.callbacks[keypath]
+    if map = @weakmap[obj[@id]]
+      if callbacks = map.callbacks[keypath]
+        if (idx = callbacks.indexOf(callback)) >= 0
+          callbacks.splice idx, 1

-    if (idx = callbacks.indexOf(callback)) >= 0
-      callbacks.splice idx, 1
+          unless callbacks.length
+            delete map.callbacks[keypath]

-      unless callbacks.length
-        delete map.callbacks[keypath]
-
-    @unobserveMutations obj[keypath], obj[@id], keypath
-    @cleanupWeakReference map, obj[@id]
+        @unobserveMutations obj[keypath], obj[@id], keypath
+        @cleanupWeakReference map, obj[@id]

   get: (obj, keypath) ->
     obj[keypath]


Include npm and node version in -v output

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -87,7 +87,14 @@
     args = options.argv
     command = options.command
     if args.version
-      console.log require('../package.json').version
+      apmVersion =  require('../package.json').version
+      npmVersion = require('../node_modules/npm/package.json').version
+      nodeVersion = process.versions.node
+      console.log """
+        apm  #{apmVersion}
+        npm  #{npmVersion}
+        node #{nodeVersion}
+      """
     else if args.help
       if Command = commands[options.command]
         new Command().showHelp(options.command)


Added filter method

Added filter method

--- src/behavior/group.coffee
+++ src/behavior/group.coffee
@@ -25,7 +25,10 @@
       for i in [0 .. @length - 1]
         callback @[i]
       null
-
+
+    filter: (callback) ->
+      @[i] for i in [0 .. @length - 1] when callback @[i] is true
+
     byName: (name) -> @[@names[name]]

     byTag: (tag) -> @tags[tag].map (index) => @[index]


Mysteriously fix mysterious bevel bug... again?

--- src/view.coffee
+++ src/view.coffee
@@ -1354,15 +1354,13 @@
             if multilineChild.child.type is 'indent'
               right.push new draw.Point @bounds[line].right(), glueTop - @view.opts.bevelClip
               right.push new draw.Point @bounds[line].right() - @view.opts.bevelClip, glueTop
-            else
-              right.push new draw.Point multilineBounds.x, glueTop
-
-            # Draw the tab at the top of the indent if necessary
-            if multilineChild.child.type is 'indent'
+
               @addTab right, new draw.Point(@bounds[line + 1].x +
                 @view.opts.indentWidth +
                 @padding +
                 @view.opts.tabOffset, @bounds[line + 1].y), true
+            else
+              right.push new draw.Point multilineBounds.x, glueTop

             right.push new draw.Point multilineNode.bounds[line - multilineChild.startLine + 1].x, glueTop
             right.push new draw.Point multilineNode.bounds[line - multilineChild.startLine + 1].x, @bounds[line + 1].y


Point#direction

Point#direction

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -111,75 +111,85 @@
       this.x == other.x && this.y == other.y

     ###*
-     * Calculate the magnitude of this Point (Vector).
-     * @name magnitude
-     * @methodOf Point#
-     *
-     * @returns The magnitude of this point as if it were a vector from (0, 0) -> (x, y).
-     * @type Number
+    Calculate the magnitude of this Point (Vector).
+    @name magnitude
+    @methodOf Point#
+
+    @returns The magnitude of this point as if it were a vector from (0, 0) -> (x, y).
+    @type Number
     ###
     magnitude: ->
       Point.distance(Point(0, 0), this)

     ###*
-     * Calculate the dot product of this point and another point (Vector).
-     * @name dot
-     * @methodOf Point#
-     *
-     * @param {Point} other The point to dot with this point.
-     * @returns The dot product of this point dot other as a scalar value.
-     * @type Number
+    Returns the direction in radians of this point from the origin.
+    @name direction
+    @methodOf Point#
+
+    @type Number
+    ###
+    direction: ->
+      Math.atan2(this.y, this.x)
+
+    ###*
+    Calculate the dot product of this point and another point (Vector).
+    @name dot
+    @methodOf Point#
+
+    @param {Point} other The point to dot with this point.
+    @returns The dot product of this point dot other as a scalar value.
+    @type Number
     ###
     dot: (other) ->
       this.x * other.x + this.y * other.y

     ###*
-     * Calculate the cross product of this point and another point (Vector).
-     * Usually cross products are thought of as only applying to three dimensional vectors,
-     * but z can be treated as zero. The result of this method is interpreted as the magnitude
-     * of the vector result of the cross product between [x1, y1, 0] x [x2, y2, 0]
-     * perpendicular to the xy plane.
-     * @name cross
-     * @methodOf Point#
-     *
-     * @param {Point} other The point to cross with this point.
-     * @returns The cross product of this point with the other point as scalar value.
-     * @type Number
+    Calculate the cross product of this point and another point (Vector).
+    Usually cross products are thought of as only applying to three dimensional vectors,
+    but z can be treated as zero. The result of this method is interpreted as the magnitude
+    of the vector result of the cross product between [x1, y1, 0] x [x2, y2, 0]
+    perpendicular to the xy plane.
+    @name cross
+    @methodOf Point#
+
+    @param {Point} other The point to cross with this point.
+    @returns The cross product of this point with the other point as scalar value.
+    @type Number
     ###
     cross: (other) ->
       this.x * other.y - other.x * this.y

     ###*
-     * The norm of a vector is the unit vector pointing in the same direction. This method
-     * treats the point as though it is a vector from the origin to (x, y).
-     * @name norm
-     * @methodOf Point#
-     *
-     * @returns The unit vector pointing in the same direction as this vector.
-     * @type Point
+    The norm of a vector is the unit vector pointing in the same direction. This method
+    treats the point as though it is a vector from the origin to (x, y).
+    @name norm
+    @methodOf Point#
+
+    @returns The unit vector pointing in the same direction as this vector.
+    @type Point
     ###
     norm: (length=1.0) ->
       this.scale(length/this.length())

     ###*
-     * Computed the length of this point as though it were a vector from (0,0) to (x,y)
-     * @name length
-     * @methodOf Point#
-     *
-     * @returns The length of the vector from the origin to this point.
-     * @type Number
+    Computed the length of this point as though it were a vector from (0,0) to (x,y)
+    @name length
+    @methodOf Point#
+
+    @returns The length of the vector from the origin to this point.
+    @type Number
     ###
     length: ->
       Math.sqrt(this.dot(this))

     ###*
-     * Computed the Euclidean between this point and another point.
-     * @name distance
-     * @methodOf Point#
-     *
-     * @param {Point} other The point to compute the distance to.
-     * @returns The distance between this point and another point.
-     * @type Number
+    Computed the Euclidean between this point and another point.
+    @name distance
+    @methodOf Point#
+
+    @param {Point} other The point to compute the distance to.
+    @returns The distance between this point and another point.
+    @type Number
     ###
     distance: (other) ->
       Point.distance(this, other)


fix small bug

fix small bug

--- index.cjsx
+++ index.cjsx
@@ -191,7 +191,6 @@
     planeCount
 analogBattle = (sortieHp, enemyHp, combinedHp, isCombined, isWater, body, leastHp, planeCount) ->
   # First air battle
-  planeCount = null

   if body.api_kouku?
     planeCount = analogKouku body.api_kouku, planeCount
@@ -204,7 +203,7 @@

   if body.api_kouku2?
     planeCount = analogKouku body.api_kouku, planeCount
-
+
     if body.api_kouku2.api_stage3?
       koukuAttack sortieHp, enemyHp, body.api_kouku2.api_stage3
     if body.api_kouku2.api_stage3_combined?
@@ -355,7 +354,7 @@
           sortieHp.dmg[i] = enemyHp.dmg[i] = combinedHp.dmg[i] = 0 for i in [0..5]
           # If practice
           if path == '/kcsapi/api_req_practice/battle'
-            enemyName == __ 'PvP'
+            enemyName = __ 'PvP'
             combinedFlag = 0
             sortieInfo = Object.clone window._decks[postBody.api_deck_id - 1].api_ship
             getShipInfo sortieHp, sortieInfo


transfer notify type from damaged to damagedAudio

--- index.cjsx
+++ index.cjsx
@@ -551,7 +551,7 @@
                 tmpShip = tmpShip + _ships[combinedInfo[i]].api_name + " "
             if tmpShip != "" and @enableProphetDamaged
               notify "#{tmpShip}" + __('Heavily damaged'),
-                type: 'damaged'
+                type: 'damagedAudio'
                 icon: join(ROOT, 'views', 'components', 'main', 'assets', 'img', 'state', '4.png')
             if body.api_get_ship?
               getShip = body.api_get_ship


fix mistake

fix mistake

--- index.cjsx
+++ index.cjsx
@@ -28,8 +28,8 @@
 ]
 module.exports =
   name: 'map-hp'
-  priority: 1
-  displayName: [<FontAwesome key={0} name='compass' />, ' 海域血量']
+  priority: 8
+  displayName: [<FontAwesome key={0} name='heart' />, ' 海域血量']
   description: '海域血量'
   version: '1.0.0'
   author: 'Chiba'
@@ -56,7 +56,7 @@

     render: ->

-        <link rel="stylesheet" href={join(relative(ROOT, __dirname), 'assets', 'map-hp.css')} />``
+        <link rel="stylesheet" href={join(relative(ROOT, __dirname), 'assets', 'map-hp.css')} />

           {
@@ -67,7 +67,7 @@
               tmpRes = tmpMax - tmpMap[1]
               [

-                  {mapName[parseInt(mpMap[0] / 10) - 1][tmpMap[0] % 10 - 1]}
+                  {mapName[parseInt(tmpMap[0] / 10) - 1][tmpMap[0] % 10 - 1]}


track module names

track module names

--- src/wrap.coffee
+++ src/wrap.coffee
@@ -104,7 +104,8 @@
             @emit('error', "no size for #{file}") unless opts.size?
         stream.props = size:opts.size
         @_push (done) ->
-            @emit('append', stream)
+            return done() if @skip[entry.name]
+            @emit('append', stream, entry)
             @tarball.append(stream, done)
             content or= fs.createReadStream(file)
             if typeof content is 'string'
@@ -150,10 +151,11 @@

         entry = @append(filename, body, dirname:opts.dirname)
         entry.target = opts.target if opts.target?
+        entry.name = opts.name ? "main"

         dirname = path.dirname(file)
         for req in required.strings
-            params = {dirname, fromFile:entry.file}
+            params = {dirname, name:entry.name, fromFile:entry.file}
             if opts.target and /^[.\/]/.test(req)
                 params.target = path.resolve(path.dirname(opts.target), req)
             @require(req, params)
@@ -188,11 +190,21 @@
             try pkgfile = resolve.sync path.join(mfile, 'package.json'),
                 basedir:dirname
             catch err # nothing
-        if pkgfile and not @files[pkgfile]
-            if path.existsSync(pkgfile)
-                pkgbody = "module.exports=" + fs.readFileSync(pkgfile, 'utf-8')
-                @append(pkgfile, pkgbody, {dirname})
-            else pkgfile = null
+        if pkgfile
+            if @files[pkgfile]
+                pkgname = @files[pkgfile].name
+            else
+                unless path.existsSync(pkgfile)
+                    pkgfile = null
+                else
+                    pkgbody = fs.readFileSync(pkgfile, 'utf-8')
+                    try
+                        npmpkg = JSON.parse(pkgbody)
+                        pkgname = npmpkg?.name
+                    catch err #  ignore broken package.jsons just like node
+                    pkgbody = "module.exports=#{pkgbody}"
+                    pkgjson = @append(pkgfile, pkgbody, {dirname})
+                    pkgjson.name = pkgname ? opts.name

         body = opts.body ? @readFile(opts.file)

@@ -209,9 +221,10 @@

         entry = @append(opts.file, body, {dirname})
         entry.target = opts.target
+        entry.name = pkgname ? opts.name

         for req in nub(required.strings)
-            params = {dirname, fromFile:entry.file}
+            params = {dirname, name:entry.name, fromFile:entry.file}
             if opts.target and /^[.\/]/.test(req)
                 # not a real directory on the filesystem; just using the path
                 # module to get rid of the filename.


Update the tests

Update the tests

--- test/spec/layout_spec.coffee
+++ test/spec/layout_spec.coffee
@@ -66,96 +66,146 @@
       mediator.publish 'loginStatus', false
       expect($body.attr('class')).toBe 'logged-out'

-    it 'should route clicks on internal links', ->
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      path = '/an/internal/link'
-      $("Hello World")
-        .appendTo(document.body)
-        .click()
-        .remove()
-      args = spy.mostRecentCall.args
-      passedPath = args[0]
-      passedCallback = args[1]
-      expect(passedPath).toBe path
-      expect(typeof passedCallback).toBe 'function'
-
-    it 'should correctly pass the query string', ->
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      path = '/another/link?foo=bar&baz=qux'
-      $("Hello World")
-        .appendTo(document.body)
-        .click()
-        .remove()
-      args = spy.mostRecentCall.args
-      passedPath = args[0]
-      passedCallback = args[1]
-      expect(passedPath).toBe path
-      expect(typeof passedCallback).toBe 'function'
-      mediator.unsubscribe '!router:route', spy
-
-    it 'should not route links without href attributes', ->
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      $('Hello World')
-        .appendTo(document.body)
-        .click()
-        .remove()
-      expect(spy).not.toHaveBeenCalled()
-      mediator.unsubscribe '!router:route', spy
-
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      $('Hello World')
-        .appendTo(document.body)
-        .click()
-        .remove()
-      expect(spy).not.toHaveBeenCalled()
-      mediator.unsubscribe '!router:route', spy
-
-    it 'should not route links with empty href', ->
-      # Technically an empty string is a valid relative URL
-      # but it doesn’t make sense to route it
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      $('Hello World')
-        .appendTo(document.body)
-        .click()
-        .remove()
-      expect(spy).not.toHaveBeenCalled()
-      mediator.unsubscribe '!router:route', spy
-
-    it 'should not route links to document fragments', ->
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      $('Hello World')
-        .appendTo(document.body)
-        .click()
-        .remove()
-      expect(spy).not.toHaveBeenCalled()
-      mediator.unsubscribe '!router:route', spy
-
-    it 'should not route links with a noscript class', ->
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      $('Hello World')
-        .appendTo(document.body)
-        .click()
-        .remove()
-      expect(spy).not.toHaveBeenCalled()
-      mediator.unsubscribe '!router:route', spy
-
-    it 'should not route clicks on external links', ->
-      spy = jasmine.createSpy()
-      mediator.subscribe '!router:route', spy
-      path = 'http://www.example.org/'
-      $("Hello World")
-        .appendTo(document.body)
-        .click()
-        .remove()
-      expect(spy).not.toHaveBeenCalled()
-      mediator.unsubscribe '!router:route', spy
+
+    # Routing
+    # =======
+
+    describe 'with default routing params', ->
+      spy = null
+
+      beforeEach ->
+        spy = jasmine.createSpy()
+        mediator.subscribe '!router:route', spy
+
+      afterEach ->
+        mediator.unsubscribe '!router:route', spy
+
+      it 'should route clicks on internal links', ->
+        path = '/an/internal/link'
+        $("Hello World")
+          .appendTo(document.body)
+          .click()
+          .remove()
+        args = spy.mostRecentCall.args
+        passedPath = args[0]
+        passedCallback = args[1]
+        expect(passedPath).toBe path
+        expect(typeof passedCallback).toBe 'function'
+
+      it 'should correctly pass the query string', ->
+        path = '/another/link?foo=bar&baz=qux'
+        $("Hello World")
+          .appendTo(document.body)
+          .click()
+          .remove()
+        args = spy.mostRecentCall.args
+        passedPath = args[0]
+        passedCallback = args[1]
+        expect(passedPath).toBe path
+        expect(typeof passedCallback).toBe 'function'
+
+      it 'should not route links without href attributes', ->
+        $('Hello World')
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+        mediator.unsubscribe '!router:route', spy
+
+        spy = jasmine.createSpy()
+        mediator.subscribe '!router:route', spy
+        $('Hello World')
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'should not route links with empty href', ->
+        # Technically an empty string is a valid relative URL
+        # but it doesn’t make sense to route it
+        $('Hello World')
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'should not route links to document fragments', ->
+        $('Hello World')
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'should not route links with a noscript class', ->
+        $('Hello World')
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'should not route links with a target="_blank" attribute', ->
+        $('Hello World')
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'should open external links in a new window', ->
+        $("Hello World")
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+    describe 'with routing param', ->
+      spy = null
+
+      beforeEach ->
+        spy = jasmine.createSpy()
+        mediator.subscribe '!router:route', spy
+        layout.dispose()
+
+      afterEach ->
+        mediator.unsubscribe '!router:route', spy
+
+      it 'routeLinks=false should NOT route clicks on internal links', ->
+        layout = new Layout
+          title: 'Test Site Title'
+          routeLinks: false
+        $("Hello World")
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'openExternalToBlank=false should NOT route clicks on external links', ->
+        layout = new Layout
+          title: 'Test Site Title'
+          openExternalToBlank: false
+        $("Hello World")
+          .appendTo(document.body)
+          .click()
+          .remove()
+        expect(spy).not.toHaveBeenCalled()
+
+      it 'skipRouting=false should route links with a noscript class', ->
+        layout = new Layout
+          title: 'Test Site Title'
+          skipRouting: false
+        path = '/an/internal/link'
+        $("Hello World")
+          .appendTo(document.body)
+          .click()
+          .remove()
+        args = spy.mostRecentCall.args
+        passedPath = args[0]
+        passedCallback = args[1]
+        expect(passedPath).toBe path
+        expect(typeof passedCallback).toBe 'function'
+
+
+    # Events hash
+    # ===========

     it 'should register event handlers on the document declaratively', ->
       spy1 = jasmine.createSpy()


sort feed by created_time

sort feed by created_time

--- api/feed/routes.coffee
+++ api/feed/routes.coffee
@@ -21,6 +21,7 @@
   Tag.find (err, tags)->

     feed = new Backbone.Collection null
+    feed.comparator = (model)-> - parseInt model.get('created_time')

     promises = _.map tags, getTagFeed


Add shorthand to register an one time event.

--- app/core/Entity.coffee
+++ app/core/Entity.coffee
@@ -71,6 +71,15 @@
     @_listeners[type] = [] unless @_listeners[type]?
     @_listeners[type].push listener

+  ###
+  Adds an event listener that will only be invoked once
+  @see Entity#addListener
+  ###
+  addOneTimeListener: (type, listener) =>
+    @addListener type, (event) =>
+      # call listener only once
+      @removeListener event.type, event.listener
+      listener.call @, event

   ###
   Fires an event, invoking all it's listeners.


Randomize position on tool create. Properly set settings_active flag default.

--- app/models/tool.coffee
+++ app/models/tool.coffee
@@ -12,7 +12,7 @@
   ]

   defaults:
-    active: true
+    settings_active: true
     data_source: {}
     height: 480
     settings: {}
@@ -24,6 +24,7 @@
     unless @get('channel') then @set 'channel', "#{@get('tool_type')}-#{@collection.length + 1}"

     if @isNew()
+      @generatePosition()
       @collection.focus @, false
       @save [],
         success: =>


Populate user on login

Populate user on login

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- app/models/user.coffee
+++ app/models/user.coffee
@@ -75,7 +75,7 @@

 schema.statics.verifyLogin = (username, password, cb) ->
-  @findOne {username: username.toLowerCase()}, (err, user) ->
+  @findOne(username: username.toLowerCase()).populate('players').populate('servers').exec (err, user) ->
     if err or not user
       return cb(err, null)


Basic parsing

Basic parsing


Issue #9. Fixed improper behavior of back button in some cases.

--- lib/assets/javascripts/wiselinks.js.coffee
+++ lib/assets/javascripts/wiselinks.js.coffee
@@ -14,6 +14,7 @@
     self = this

     @options = jQuery.extend(self._defaults(), @options);
+    @template_id = new Date().getTime()

     if self.enabled()
       @assets_digest = $("meta[name='assets-digest']").attr("content")
@@ -28,9 +29,12 @@
       History.Adapter.bind(
         window,
         "statechange"
-        (event, data) ->
-          state = History.getState()
-          self._call(state.url, state.data.target, state.data.render)
+        (event, data) ->
+          state = History.getState()
+          if self._template_id_changed(state)
+            self._call(state.url, null, 'template')
+          else
+            self._call(state.url, state.data.target, state.data.render)
       )

       $(document).on(
@@ -57,10 +61,11 @@
     !History.emulated.pushState || @options.html4 == true

   load: (url, target, render = 'template') ->
-    History.pushState({ timestamp: (new Date().getTime()), render: render, target: target }, document.title, url )
+    @template_id = new Date().getTime() if render != 'partial'
+    History.pushState({ template_id: @template_id, render: render, target: target }, document.title, url )

-  reload: () ->
-    History.replaceState({ timestamp: (new Date().getTime()), render: 'template' }, document.title, History.getState().url )
+  reload: () ->
+    History.replaceState({ template_id: @template_id, render: 'template' }, document.title, History.getState().url )

   _defaults: ->
     html4: true
@@ -157,6 +162,9 @@
   _assets_changed: (digest) ->
     @assets_digest? && @assets_digest != digest

+  _template_id_changed: (state) ->
+    !state.data.template_id? || state.data.template_id != @template_id
+
   _set_title: (xhr) ->
     value = xhr.getResponseHeader('X-Wiselinks-Title')
     document.title = decodeURI(value) if value?


use react components for renderable lists

--- src/context.coffee
+++ src/context.coffee
@@ -25,7 +25,7 @@
         true

   handle_renderable = (object) ->
-    if object?._lead_render
+    if is_renderable object
       @add_renderable object
       true

@@ -95,27 +95,43 @@
       vars: vars
       imported_vars: imported_vars

-  render = (o) ->
-    o._lead_render()
-
-  delayed_then_immediate_renderable_list_builder = ->
-    $item = $ '<div/>'
-    renderables = []
-    rendered = false
-    add_renderable: (renderable) ->
-      if rendered
-        $item.append render renderable
-      else
-        renderables.push renderable
+  render = (renderable) ->
+    wrapper = document.createElement 'div'
+    component = component_for_renderable renderable
+    React.renderComponent component, wrapper
+    $ wrapper
+
+  is_component = (o) -> o?._lifeCycleState?
+
+  is_renderable = (o) ->
+    o? and (is_component(o) or o._lead_render?)
+
+  renderable_component = React.createClass
+    render: -> React.DOM.div()
+    componentDidMount: (node) -> $(node).append @props.renderable._lead_render()
+
+  component_list = React.createClass
+    getInitialState: -> components: @components
+    add_component: (c) ->
+      @components ?= []
+      @components.push c
+      @setState components: @components if @state
     empty: ->
-      renderables.length = 0
-      $item.empty()
-    _lead_render: ->
-      unless rendered
-        rendered = true
-        children = _.map renderables, render
-        $item.append children
-      $item
+      @components = []
+      @setState components: @components if @state
+    render: ->
+      React.DOM.div {}, @state.components
+
+  component_for_renderable = (renderable) ->
+    if is_component renderable
+      renderable
+    else if is_component renderable._lead_render
+      renderable._lead_render
+    # TODO remove context special case
+    else if renderable.renderable_list_builder?
+      renderable.renderable_list_builder
+    else
+      renderable_component {renderable}

   create_run_context = (extra_contexts) ->
     result_handlers =[
@@ -132,12 +148,7 @@
       changes: new Bacon.Bus
       pending: asyncs.scan 0, (a, b) -> a + b
       current_options: {}
-      renderable_list_builder: delayed_then_immediate_renderable_list_builder()
-      _lead_render: ->
-        result = render @renderable_list_builder
-        @changes.push true
-        result
-
+      renderable_list_builder: component_list()
       running_context: -> running_context_binding

       options: -> @current_options
@@ -173,19 +184,17 @@
           restoring_context fn, arguments

       add_renderable: (renderable) ->
-        @renderable_list_builder.add_renderable renderable
+        @add_component component_for_renderable renderable
         ignore

       add_component: (component) ->
-        @add_rendering ->
-          wrapper = document.createElement 'div'
-          React.renderComponent component, wrapper
-          wrapper
+        @renderable_list_builder.add_component component

       add_rendered: (rendered) ->
         @add_rendering -> rendered

-      # adds a function that can render
+      # adds a function that can render.
+      # the function will be called in the current context.
       add_rendering: (rendering) -> @add_renderable _lead_render: @keeping_context(rendering)

       render: render
@@ -204,18 +213,20 @@

       # makes o renderable using the given function or renderable
       renderable: (o, fn) ->
-        if fn._lead_render?
+        if is_component fn
+          o._lead_render = fn
+        else if fn._lead_render?
           o._lead_render = fn._lead_render
         else
           nested_context = @create_nested_context
-            renderable_list_builder: add_renderable: -> throw new Error 'Output functions not allowed inside a renderable'
+            renderable_list_builder: add_component: -> throw new Error 'Output functions not allowed inside a renderable'
           o._lead_render = -> nested_context.apply_to fn
         o

-      create_nested_renderable_context: ($item) ->
-        renderable = delayed_then_immediate_renderable_list_builder()
+      create_nested_renderable_context: ->
+        component = component_list()
         @create_nested_context
-          renderable_list_builder: renderable
+          renderable_list_builder: component

       create_nested_context: (overrides) ->
         nested_context = _.extend create_new_run_context(@), overrides
@@ -228,7 +239,7 @@
       # creates a nested context, adds it to the renderable list, and applies the function to it
       nested_item: (fn, args...) ->
         nested_context = @create_nested_renderable_context()
-        @add_renderable nested_context.renderable_list_builder
+        @add_component nested_context.renderable_list_builder
         nested_context.apply_to fn, args

       display_object: (object) ->
@@ -330,7 +341,10 @@
     run_in_context,
     eval_coffeescript_in_context,
     eval_in_context,
-    render,
+    render: (ctx) ->
+      result = render ctx
+      ctx.changes.push true
+      result
     scope,
     collect_extension_points
   }


code-peek -> goto-definition

code-peek -> goto-definition

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -30,6 +30,8 @@
   "git-plus":
     remoteInteractions:
       pullBeforePush: true
+  "goto-definition":
+    performanceMode: true
   latex:
     buildOnSave: true
     opener: "pdf-view"


atom: more config

atom: more config

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -36,6 +36,8 @@
       pycodestyle:
         maxLineLength: 88
     python: "/usr/bin/python"
+  "linter-ui-default":
+    panelHeight: 238
   minimap:
     plugins:
       "git-diff": true
@@ -86,5 +88,7 @@
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
+  wakatime:
+    apikey: "Saved in your ~/.wakatime.cfg file"
   welcome:
     showOnStartup: false


split create into fetch commands, create, stop to allow interaction before creation

--- src/decking.coffee
+++ src/decking.coffee
@@ -144,38 +144,57 @@
     # of dependency resolution ourselves
     target = getCluster @config, cluster

-    iterator = (details, callback) ->
+    commands = []
+
+    fetchIterator = (details, callback) ->
       name = details.name
-
       container = docker.getContainer name
+
+      command =
+        name: name
+        container: container
+
       container.inspect (err, data) ->
         if not err
-          # already exists, BUT it might be a dependency so it needs starting
-          #log "Container #{name} already exists, skipping..."
-          # @TODO check if this container has dependents or not...
-          logAction name, "already exists - running in case of dependents"
-          return container.start callback
+          command.exists = true
+          commands.push command
+          return callback null

         cmdArgs = ["docker", "run", "-d", "-name", "#{name}"]
+        # @TODO allow getRunArg to prompt if necessary
         cmdArgs = [].concat cmdArgs, getRunArg key, val, details for key,val of details
         cmdArgs.push details.image

-        cmdString = cmdArgs.join " "
-
-        logAction name, "creating..."
-
-        child_process.exec cmdString, callback
+        command.exec = cmdArgs.join " "
+        commands.push command
+
+        callback null
+
+    createIterator = (command, callback) ->
+      name = command.name
+
+      if command.exists
+        # already exists, BUT it might be a dependency so it needs starting
+        #log "Container #{name} already exists, skipping..."
+        # @TODO check if this container has dependents or not...
+        logAction name, "already exists - running in case of dependents"
+        return command.container.start callback
+
+      logAction name, "creating..."
+
+      child_process.exec command.exec, callback

     stopIterator = (details, callback) ->
       container = docker.getContainer details.name
       container.stop callback

     resolveOrder @config, target, (list) ->
-      async.eachSeries list, iterator, ->
-        # @FIXME hack to avoid ghosts with quick start/stop combos
-        setTimeout ->
-          async.eachLimit list, 5, stopIterator, done
-        , 500
+      async.eachSeries list, fetchIterator, ->
+        async.eachSeries commands, createIterator, ->
+          # @FIXME hack to avoid ghosts with quick start/stop combos
+          setTimeout ->
+            async.eachLimit list, 5, stopIterator, done
+          , 500

   build: (image, done) ->

@@ -272,6 +291,10 @@
     when "env"
       for v,k in val
         [key, value] = v.split "="
+        # @TODO maybe:
+        # -  = required from env
+        # -? = optional from env
+        # -! = required from env, prompt if not supplied
         if value is "-" then value = process.env[key]
         arg = [].concat arg, ["-e", "#{key}=#{value}"]


more work

more work

--- lib/sails-migrations/database_tasks.coffee
+++ lib/sails-migrations/database_tasks.coffee
@@ -2,9 +2,10 @@
 LOCAL_HOSTS = ['127.0.0.1', 'localhost']
 class DatabaseTasks
   @create: (adapter, cb)->
+    console.log(adapter.config)
     switch adapter.identity
       when 'sails-mysql'
-        exec("mysql -u #{adapter.config.username} -p#{adapter.config.password} -e 'CREATE DATABASE IF NOT EXISTS #{adapter.config.database}'", (err, stdout, stdin)->
+        exec("mysql -h #{adapter.config.host} -u #{adapter.config.user} -p#{adapter.config.password} -e 'CREATE DATABASE IF NOT EXISTS #{adapter.config.database}'", (err, stdout, stdin)->
           console.log(arguments)
           return cb(err)
         )


random

random

--- src/random.coffee
+++ src/random.coffee
@@ -48,9 +48,8 @@

   @name rand
   @methodOf window
-
   @param {Number} n
-  @type Number
+  @returns {Number} A random integer from 0 to n - 1 if n is given. If n is not given, a random float between 0 and 1.
   ###
   (exports ? this)["rand"] = (n) ->
     if n


Make sure watch ignores files within output directory

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -156,7 +156,13 @@
         if typeof f is 'object' and curr is null and prev is null
           # Finished walking tree, ignore
           return
-        else if prev is null
+
+        # Ignore files within destination directory
+        fullPath = path.resolve f
+        if fullPath.substr(0, realDestination.length) is realDestination
+          return
+
+        if prev is null
           # New file
           generateDebounced config, ->
             console.log 'Updated due to new file'


Removed extraneous data variable

Removed extraneous data variable

--- src/routes.coffee
+++ src/routes.coffee
@@ -61,7 +61,6 @@
     console.warn "controller not found: " + controllerName, e
     next()
     return
-  data = null
   if typeof controller[methodName] is 'function'
     actionMethod = controller[methodName].bind controller
     actionMethod req, res, next


Tweak ctor regex

Tweak ctor regex

Fixes #65

--- src/haskell.coffee
+++ src/haskell.coffee
@@ -70,8 +70,8 @@
       |(?:(?!deriving)(?:[\w()'→⇒\[\],]|->|=>)+\s*)+ #anything goes!
       )
       ///
-    ctor: concat /\b({className})\s+/,
-      listMaybe('ctorArgs', /{ctorArgs}/, /\s+/)
+    ctor: concat /\b({className})\b/,
+      listMaybe('ctorArgs', /\s+{ctorArgs}/, '')
     typeDeclOne: /(?:(?!\bwhere(?!')\b)(?:{className}|{functionName}))/
     typeDecl: '(?>(?:{typeDeclOne})(?:\\s+{typeDeclOne})*)'
     indentChar: /[ \t]/


Change argument order

Change argument order

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -126,7 +126,7 @@
     return callback stderr.toString() if error?
     callback null, stdout

-startDefaultServer = (port, path, callback, base = '') ->
+startDefaultServer = (port, path, base, callback) ->
   server = express.createServer()
   server.use (request, response, next) ->
     response.header 'Cache-Control', 'no-cache'
@@ -149,7 +149,7 @@
     catch error
       logger.error "couldn\'t load server #{config.server.path}: #{error}"
   else
-    startDefaultServer config.server.port, config.paths.public, onListening, config.server.base
+    startDefaultServer config.server.port, config.paths.public, config.server.base, onListening

 exports.replaceSlashes = replaceSlashes = (config) ->
   changePath = (string) -> string.replace(/\//g, '\\')


Remove modules.addSourceUrls, add config.sourceMaps.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -219,12 +219,12 @@
   conventions.vendor  ?= /vendor(\/|\\)/

   config.notifications ?= on
+  config.sourceMaps   ?= yes
   config.optimize     ?= no

   modules              = config.modules      ?= {}
   modules.wrapper     ?= 'commonjs'
   modules.definition  ?= 'commonjs'
-  modules.addSourceURLs ?= no

   config.server       ?= {}
   config.server.base  ?= ''


Only listen for data packets, not brackets

--- src/runner.coffee
+++ src/runner.coffee
@@ -99,7 +99,12 @@
     return received

   checkSuccess = (s) ->
-    debug 'runtest got output on', s.payload.port
+    # We're only interested in response packets
+    return false unless s.protocol is 'runtime' and s.command is 'packet'
+    # Check that is for the current graph under test
+    return false unless s.payload.graph is currentGraph
+    # We're only interested in data IPs, not brackets
+    return false unless s.payload.event is 'data'
     received = signalsToReceived observer.signals
     result = (Object.keys(received).length == Object.keys(expectData).length)
     return result
@@ -116,6 +121,8 @@
       # Output packet, see if it is an unexpected error
       # Check that is for the current graph under test
       return false unless s.payload.graph is currentGraph
+      # We're only interested in data IPs, not brackets
+      return false unless s.payload.event is 'data'
       # We only care for packets to error port
       return false unless s.payload.port is 'error'
       # We only care if spec isn't expecting errors


Fixing incorrect exceptions due to init order

--- src/scenes.coffee
+++ src/scenes.coffee
@@ -25,7 +25,7 @@
   loadScenes: (argList=[]) ->
     sceneNum = 1
     for args in argList
-      @scenes.push new Kona.Scene(Kona.Utils.merge { name: "s#{sceneNum}" }, args)
+      scene = new Kona.Scene(Kona.Utils.merge { name: "s#{sceneNum}" }, args)
       sceneNum++

     @currentScene = @scenes[0]
@@ -60,7 +60,8 @@
     @background     = new Image()
     @background.src = opts.background || ''
     @entities       = {}
-    @loadEntities(opts.entities)
+    @loadEntities(opts.entities) if opts.entities?
+    Kona.Scenes.scenes.push(@)

   # Add a single entity to a named group


It turns out if we don't specify the hostname for the web socket server, it works on both localhost and remote devices at the same time.

--- src/server.coffee
+++ src/server.coffee
@@ -29,7 +29,7 @@
     @connections = []

   start: ->
-    server = new WebSocketServer {host: 'localhost', port: @port}
+    server = new WebSocketServer {port: @port}
     server.on 'connection', (c) =>
       @connections.push c
       c.on 'close', =>


fix minor bug in Sprite._update

fix minor bug in Sprite._update

The removal of Greenhorn.set and Greenhorn.get created a very small
bug in Sprite._update, which used the get function to find the canvas
boundaries. I have implemented a simple work-around.

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -91,13 +91,13 @@
         _list.sort _sortRule

     #_update control
+    isRunning: ->
+        @_updateID?
     _start: ->
         @_updateID = setInterval @_update, 1000 / env.FRAME_RATE
     _stop: ->
         clearInterval @_updateID
         @_updateID = null
-    isRunning: ->
-        @_updateID?

     #getter
     get: (what) ->
@@ -300,12 +300,14 @@
             @change '_mot', @_acc
             @change '_pos', @_mot

-            #check boundaries
+            #define boundaries
+            canvasWidth = document.querySelector('#gh-canvas').width
+            canvasHeight = document.querySelector('#gh-canvas').height
             bounds =
-                top: Greenhorn.get('canvas', 'height') / 2
-                bottom: -Greenhorn.get('canvas', 'height') / 2
-                right: Greenhorn.get('canvas', 'width') / 2
-                left: -Greenhorn.get('canvas', 'width') / 2
+                top: canvasHeight / 2
+                bottom: -canvasHeight / 2
+                right: canvasWidth / 2
+                left: -canvasWidth / 2

             #sprite has completely disappeared offscreen
             offTop = @get('bottom') > bounds.top


Update dynamic beat lengths

Update dynamic beat lengths

--- src/stdlib.coffee
+++ src/stdlib.coffee
@@ -176,7 +176,7 @@

   invalidateBeatLength: ->
     for descriptor in @callbackDescriptors
-      if descriptor.interval.hasBeatValue()
+      if descriptor.interval.hasValueInBeats()
         descriptor.interval = descriptor.interval.beats()


Set maxTokensPerLine to 100

Set maxTokensPerLine to 100

Previously this default lived in firs-mate but it belongs here since
it is an Atom limitation to tokenize responsively.

--- src/syntax.coffee
+++ src/syntax.coffee
@@ -26,7 +26,7 @@
     syntax

   constructor: ->
-    super
+    super(maxTokensPerLine: 100)
     @propertyStore = new ScopedPropertyStore

   serialize: ->


More typos

More typos

--- src/hubot-coininfo.coffee
+++ src/hubot-coininfo.coffee
@@ -155,7 +155,7 @@
       avg = d.ticker.avg.toFixed(8)
       updated = d.ticker.updated
       date = new Date(updated*1000).toUTCString()
-      msg.reply "#[OK] {cU}-BTC on C-CEX [Last: #{last}] -
+      msg.reply "[OK] #{cU}-BTC on C-CEX [Last: #{last}] -
       [High: #{high}] - [Low: #{low}] - [Avg: #{avg}]"
       # - [Updated: #{date}]

@@ -177,7 +177,7 @@
       high = d.data["24hr"].price_high.toFixed(8)
       low = d.data["24hr"].price_low.toFixed(8)
       vol = d.data["24hr"].volume
-      msg.reply "#[OK] {c}-BTC on Cryptsy [Last: #{last}] -
+      msg.reply "[OK] #{c}-BTC on Cryptsy [Last: #{last}] -
       [High: #{high}] - [Low: #{low}] - [Vol: #{vol}]"

   #Bter


Update to new compile cache path

Update to new compile cache path

--- src/install.coffee
+++ src/install.coffee
@@ -473,16 +473,16 @@
   warmCompileCache: (packageName, callback) ->
     packageDirectory = path.join(@atomPackagesDirectory, packageName)

-    @getResourcePath (resourcePath) ->
+    @getResourcePath (resourcePath) =>
       try
-        CompileCache = require(path.join(resourcePath, 'src', 'coffee-cache'))
+        CompileCache = require(path.join(resourcePath, 'src', 'compile-cache'))

         onDirectory = (directoryPath) ->
           path.basename(directoryPath) isnt 'node_modules'

-        onFile = (filePath) ->
+        onFile = (filePath) =>
           try
-            CompileCache.addPathToCache(filePath)
+            CompileCache.addPathToCache(filePath, @atomDirectory)

         fs.traverseTreeSync(packageDirectory, onFile, onDirectory)
       callback(null)


Fix doc strings

Fix doc strings

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -94,15 +94,15 @@

   # Public: Create a keydown DOM event for testing purposes.
   #
-  # * `key` The key or keyIdentifier of the event. For example, 'a', '1',
-  #         'escape', 'backspace', etc.
-  # * options An {Object} containing any of the following:
+  # * `key` The key or keyIdentifier of the event. For example, `'a'`, `'1'`,
+  #   `'escape'`, `'backspace'`, etc.
+  # * `options` (optional) An {Object} containing any of the following:
   #   * `ctrl`   A {Boolean} indicating the ctrl modifier key
   #   * `alt`    A {Boolean} indicating the alt modifier key
   #   * `shift`  A {Boolean} indicating the shift modifier key
   #   * `cmd`    A {Boolean} indicating the cmd modifier key
   #   * `which`  A {Number} indicating `which` value of the event. See
-  #              the docs for KeyboardEvent for more information.
+  #     the docs for KeyboardEvent for more information.
   #   * `target` The target element of the event.
   @keydownEvent: (key, options) -> keydownEvent(key, options)

@@ -117,11 +117,10 @@
   # Public: Create a new KeymapManager.
   #
   # * `options` An {Object} containing properties to assign to the keymap.  You
-  #             can pass custom properties to be used by extension methods. The
-  #             following properties are also supported:
+  #   can pass custom properties to be used by extension methods. The
+  #   following properties are also supported:
   #   * `defaultTarget` This will be used as the target of events whose target
-  #                     is `document.body` to allow for a catch-all element when
-  #                     nothing is focused.
+  #     is `document.body` to allow for a catch-all element when nothing is focused.
   constructor: (options={}) ->
     @[key] = value for key, value of options
     @keyBindings = []
@@ -144,9 +143,9 @@
   # Public: Add sets of key bindings grouped by CSS selector.
   #
   # * `source` A {String} (usually a path) uniquely identifying the given bindings
-  #            so they can be removed later.
+  #   so they can be removed later.
   # * `bindings` An {Object} whose top-level keys point at sub-objects mapping
-  #              keystroke patterns to commands.
+  #   keystroke patterns to commands.
   add: (source, keyBindingsBySelector) ->
     for selector, keyBindings of keyBindingsBySelector
       # Verify selector is valid before registering any bindings
@@ -167,11 +166,10 @@
   # Public: Load the key bindings from the given path.
   #
   # * `path` A {String} containing a path to a file or a directory. If the path is
-  #          a directory, all files inside it will be loaded.
+  #   a directory, all files inside it will be loaded.
   # * `options` An {Object} containing the following optional keys:
   #   * `watch` If `true`, the keymap will also reload the file at the given
-  #             path whenever it changes. This option cannot be used with
-  #             directory paths.
+  #     path whenever it changes. This option cannot be used with directory paths.
   loadKeymap: (bindingsPath, options) ->
     checkIfDirectory = options?.checkIfDirectory ? true
     if checkIfDirectory and fs.isDirectorySync(bindingsPath)
@@ -200,7 +198,7 @@
   # Public: Remove the key bindings added with {::add} or {::loadKeymap}.
   #
   # * `source` A {String} representing the `source` in a previous call to
-  #            {::add} or the path in {::loadKeymap}.
+  #   {::add} or the path in {::loadKeymap}.
   remove: (source) ->
     @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source
     undefined
@@ -295,13 +293,12 @@
   #
   # * `params` An {Object} whose keys constrain the binding search:
   #   * `keystrokes` A {String} representing one or more keystrokes, such as
-  #                  'ctrl-x ctrl-s'
+  #     'ctrl-x ctrl-s'
   #   * `command` A {String} representing the name of a command, such as
-  #               'editor:backspace'
+  #     'editor:backspace'
   #   * `target` An optional DOM element constraining the search. If this
-  #              parameter is supplied, the call will only return bindings that
-  #              can be invoked by a KeyboardEvent originating from the target
-  #              element.
+  #     parameter is supplied, the call will only return bindings that
+  #     can be invoked by a KeyboardEvent originating from the target element.
   #
   # Returns an {Array} of key bindings.
   findKeyBindings: (params={}) ->


remove stray logging

remove stray logging

--- src/lemonade-stand.coffee
+++ src/lemonade-stand.coffee
@@ -24,7 +24,6 @@
       webpackConfig = require '../webpack.config'
       webpackConfig.entry.unshift('webpack-hot-middleware/client?reload=true')
       webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin())
-      console.dir webpackConfig
       compiler = webpack(webpackConfig)

       exp.use require('webpack-dev-middleware')(compiler, {


Add back testing to Gruntfile

Add back testing to Gruntfile

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -288,7 +288,7 @@
   ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
   ciTasks.push('mkdeb') if process.platform is 'linux'
   ciTasks.push('mktar') if process.platform is 'linux'
-  # ciTasks.push('test') if process.platform is 'darwin'
+  ciTasks.push('test') if process.platform is 'darwin'
   ciTasks.push('codesign:app') if process.platform is 'darwin' and not process.env.CI
   if process.platform is 'win32'
     ciTasks.push('codesign:exe') if process.env.JANKY_SIGNTOOL


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -147,7 +147,7 @@
       width: 100

     if not I[options.name]
-      I[I.name] = 100
+      I[options.name] = 100

     if not I["max#{I.name.capitalize()}"]
       if I[I.name]


REFACTOR remove unused line, set defualt mainAttr value

--- src/modules/helper.coffee
+++ src/modules/helper.coffee
@@ -12,7 +12,7 @@

   constructor: (opts)->
     super
-    @_setAccessor '_main', opts.mainAttr
+    @_setAccessor '_main', opts.mainAttr or ""

     if @attributes
       for attr in @attributes
@@ -104,7 +104,6 @@
             break
           else unless controller.parent
             @scope._resolves[key].listeners.push controller: @parent, key: chain
-            @scope._resolves[key].triggers.push controller: @parent, key: chain if chain in triggers and chain isnt rootKey
             @_createObjectChain @parent.scope, chain, evaluate

     getter = @_getter(@parent.scope, parsedEvaluate, evaluate)


Fixes #132 for Linux JCL.

Fixes #132 for Linux JCL.

--- src/natives.coffee
+++ src/natives.coffee
@@ -111,6 +111,8 @@
     nio:
       Bits: [
         o 'byteOrder()L!/!/ByteOrder;', (rs) -> rs.static_get {class:'java/nio/ByteOrder',name:'LITTLE_ENDIAN'}
+        o 'copyToByteArray(JLjava/lang/Object;JJ)V', (rs, srcAddr, dst, dstPos, length) ->
+          unsafe_memcpy rs, null, srcAddr, dst, dstPos, length
       ]
       charset:
         Charset$3: [
@@ -164,6 +166,45 @@
     j++
   # CoffeeScript, we are not returning an array.
   return
+
+unsafe_memcpy = (rs, src_base, src_offset, dest_base, dest_offset, num_bytes) ->
+  # XXX assumes base object is an array if non-null
+  # TODO: optimize by copying chunks at a time
+  num_bytes = num_bytes.toNumber()
+  if src_base?
+    src_offset = src_offset.toNumber()
+    if dest_base?
+      # both are java arrays
+      arraycopy_no_check(src_base, src_offset, dest_base, dest_offset.toNumber(), num_bytes)
+    else
+      # src is an array, dest is a mem block
+      dest_addr = rs.block_addr(dest_offset)
+      if DataView?
+        for i in [0...num_bytes] by 1
+          rs.mem_blocks[dest_addr].setInt8(i, src_base.array[src_offset+i])
+      else
+        for i in [0...num_bytes] by 1
+          rs.mem_blocks[dest_addr+i] = src_base.array[src_offset+i]
+  else
+    src_addr = rs.block_addr(src_offset)
+    if dest_base?
+      # src is a mem block, dest is an array
+      dest_offset = dest_offset.toNumber()
+      if DataView?
+        for i in [0...num_bytes] by 1
+          dest_base.array[dest_offset+i] = rs.mem_blocks[src_addr].getInt8(i)
+      else
+        for i in [0...num_bytes] by 1
+          dest_base.array[dest_offset+i] = rs.mem_blocks[src_addr+i]
+    else
+      # both are mem blocks
+      dest_addr = rs.block_addr(dest_offset)
+      if DataView?
+        for i in [0...num_bytes] by 1
+          rs.mem_blocks[dest_addr].setInt8(i, rs.mem_blocks[src_addr].getInt8(i))
+      else
+        for i in [0...num_bytes] by 1
+          rs.mem_blocks[dest_addr+i] = rs.mem_blocks[src_addr+i]

 unsafe_compare_and_swap = (rs, _this, obj, offset, expected, x) ->
   actual = obj.get_field_from_offset rs, offset
@@ -989,42 +1030,7 @@
             rs.mem_start_addrs.push(next_addr + size)
             return gLong.fromNumber(next_addr)
         o 'copyMemory(Ljava/lang/Object;JLjava/lang/Object;JJ)V', (rs, _this, src_base, src_offset, dest_base, dest_offset, num_bytes) ->
-            # XXX assumes base object is an array if non-null
-            # TODO: optimize by copying chunks at a time
-            if src_base?
-              src_offset = src_offset.toNumber()
-              if dest_base?
-                # both are java arrays
-                arraycopy_no_check(src_base, src_offset, dest_base, dest_offset.toNumber(), num_bytes)
-              else
-                # src is an array, dest is a mem block
-                dest_addr = rs.block_addr(dest_offset)
-                if DataView?
-                  for i in [0...num_bytes] by 1
-                    rs.mem_blocks[dest_addr].setInt8(i, src_base.array[src_offset+i])
-                else
-                  for i in [0...num_bytes] by 1
-                    rs.mem_blocks[dest_addr+i] = src_base.array[src_offset+i]
-            else
-              src_addr = rs.block_addr(src_offset)
-              if dest_base?
-                # src is a mem block, dest is an array
-                dest_offset = dest_offset.toNumber()
-                if DataView?
-                  for i in [0...num_bytes] by 1
-                    dest_base.array[dest_offset+i] = rs.mem_blocks[src_addr].getInt8(i)
-                else
-                  for i in [0...num_bytes] by 1
-                    dest_base.array[dest_offset+i] = rs.mem_blocks[src_addr+i]
-              else
-                # both are mem blocks
-                dest_addr = rs.block_addr(dest_offset)
-                if DataView?
-                  for i in [0...num_bytes] by 1
-                    rs.mem_blocks[dest_addr].setInt8(i, rs.mem_blocks[src_addr].getInt8(i))
-                else
-                  for i in [0...num_bytes] by 1
-                    rs.mem_blocks[dest_addr+i] = rs.mem_blocks[src_addr+i]
+            unsafe_memcpy rs, src_base, src_offset, dest_base, dest_offset, num_bytes
         o 'setMemory(JJB)V', (rs, _this, address, bytes, value) ->
             block_addr = rs.block_addr(address)
             for i in [0...bytes] by 1
@@ -1094,11 +1100,17 @@
             obj.set_field_from_offset rs, offset, new_obj
         o 'defineClass(Ljava/lang/String;[BIILjava/lang/ClassLoader;Ljava/security/ProtectionDomain;)Ljava/lang/Class;', (rs, _this, name, bytes, offset, len, loader, pd) ->
             native_define_class rs, name, bytes, offset, len, loader
+        o 'pageSize()I', (rs) ->
+            # Keep this in sync with sun/nio/ch/FileChannelImpl/initIDs for Mac
+            # JCL compatibility.
+            1024
       ]
     nio:
       ch:
         FileChannelImpl: [
           # this poorly-named method actually specifies the page size for mmap
+          # This is the Mac name for sun/misc/Unsafe::pageSize. Apparently they
+          # wanted to ensure page sizes can be > 2GB...
           o 'initIDs()J', (rs) -> gLong.fromNumber(1024)  # arbitrary
         ]
         FileDispatcher: [
@@ -1119,6 +1131,13 @@
             return bytes_read
           o 'preClose0(Ljava/io/FileDescriptor;)V', (rs, fd_obj) ->
             # NOP, I think the actual fs.close is called later. If not, NBD.
+        ]
+        NativeThread: [
+          o "init()V", (rs) -> # NOP
+          o "current()J", (rs) ->
+              # -1 means that we do not require signaling according to the
+              # docs.
+              gLong.fromNumber(-1)
         ]
     reflect:
       ConstantPool: [


Require project directories to exist on deserialization

--- src/project.coffee
+++ src/project.coffee
@@ -72,7 +72,7 @@
       bufferPromises.push(TextBuffer.deserialize(bufferState))
     Promise.all(bufferPromises).then (@buffers) =>
       @subscribeToBuffer(buffer) for buffer in @buffers
-      @setPaths(state.paths)
+      @setPaths(state.paths, mustExist: true)

   serialize: (options={}) ->
     deserializer: 'Project'
@@ -234,7 +234,7 @@
     if added
       @emitter.emit 'did-change-paths', projectPaths

-    if options.mustExist is true and missingProjectPaths
+    if options.mustExist is true and missingProjectPaths.length > 0
       err = new Error "One or more project directories do not exist"
       err.missingProjectPaths = missingProjectPaths
       throw err


fix repository detection on case-insensitive fs

--- src/publish.coffee
+++ src/publish.coffee
@@ -263,7 +263,7 @@
     currentDirectory = process.cwd()

     repo = Git.open(currentDirectory)
-    if repo?.getWorkingDirectory() isnt currentDirectory
+    if repo?.getWorkingDirectory().toLowerCase() isnt currentDirectory.toLowerCase()
       throw new Error('Package must be in a Git repository before publishing: https://help.github.com/articles/create-a-repo')

     unless repo.getConfigValue('remote.origin.url')


Make PaneContainer::getActivePaneItem retrieve it from the model

--- src/pane-container.coffee
+++ src/pane-container.coffee
@@ -99,7 +99,7 @@
     @find('.pane.active').view() ? @find('.pane:first').view()

   getActivePaneItem: ->
-    @getActivePane()?.activeItem
+    @model.activePaneItem

   getActiveView: ->
     @getActivePane()?.activeView


Spacing

Spacing

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -55,7 +55,7 @@
             # need to start a process to quit when existing connections disconnect
             @_shutdownTimeout = setInterval =>
                 # have we used up the grace period yet?
-                force_shut = if (new Date).getTime() > @_shutdownMaxTime then true else false
+                force_shut = if (new Date).getTime() > @_shutdownMaxTime then true else false

                 if @listeners == 0 || force_shut
                     # everyone's out...  go ahead and shut down
@@ -316,6 +316,3 @@

         conn.once "close", conn_pre_abort
         conn.once "end", conn_pre_abort
-
-
-


the bandwidth calculation was done with bits per second Flash only wants bytes per second

--- src/publish_stream.coffee
+++ src/publish_stream.coffee
@@ -192,7 +192,14 @@
       streamWidth: userOrDefault(@publishOptions, 'streamWidth')
       streamHeight: userOrDefault(@publishOptions, 'streamHeight')
       streamFPS: userOrDefault(@publishOptions, 'streamFPS')
-      bandwidth: userOrDefault(@publishOptions, 'bandwidth') * 1024 * 8
+
+      # Kbps uses bites per second
+      # but action script wants bytes per seconds
+      # http://help.adobe.com/en_US/AS2LCR/Flash_10.0/help.html?content=00000880.html
+      # so we multiply by 1024 to get the K
+      # AS will then multiply by 8 to get bits
+      bandwidth: userOrDefault(@publishOptions, 'bandwidth') * 1024
+
       videoQuality: userOrDefault(@publishOptions, 'videoQuality')
       embedTimecode: userOrDefault(this.publishOptions, "embedTimecode"),
       timecodeFrequency: userOrDefault(this.publishOptions, "timecodeFrequency")


fix #51

fix #51

--- src/routes/monitor.coffee
+++ src/routes/monitor.coffee
@@ -20,8 +20,10 @@
 			stats[modelName] = {}
 			model.collection.indexInformation (err, indexInfo) ->
 				stats[modelName].indexedFields = []
+				console.log indexInfo
 				for k,v of indexInfo
 					continue if v[0][0] is '_id'
+					continue if v[0][0] in stats[modelName].indexedFields
 					stats[modelName].indexedFields.push v[0][0]
 				model.count (err, nr) ->
 					stats[modelName].count = nr


Replace !== with ? (the coffeescript null check)
!== causes compilation problems with the most recent version of coffeescript and hubot

Mon Dec 02 2013 16:04:50 GMT-0800 (PST)] ERROR Unable to load /src/hubot-scripts/src/scripts/polite: SyntaxError: unexpected =
  at Object.exports.throwSyntaxError (/src/hubot/node_modules/coffee-script/lib/coffee-script/helpers.js:197:13)
  at Object.parser.yy.parseError (/src/hubot/node_modules/coffee-script/lib/coffee-script/coffee-script.js:265:20)
  at Object.parse (/src/hubot/node_modules/coffee-script/lib/coffee-script/parser.js:537:22)
  at exports.compile.compile (/src/hubot/node_modules/coffee-script/lib/coffee-script/coffee-script.js:35:24)
  at Object.loadFile (/src/hubot/node_modules/coffee-script/lib/coffee-script/coffee-script.js:176:14)
  at Module.load (/src/hubot/node_modules/coffee-script/lib/coffee-script/coffee-script.js:211:36)
  at Function.Module._load (module.js:312:12)
  at Module.require (module.js:364:17)
  at require (module.js:380:17)
  at Robot.loadFile (/src/hubot/src/robot.coffee:181:9, <js>:133:11)
  at Robot.loadHubotScripts (/src/hubot/src/robot.coffee:208:7, <js>:166:28)
  at /src/hubot/bin/hubot:98:15, <js>:99:30
  at fs.js:266:14
  at Object.oncomplete (fs.js:107:15)

--- src/scripts/polite.coffee
+++ src/scripts/polite.coffee
@@ -40,7 +40,7 @@
 youTalkinToMe = (msg, robot) ->
   input = msg.message.text.toLowerCase()
   name = robot.name.toLowerCase()
-  input.match(new RegExp('\\b' + name + '\\b', 'i')) !== null
+  input.match(new RegExp('\\b' + name + '\\b', 'i'))?

 module.exports = (robot) ->
   robot.hear /\b(thanks|thank you|cheers|nice one)\b/i, (msg) ->


extended rotten to support more of the rotten tomatoes api

--- src/scripts/rotten.coffee
+++ src/scripts/rotten.coffee
@@ -8,34 +8,140 @@
 #   HUBOT_ROTTEN_TOMATOES_API_KEY
 #
 # Commands:
-#   hubot rotten me <movie>
+#   hubot rotten [me] <movie>
+#   hubot what's in theaters?
+#   hubot what's coming out in theaters?
+#   hubot what's coming out on (dvd|bluray)? - there is not a distinction between dvd and bluray
 #
 # Author:
 #   mportiz08

+class Rotten
+  constructor: (@robot) ->
+    @api_url = "http://api.rottentomatoes.com/api/public/v1.0"
+    @api_key = process.env.HUBOT_ROTTEN_TOMATOES_API_KEY
+
+  _links: (match, callback) =>
+    return callback @__links[match] if @__links
+    @send "#{@api_url}/lists.json", {},
+      (err, res, body) =>
+        @__links = JSON.parse(body)['links']
+        callback @__links[match]
+
+  _movie_links: (match, callback) =>
+    return callback @__movie_links[match] if @__movie_links
+    @_links 'movies',
+      (link) =>
+        @send link, {},
+          (err, res, body) =>
+            @__movie_links = JSON.parse(body)['links']
+            callback @__movie_links[match]
+
+  _dvd_links: (match, callback) =>
+    return callback @__dvd_links[match] if @__dvd_links
+    @_links 'dvds',
+      (link) =>
+        @send link, {},
+          (err, res, body) =>
+            @__dvd_links = JSON.parse(body)['links']
+            callback @__dvd_links[match]
+
+  in_theaters: (callback) =>
+    @_movie_links 'in_theaters',
+      (match) =>
+        @send match,
+          page_limit: 20
+          country: 'us'
+          (err, res, body) ->
+            movies = JSON.parse(body)['movies']
+
+            unless err? or movies?
+              return callback("Couldn't find anything, sorry.")
+
+            callback null, (new RottenMovie(movie) for movie in movies)
+
+  upcoming: (type, callback) =>
+    link_list = switch type
+      when 'movies'
+        @_movie_links
+      when 'dvds'
+        @_dvd_links
+      else
+        @_movie_links
+
+    link_list 'upcoming',
+      (match) =>
+        @send match,
+          page_limit: 20
+          country: 'us'
+          (err, res, body) ->
+            movies = JSON.parse(body)['movies']
+
+            unless err? or movies?
+              return callback("Couldn't find anything, sorry.")
+
+            callback null, (new RottenMovie(movie) for movie in movies)
+
+  search: (query, callback) =>
+    @send "#{@api_url}/movies.json",
+      q: query
+      page_limit: 1
+      (err, res, body) ->
+        movie = JSON.parse(body)['movies'][0]
+
+        unless err? or movie?
+          return callback("Couldn't find anything, sorry.")
+
+        callback null, new RottenMovie(movie)
+
+    return
+
+  send: (url, options, callback) =>
+    options.apikey = @api_key
+    @robot.http(url).query(options).get()(callback)
+
+class RottenMovie
+  constructor: (@info) ->
+
+  toDetailedString: ->
+    "#{@info['title']} (#{@info['year']})\n" +
+    "#{@info['runtime']} min, #{@info['mpaa_rating']}\n\n" +
+    "Critics:\t" + "#{@info['ratings']['critics_score']}%" +
+      "\t\"#{@info['ratings']['critics_rating']}\"\n" +
+    "Audience:\t" + "#{@info['ratings']['audience_score']}%" +
+      "\t\"#{@info['ratings']['audience_rating']}\"\n\n" +
+    "#{@info['critics_consensus']}"
+
+  toReleaseString: ->
+    "#{@info['title']}, #{@info['release_dates']['dvd'] || @info['release_dates']['theater']} (#{@info['ratings']['audience_score']}%)"
+
+  toString: ->
+    "#{@info['title']} (#{@info['ratings']['audience_score']}%)"
+
 module.exports = (robot) ->
-  robot.respond /rotten me (.*)$/i, (msg) ->
-    api_key = process.env.HUBOT_ROTTEN_TOMATOES_API_KEY
-
-    unless api_key
-      msg.send 'The environment variable $HUBOT_ROTTEN_TOMATOES_API_KEY needs to be set.'
-      return
-
-    title = msg.match[1]
-    msg.http('http://api.rottentomatoes.com/api/public/v1.0/movies.json')
-       .query
-         apikey: api_key
-         q: title
-         page_limit: 1
-       .get() (err, res, body) ->
-         movie = JSON.parse(body)['movies'][0]
-
-         unless movie?
-           msg.send "Couldn't find that movie, sorry."
-           return
-
-         msg.send "#{movie['title']} (#{movie['year']})\n" +
-                  "#{movie['runtime']} min, #{movie['mpaa_rating']}\n\n" +
-                  "Critics:\t" + "#{movie['ratings']['critics_score']}%" + "\t\"#{movie['ratings']['critics_rating']}\"\n" +
-                  "Audience:\t" + "#{movie['ratings']['audience_score']}%" + "\t\"#{movie['ratings']['audience_rating']}\"\n\n" +
-                  "#{movie['critics_consensus']}"
+  rotten = new Rotten robot
+
+  robot.respond /rotten (me )?(.*)$/i, (message) ->
+    message.send "Well, let's see..."
+    rotten.search message.match[2], (err, movie) ->
+      unless err?
+        message.send movie.toDetailedString()
+      else
+        message.send err
+
+  robot.respond /what(\')?s in theaters(\?)?$/i, (message) ->
+    message.send "Well, let's see..."
+    rotten.in_theaters (err, movies) ->
+      unless err?
+        message.send (movie.toString() for movie in movies).join("\n")
+      else
+        message.send err
+
+  robot.respond /what(\')?s coming out ((on (dvd|blu(-)?ray))|(in theaters))(\?)?$/i, (message) ->
+    message.send "Well, let's see..."
+    type = if message.match[2] is 'in theaters' then 'movies' else 'dvds'
+    rotten.upcoming type, (err, movies) ->
+      unless err?
+        message.send (movie.toReleaseString() for movie in movies).join("\n")
+      else
+        message.send err


Updated documentation

Updated documentation

--- src/scripts/sifter.coffee
+++ src/scripts/sifter.coffee
@@ -1,9 +1,11 @@
 # Sifter API integration
 #
-# hubot give me a sifter report- Returns a summary of the open sifter tickets based on the projects.  Regex used so that people can say "give me a sifters report"
+# hubot give me a sifter report - Returns a summary of the open sifter tickets based on the projects.  Regex used so that people can say "give me a sifters report"
+#
+# hubot give me a milestone report - Returns a summary of the open sifter tickets for each milestone for each project
 #
 #
-#
+
 token = process.env.HUBOT_SIFTER_TOKEN
 company = process.env.HUBOT_SIFTER_COMPANY
 module.exports = (robot) ->


incorrect log message

incorrect log message

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -354,7 +354,7 @@
         data = misc.to_json_socket(mesg)
         tm = new Date() - t
         if tm > 10
-            dbg("mesg.id=#{mesg.id}: time to json=#{tm}ms; length=#{json.length}; value='#{misc.trunc(json, 500)}'")
+            dbg("mesg.id=#{mesg.id}: time to json=#{tm}ms; length=#{data.length}; value='#{misc.trunc(data, 500)}'")
         @push_data_to_client(data)
         if not listen
             cb?()


changed keyboard layout, #38

changed keyboard layout, #38

--- web-src/index.coffee
+++ web-src/index.coffee
@@ -36,98 +36,76 @@

   # Symbols table {{{1
-  symbolDefs = [
-    ['+', 'Conjugate, Add']
-    ['-', 'Negate, Subtract']
-    ['×', 'Sign of, Multiply']
-    ['÷', 'Reciprocal, Divide']
-    ['⌈', 'Ceiling, Greater of']
-    ['⌊', 'Floor, Lesser of']
-    ['∣', 'Absolute value, Residue']
-    ['⍳', 'Index generator, Index of']
-    ['?', 'Roll, Deal']
-    ['*', 'Exponential, To the power of']
-    ['⍟', 'Natural logarithm, Logarithm to the base']
-    ['○', 'Pi times, Circular and hyperbolic functions']
-    ['!', 'Factorial, Binomial']
-    ['⌹', 'Matrix inverse, Matrix divide']
-    ['⍠', 'Variant operator']
-    ['<', 'Less than']
-    ['≤', 'Less than or equal']
-    ['=', 'Equal']
-    ['≥', 'Greater than or equal']
-    ['>', 'Greater than']
-    ['≠', 'Not equal']
-    ['≡', 'Depth, Match']
-    ['≢', 'Not match']
-    ['∊', 'Enlist, Membership']
-    ['⍷', 'Find']
-    ['∪', 'Unique, Union']
-    ['∩', 'Intersection']
-    ['~', 'Not, Without']
-    ['∨', 'Or (Greatest Common Divisor)']
-    ['∧', 'And (Least Common Multiple)']
-    ['⍱', 'Nor']
-    ['⍲', 'Nand']
-    ['⍴', 'Shape of, Reshape']
-    [',', 'Ravel, Catenate']
-    ['⍪', 'First axis catenate']
-    ['⌽', 'Reverse, Rotate']
-    ['⊖', 'First axis rotate']
-    ['⍉', 'Transpose']
-    ['↑', 'First, Take']
-    ['↓', 'Drop']
-    ['⊂', 'Enclose, Partition']
-    ['⊃', 'Disclose, Pick']
-    ['⌷', 'Index']
-    ['⍋', 'Grade up']
-    ['⍒', 'Grade down']
-    ['⊤', 'Encode']
-    ['⊥', 'Decode']
-    ['⍕', 'Format, Format by specification']
-    ['⍎', 'Execute']
-    ['⊣', 'Stop, Left']
-    ['⊢', 'Pass, Right']
-    ['⎕', 'Evaluated input, Output with a newline']
-    ['⍞', 'Character input, Bare output']
-    ['¨', 'Each']
-    ['∘.','Outer product', {keys: '`j.'}]
-    ['/', 'Reduce']
-    ['⌿', '1st axis reduce']
-    ['\\','Scan']
-    ['⍀', '1st axis scan']
-    ['⍣', 'Power operator']
-    ['⍨', 'Commute']
-    ['¯', 'Negative number sign']
-    ['⍝', 'Comment']
-    ['←', 'Assignment']
-    ['⍬', 'Zilde']
-    ['⋄', 'Statement separator']
-    ['⍺', 'Left formal parameter']
-    ['⍵', 'Right formal parameter']
-    ['APL', 'Press backquote (`) followed by another key to insert an APL symbol, e.g. `r inserts rho (⍴)']
-  ]
-
-  # Symbols setup {{{1
-  mapping = {}
-  rMapping = {} # reverse mapping
-  a = '''
-    `< «   `= ×   `> »   `_ ≡   `+ ≢   `- -   `, ⍪   `; ⋄   `: ÷   `! ⍣   `/ ⌿
-    `( ⍱   `) ⍲   `[ ←   `\\ ⍀  `0 ∧   `1 ¨   `2 ¯   `4 ≤   `6 ≥   `8 ≠   `9 ∨
-    `a ⍺   `A ⊖   `b ⊥   `B ⍎   `c ∩   `C ⍝   `d ⌊   `D ⌹   `e ∊   `E ⍷   `F ⍠
-    `g ∇   `G ⍒   `h ∆   `H ⍋   `i ⍳   `I ⌷   `j ∘   `l ⎕   `L ⍞   `m ∣   `n ⊤
-    `N ⍕   `o ○   `O ⍬   `p *   `P ⍟   `r ⍴   `s ⌈   `S ⍨   `t ~   `T ⍉   `u ↓
-    `v ∪   `w ⍵   `W ⌽   `x ⊃   `y ↑   `z ⊂
-  '''.replace(/(^\s+|\s+$)/g, '').split /\s+/
-  for i in [0 ... a.length / 2]
-    k = a[2 * i]; v = a[2 * i + 1]; mapping[k] = v; rMapping[v] = k
-
-  hSymbolDefs = {} # indexed by symbol
-  for symbolDef in symbolDefs
-    hSymbolDefs[symbolDef[0]] = symbolDef
-
-  $('#code').keydown (event) -> if event.keyCode is 13 and event.ctrlKey then $('#go').click(); false
-  $('#code').retype 'on', {mapping}
+  hSymbolDefs =
+    '+': 'Conjugate, Add'
+    '-': 'Negate, Subtract'
+    '×': 'Sign of, Multiply'
+    '÷': 'Reciprocal, Divide'
+    '⌈': 'Ceiling, Greater of'
+    '⌊': 'Floor, Lesser of'
+    '∣': 'Absolute value, Residue'
+    '⍳': 'Index generator, Index of'
+    '?': 'Roll, Deal'
+    '*': 'Exponential, To the power of'
+    '⍟': 'Natural logarithm, Logarithm to the base'
+    '○': 'Pi times, Circular and hyperbolic functions'
+    '!': 'Factorial, Binomial'
+    '⌹': 'Matrix inverse, Matrix divide'
+    '⍠': 'Variant operator'
+    '<': 'Less than'
+    '≤': 'Less than or equal'
+    '=': 'Equal'
+    '≥': 'Greater than or equal'
+    '>': 'Greater than'
+    '≠': 'Not equal'
+    '≡': 'Depth, Match'
+    '≢': 'Tally, Not match'
+    '∊': 'Enlist, Membership'
+    '⍷': 'Find'
+    '∪': 'Unique, Union'
+    '∩': 'Intersection'
+    '~': 'Not, Without'
+    '∨': 'Or (Greatest Common Divisor)'
+    '∧': 'And (Least Common Multiple)'
+    '⍱': 'Nor'
+    '⍲': 'Nand'
+    '⍴': 'Shape of, Reshape'
+    ',': 'Ravel, Catenate'
+    '⍪': 'First axis catenate'
+    '⌽': 'Reverse, Rotate'
+    '⊖': 'First axis rotate'
+    '⍉': 'Transpose'
+    '↑': 'First, Take'
+    '↓': 'Drop'
+    '⊂': 'Enclose, Partition'
+    '⊃': 'Disclose, Pick'
+    '⌷': 'Index'
+    '⍋': 'Grade up'
+    '⍒': 'Grade down'
+    '⊤': 'Encode'
+    '⊥': 'Decode'
+    '⍕': 'Format, Format by specification'
+    '⍎': 'Execute'
+    '⊣': 'Stop, Left'
+    '⊢': 'Pass, Right'
+    '⎕': 'Evaluated input, Output with a newline'
+    '⍞': 'Character input, Bare output'
+    '¨': 'Each'
+    '∘': 'Compose'
+    '/': 'Reduce'
+    '⌿': '1st axis reduce'
+    '\\': 'Scan'
+    '⍀': '1st axis scan'
+    '⍣': 'Power operator'
+    '⍨': 'Commute'
+    '¯': 'Negative number sign'
+    '⍝': 'Comment'
+    '←': 'Assignment'
+    '⍬': 'Zilde'
+    '⋄': 'Statement separator'
+    '⍺': 'Left formal parameter'
+    '⍵': 'Right formal parameter'
+    APL: 'Press backquote (`) followed by another key to insert an APL symbol, e.g. `r inserts rho (⍴)'

@@ -143,33 +121,33 @@
       exec: '⍎'
     autoAccept: true
     usePreview: false
-    customLayout:
+    customLayout: layout =
       default: [
-        '1 2 3 4 5 6 7 8 9 0 - ='
-        'q w e r t y u i o p [ ]'
-        'a s d f g h j k l {enter}'
-        '{shift} z x c v b n m , . {bksp}'
+        '~ 1 2 3 4 5 6 7 8 9 0 - ='
+        'q w e r t y u i o p [ ] \''
+        'a s d f g h j k l ; \' {enter}'
+        '{shift} z x c v b n m , . / {bksp}'
         '{alt} {space} {exec!!}'
       ]
       shift: [
-        '! @ # $ % ^ & * ( ) _ +'
-        'Q W E R T Y U I O P { }'
-        'A S D F G H J K L {enter}'
-        '{shift} Z X C V B N M < > {bksp}'
+        '` ! @ # $ % ^ & * ( ) _ +'
+        'Q W E R T Y U I O P { } |'
+        'A S D F G H J K L : " {enter}'
+        '{shift} Z X C V B N M < > ? {bksp}'
         '{alt} {space} {exec!!}'
       ]
       alt: [
-        '¨ ¯ < ≤ = ≥ > ≠ ∨ ∧ - ×'
-        '{empty} ⍵ ∊ ⍴ ~ ↑ ↓ ⍳ ○ * ← {empty}'
-        '⍺ ⌈ ⌊ {empty} ∇ ∆ ∘ {empty} ⎕ {enter}'
-        '{shift} ⊂ ⊃ ∩ ∪ ⊥ ⊤ ∣ ⍪ ÷ {bksp}'
+        '{empty} ¨ ¯ < ≤ = ≥ > ≠ ∨ ∧ ÷ ×'
+        '{empty} ⍵ ∊ ⍴ ~ ↑ ↓ ⍳ ○ ⍟ ← → ⍀'
+        '⍺ ⌈ ⌊ {empty} ∇ ∆ ∘ {empty} ⎕ ⋄ {empty} {enter}'
+        '{shift} ⊂ ⊃ ∩ ∪ ⊥ ⊤ ∣ ⍪ {empty} ⌿ {bksp}'
         '{alt} {space} {exec!!}'
       ]
       'alt-shift': [
-        '⍣ {empty} {empty} {empty} {empty} {empty} {empty} {empty} ⍱ ⍲ ≡ ≢'
-        '{empty} ⌽ ⍷ {empty} ⍉ {empty} {empty} ⌷ ⍬ ⍟ {empty} {empty}'
-        '⊖ ⍨ ⌹ ⍠ ⍒ ⍋ {empty} {empty} ⍞ {enter}'
-        '{shift} {empty} {empty} ⍝ {empty} ⍎ ⍕ {empty} « » {bksp}'
+        '⍨ {empty} ⍁ ⍂ ⍠ ≈ ⌸ ⍯ ⍣ ⍱ ⍲ ≢ ≡'
+        '⌹ ⍹ ⍷ ⍤ {empty} {empty} ⊖ ⍸ ⍬ ⌽ ⊣ ⊢ ⍉'
+        '⍶ {empty} {empty} {empty} ⍒ ⍋ ⍝ {empty} ⍞ {empty} {empty} {enter}'
+        '{shift} ⊆ ⊇ ⋔ ⍦ ⍎ ⍕ ⌷ « » ↗ {bksp}'
         '{alt} {space} {exec!!}'
       ]

@@ -177,8 +155,24 @@

   $('textarea').focus()

+
+
+  # Key mappings {{{1
+  mapping = {}
+  asciiKeys = layout.default.concat(layout.shift).join(' ').split ' '
+  aplKeys = layout.alt.concat(layout['alt-shift']).join(' ').split ' '
+  for k, i in asciiKeys
+    v = aplKeys[i]
+    if '{' not in [k[0], v[0]]
+      mapping['`' + k] = v
+
+  $('#code').keydown (event) -> if event.keyCode is 13 and event.ctrlKey then $('#go').click(); false
+  $('#code').retype 'on', {mapping}
+
   tipsyOpts =
-    title: -> (hSymbolDefs[$(@).text()] or {})[1] or ''
+    title: ->
+      console.info 'k', $(@).text()
+      hSymbolDefs[$(@).text()] ? ''
     gravity: 's'
     delayIn: 1000
     opacity: 1
@@ -200,4 +194,4 @@
     $('#code').val(code).focus()
     false

-  {}
+  return


Use a helper function for building an authenticated instance of Twit

--- src/tweeter.coffee
+++ src/tweeter.coffee
@@ -42,6 +42,13 @@
   consumer_secret: process.env.HUBOT_TWITTER_CONSUMER_SECRET
   accounts_json: process.env.HUBOT_TWEETER_ACCOUNTS

+authenticated_twit = (username) ->
+  new Twit
+    consumer_key: config.consumer_key
+    consumer_secret: config.consumer_secret
+    access_token: config.accounts[username].access_token
+    access_token_secret: config.accounts[username].access_token_secret
+
 unless config.consumer_key
   console.log "Please set the HUBOT_TWITTER_CONSUMER_KEY environment variable."
 unless config.consumer_secret
@@ -74,13 +81,7 @@
       msg.reply "Your tweet is #{tweetOverflow} characters too long. Twitter users can't read that many characters!"
       return

-    twit = new Twit
-      consumer_key: config.consumer_key
-      consumer_secret: config.consumer_secret
-      access_token: config.accounts[username].access_token
-      access_token_secret: config.accounts[username].access_token_secret
-
-    twit.post "statuses/update",
+    authenticated_twit(username).post "statuses/update",
       status: update
     , (err, reply) ->
       if err
@@ -96,13 +97,7 @@
     username = msg.match[1]
     tweet_id = msg.match[2]

-    twit = new Twit
-      consumer_key: config.consumer_key
-      consumer_secret: config.consumer_secret
-      access_token: config.accounts[username].access_token
-      access_token_secret: config.accounts[username].access_token_secret
-
-    twit.post "statuses/destroy/#{tweet_id}", {}, (err, reply) ->
+    authenticated_twit(username).post "statuses/destroy/#{tweet_id}", {}, (err, reply) ->
       if err
         msg.reply "I can't do that. #{err.message} (error #{err.statusCode})"
         return


Teamwork: Fixed include order.

Teamwork: Fixed include order.

--- client/includes.coffee
+++ client/includes.coffee
@@ -618,11 +618,11 @@
   "app/Applications/Teamwork.kdapplication/Views/teamworkenvironmentsmodal.coffee",
   "app/Applications/Teamwork.kdapplication/Views/teamworkmarkdownmodal.coffee",
   "app/Applications/Teamwork.kdapplication/Views/facebookteamworkinstructionsmodal.coffee",
-  "app/Applications/Teamwork.kdapplication/Views/facebookteamwork.coffee",
   "app/Applications/Teamwork.kdapplication/Views/teamworkpanel.coffee",
   "app/Applications/Teamwork.kdapplication/Views/teamworktools.coffee",
   "app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee",
   "app/Applications/Teamwork.kdapplication/Views/teamworkapp.coffee",
+  "app/Applications/Teamwork.kdapplication/Views/facebookteamwork.coffee",
   "app/Applications/Teamwork.kdapplication/AppView.coffee",
   "app/Applications/Teamwork.kdapplication/AppController.coffee",


code formatting

code formatting

--- src/ui/views/input.coffee
+++ src/ui/views/input.coffee
@@ -12,21 +12,21 @@
 historyBackup = ""

 historyPush = (data) ->
-  history.push data
-  if history.length == historyLength then history.shift()
-  historyIndex = history.length
+    history.push data
+    if history.length == historyLength then history.shift()
+    historyIndex = history.length

 historyWalk = (el, offset) ->
-  # if we are starting to dive into history be backup current message
-  if offset is -1 and historyIndex is history.length then historyBackup = el.value
-  historyIndex = historyIndex + offset
-  # constrain index
-  if historyIndex < 0 then historyIndex = 0
-  if historyIndex > history.length then historyIndex = history.length
-  # if don't have history value restore 'current message'
-  val = history[historyIndex] or historyBackup
-  el.value = val
-  setTimeout (-> cursorToEnd el), 1
+    # if we are starting to dive into history be backup current message
+    if offset is -1 and historyIndex is history.length then historyBackup = el.value
+    historyIndex = historyIndex + offset
+    # constrain index
+    if historyIndex < 0 then historyIndex = 0
+    if historyIndex > history.length then historyIndex = history.length
+    # if don't have history value restore 'current message'
+    val = history[historyIndex] or historyBackup
+    el.value = val
+    setTimeout (-> cursorToEnd el), 1

 module.exports = view (models) ->
     div class:'input', -> div ->


Added random song array

Added random song array

Signed-off-by: Kevin <e043899daa0c7add37bc99792b2c045d6abbc6dc@stealsyour.pw>

--- src/usernowplaying.coffee
+++ src/usernowplaying.coffee
@@ -11,6 +11,14 @@
 #
 # Authors:
 #   Kevin L <kevin@stealsyour.pw>
+
+songs = [
+  "Shakira - Hips Don't Lie",
+  "R. Kelly - Trapped in the Closet",
+  "Spice Girls - Wannabe",
+  "Backstreet Boys - I Want It That Way",
+  "Justin Bieber - Never say Never"
+]

 module.exports = (robot) ->
   robot.router.post "/hubot/nowplaying", (req, res) ->
@@ -34,5 +42,5 @@
     user = msg.match[1].trim()
     song = robot.brain.get("#{user}_nowplaying") or 0
     if song == 0
-      song = "Justin Bieber - Never say Never"
+      song = msg.random songs
     msg.send song


Less steps for circle.

Less steps for circle.

--- src/widgets.coffee
+++ src/widgets.coffee
@@ -84,7 +84,7 @@
             center = @circle.getLatLng()
             lngRadius = east - center.lng
             latRadius = north - center.lat
-            STEPS = 360 * 2
+            STEPS = 180
             for i in [0 ... STEPS]
                 rad = (2 * i * Math.PI) / STEPS
                 [center.lat + Math.sin(rad) * latRadius


Alternative method of only handling event once.  Closes #32

--- coffee/sortable.coffee
+++ coffee/sortable.coffee
@@ -37,11 +37,16 @@
   setupClickableTH: (table, th, i) ->
     type = sortable.getColumnType table, i

+    eventHandled = false
+
     onClick = (e) ->
-      if e.handled isnt true
-        e.handled = true
-      else
-        return false
+      if eventHandled
+        return
+
+      eventHandled = true
+      setTimeout ->
+        eventHandled = false
+      , 0

       sorted = @getAttribute('data-sorted') is 'true'
       sortedDirection = @getAttribute 'data-sorted-direction'


update stats tracking to allow for multiple hubs per host

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -2175,8 +2175,11 @@
             clients : required
             ttl     : required
             cb      : required
+        # Since multiple hubs can run on the same host (but with different ports) and the host is the primary
+        # key, we combine the host and port number in the host name for the db.  The hub_servers table is only
+        # used for tracking connection stats, so this is safe.
         @table('hub_servers').insert({
-            host:opts.host, port:opts.port, clients:opts.clients, expire:expire_time(opts.ttl)
+            host:"#{opts.host}-#{opts.port}", port:opts.port, clients:opts.clients, expire:expire_time(opts.ttl)
             }, conflict:"replace").run(opts.cb)

     get_hub_servers: (opts) =>


config: main.dev: add resetdb functionality to run script

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -735,6 +735,7 @@
         echo "  run log [worker]          : to see of specified worker logs only"
         echo "  run buildservices         : to initialize and start services"
         echo "  run buildservices sandbox : to initialize and start services on sandbox"
+        echo "  run resetdb               : to reset databases"
         echo "  run services              : to stop and restart services"
         echo "  run worker                : to list workers"
         echo "  run chaosmonkey           : to restart every service randomly to test resilience."
@@ -1032,6 +1033,27 @@
         check_service_dependencies
         services

+      elif [ "$1" == "resetdb" ]; then
+
+        if [ "$2" == "--yes" ]; then
+
+          restoredefaultmongodump
+          migrateusers
+
+          exit 0
+
+        fi
+
+        read -p "This will reset current databases, all data will be lost! (y/N)" -n 1 -r
+        echo ""
+        if [[ ! $REPLY =~ ^[Yy]$ ]]
+        then
+            exit 1
+        fi
+
+        restoredefaultmongodump
+        migrateusers
+
       elif [ "$1" == "buildservices" ]; then

         if [ "$2" == "sandbox" ]; then


just attach to the blackout element instead

--- resources/assets/lib/select-options.coffee
+++ resources/assets/lib/select-options.coffee
@@ -30,7 +30,7 @@

   componentDidMount: =>
-    document.addEventListener 'click', @hideSelector
+    element.addEventListener 'click', @hideSelector for element in Blackout.el

   componentDidUpdate: (_prevProps, prevState) =>
@@ -38,7 +38,7 @@

   componentWillUnmount: ->
-    document.removeEventListener 'click', @hideSelector
+    element.removeEventListener 'click', @hideSelector for element in Blackout.el

   render: =>
@@ -99,11 +99,7 @@

   hideSelector: (e) =>
-    return if e.button != 0
-    return unless @state.showingSelector
-    return if $(e.target).closest(@ref.current).length
-
-    @setState showingSelector: false
+    @setState showingSelector: false if e.button == 0

   itemSelected: (event, item) ->


Use better regexes when parsing and cleanup

--- storyturtle.coffee
+++ storyturtle.coffee
@@ -1,19 +1,46 @@
-# storyturtle.js
-# License MIT
-# http://simplectic.com/story_turtle
-$(->
-  game = $("#game").hide()
-  editor = $("<textarea>", { rows: 15, cols: 30 }).
-    hide().val(game.text()).appendTo(game.text(""))
-  board = $("<div>").width(300).height(300).appendTo(game)
-  speaker = $("<div>").width(350).appendTo(game)
-  play = $("<a>", href:'#').text("Play!").css(float: "left")
-  edit = $("<a>", href: '#').text("Edit").css(float: "right")
-  controls = $("<div>").width(300).append(play, edit).appendTo(game)
+###
+storyturtle.js
+License MIT
+http://simplectic.com/story_turtle
+###
+$ ->
+  game = $("#game")
+    .hide()
+
+  editor = $("<textarea>",
+    rows: 15
+    cols: 30).
+    hide().
+    val(game.text()).
+    appendTo(game.text(""))
+
+  board = $("<div>")
+    .width(300)
+    .height(300)
+    .appendTo(game)
+
+  speaker = $("<div>")
+    .width(350)
+    .appendTo(game)
+
+  play = $("<a>", href:'#').
+    text("Play!")
+    .css(float: "left")
+
+  edit = $("<a>", href: '#')
+    .text("Edit")
+    .css(float: "right")
+
+  controls = $("<div>")
+    .width(300)
+    .append(play, edit)
+    .appendTo(game)

   features = {}
   moveQueue = []
-  offset = {top: 0, left:0}
+  offset =
+    left:0
+    top:0

   create = (cb, name, type, x, y)->
     feature = features[name]
@@ -21,10 +48,10 @@
     feature = $("<img>", src: "images/#{type}.png")
     feature.css
       position: 'absolute'
-      left: x
-      top: y
-      width: 30
-      height: 30
+      left:x
+      top:y
+      width:30
+      height:30
     feature.appendTo board
     features[name] = feature
     cb()
@@ -33,7 +60,9 @@
     feature = features[name]
     feature and moveQueue.push
       feature: feature
-      attrs: { left: x, top: y }
+      attrs:
+        left:x
+        top:y
     cb()

   go = (cb)->
@@ -44,60 +73,71 @@
     toMove = moveQueue.pop()

     while toMove
-      toMove.feature.animate(toMove.attrs, 1000, 'linear', countdown)
+      toMove.feature.animate(
+        toMove.attrs
+        1000
+        'linear'
+        countdown)
       toMove = moveQueue.pop()

     countdown()

   grammer =
     create:
-      match: /(\w*) is an? (\w*) at (\w*) (\w*)/
+      match: /(\w+)\s+is\s+an?\s+(\w+)\s+at\s+([123]?\d?\d)\s+([123]?\d?\d)/
       handle: (match, cb) ->
-        create(cb,
-          match[1],
-          match[2],
-          parseInt(match[3], 10) + offset.left,
+        create(cb
+          match[1]
+          match[2]
+          parseInt(match[3], 10) + offset.left
           parseInt(match[4], 10) + offset.top)
     die:
-      match: /(\w*) dies/
+      match: /(\w+)\s+dies/
       handle: (match, cb) ->
         feature = features[match[1]]
         feature.remove() if feature
         cb()
     move:
-      match: /(\w*) moves to (\w*) (\w*)/
+      match: /(\w+)\s+moves\s+to\s+([123]?\d?\d)\s+([123]?\d?\d)/
       handle: (match, cb)->
-        move(cb,
-          match[1],
-          parseInt(match[2], 10) + offset.left,
+        move(
+          cb
+          match[1]
+          parseInt(match[2], 10) + offset.left
           parseInt(match[3], 10) + offset.top)
     pause:
-      match: /pause (\w*)/
+      match: /pause\s+(\d+)/
       handle: (match, cb)->
-        setTimeout(cb,
-        parseInt(match[1], 10) * 10)
+        setTimeout(cb
+          parseInt(match[1], 10) * 10)
     say:
-      match: /say (.*)/
+      match: /say\s+(.+)/
       handle: (match, cb)->
         speaker.text match[1]
         cb()
     says:
-      match: /(\w*) says (.*)/
+      match: /(\w+)\s+says\s+(.*)/
       handle: (match, cb)->
         speaker.text "#{match[1]} says, \"#{match[2]}\""
         cb()
     go:
       match: /^(\s*)$/
       handle: (match, cb)->
-        go(cb)
+        go cb

   parse = (text, cb)->
+    features = {}
+    moveQueue = []
+    board.html ""
+
     steps = []
-    parseLine = (i, line)->
-      $.each grammer, (key, check)->
-        match = check.match.exec(line)
+    for line in text.split '\n'
+      for own key, check of grammer
+        match = check.match.exec line
         if match
-          steps[steps.length] = { check: check, match: match }
+          steps[steps.length] =
+            check: check
+            match: match

     idx = 0
     next = ->
@@ -105,13 +145,8 @@
       if step
         step.check.handle step.match, next
       else
-        go(cb)
+        go cb

-    features = {}
-    moveQueue = []
-    board.html("")
-
-    $.each text.split('\n'), parseLine
     next()

   play.click ->
@@ -134,4 +169,3 @@
     false

   game.show()
-)


Update banban.coffee

Update banban.coffee

--- scripts/banban.coffee
+++ scripts/banban.coffee
@@ -28,4 +28,4 @@
     robot.hear /md5ify/, (msg)->
       m = crypto.createHash('md5').update("md5ify this text").digest("hex")
       msg.send m
-      msg.send util.inspect(msg)+      msg.send "util.inspect(msg):\n" + util.inspect(msg)


Improved marking indicators

Improved marking indicators

--- scripts/controllers/MarkerIndicator.coffee
+++ scripts/controllers/MarkerIndicator.coffee
@@ -1,5 +1,8 @@
 Spine = require 'Spine'
 Raphael = require 'Raphael'
+
+Marker = require 'controllers/Marker'
+{delay} = require 'util'

 template = require 'lib/text!views/MarkerIndicator.html'

@@ -40,8 +43,10 @@
     @html @template
     @paper = Raphael @points[0], '100%', '100%'

-  setSpecies: (@species) =>
-    return unless @species
+  setSpecies: (species) =>
+    return unless species
+    return if species is @species
+    @species = species

     @image.attr 'src', @helpers[@species].image
     @image.one 'load', =>
@@ -56,12 +61,19 @@
       circle.attr cx: coords.x, cy: coords.y, fill: style[@species]
       @circles.push circle

+    @step = -1
     @setStep 0

-  setStep: (@step) =>
-    @step %= @helpers[@species].points.length
-    console.log 'step', @step
+  setStep: (step) =>
+    step %= @helpers[@species].points.length
+    return if step is @step
+    @step = step
+
     @circles.attr style.helperCircle
-    @circles[@step].animate style.helperCircle.active
+
+    # Flicker the active dot.
+    @circles[@step].animate style.helperCircle.active, 100, =>
+      @circles[@step].animate style.helperCircle, 100, =>
+        @circles[@step].animate style.helperCircle.active, 100

 exports = MarkerIndicator


Groups: fixed crawlable feed range typo

Groups: fixed crawlable feed range typo

--- server/lib/crawler/staticpages/feed.coffee
+++ server/lib/crawler/staticpages/feed.coffee
@@ -34,7 +34,7 @@
     model.some selector, options, (err, contents)=>
       return callback err, null  if err
       return callback null, getEmptyPage contentType  if count is 0
-      queue = [0..contents.length].map (index)=>=>
+      queue = [0...contents.length].map (index)=>=>
         queue.pageContent or= ""

         content = contents[index]