STL/core migrating the hash test from vows to mocha

--- stl/core/test/unit/hash_test.coffee
+++ stl/core/test/unit/hash_test.coffee
@@ -1,263 +1,253 @@
 #
 # The `Hash` unit tests
 #
-# Copyright (C) 2011 Nikolay Nemshilov
+# Copyright (C) 2011-2012 Nikolay Nemshilov
 #
-{describe, assert, Lovely} = require('../test_helper')
-
-Hash = Lovely.Hash
-
-#
-# A dummy class to check the prototype
-# values filtration
-#
-# @param {Object} own properties
-# @return undefined
-#
-Foo = (object) ->
-  object or= {}
-  for key, value of object
-    this[key] = value
-  this
-
-Foo.prototype.boo = 'boo'
-Foo.prototype.hoo = 'hoo'
-
-
-describe 'Hash', module,
-  '#initialization':
-    topic: ->
-      this.object = {}
-      new Hash(this.object)
-
-    "should create an instance of Hash": (hash) ->
-      assert.instanceOf hash, Hash
-
-    "should refer to the original object via the '_' attribute": (hash) ->
-      assert.same hash._, this.object
-
-  '#keys()':
-    "should return an array of the keys": ->
-      assert.deepEqual new Hash(a: 1, b: 2).keys(), ['a', 'b']
-
-    "should skip prototype keys": ->
-      assert.deepEqual new Hash(new Foo(a: 1, b: 2)).keys(), ['a', 'b']
-
-  '#values()':
-    "should return an array of values": ->
-      assert.deepEqual new Hash(a: 1, b: 2).values(), [1, 2]
-
-    "should skip any prototype values": ->
-      assert.deepEqual new Hash(new Foo(a: 1, b: 2)).values(), [1, 2]
-
-  '#empty()':
-    "should say 'true' for empty hashes": ->
-      assert.isTrue new Hash({}).empty()
-
-    "should say 'false' for non empty hashes": ->
-      assert.isFalse new Hash(a: 1).empty()
-
-    "should filter out prototype properties": ->
-      assert.isTrue  new Hash(new Foo()).empty()
-      assert.isFalse new Hash(new Foo(a: 1)).empty()
-
-  '#clone()':
-    topic: {a:1, b:2}
-
-    "should create a new Hash with cloned data": (data) ->
+{Lovely} = require('../test_helper')
+
+describe 'Hash', ->
+  Hash = Lovely.Hash
+
+  #
+  # A dummy class to check the prototype
+  # values filtration
+  #
+  # @param {Object} own properties
+  # @return undefined
+  #
+  Foo = (object) ->
+    object or= {}
+    for key, value of object
+      this[key] = value
+    this
+
+  Foo.prototype.boo = 'boo'
+  Foo.prototype.hoo = 'hoo'
+
+
+  describe '\b#constructor', ->
+    hash = new Hash(object = {})
+
+    it "should create an instance of Hash", ->
+      hash.should.be.instanceOf Hash
+
+    it "should refer to the original object via the '_' attribute", ->
+      hash._.should.equal object
+
+  describe '\b#keys()', ->
+    it "should return an array of the keys", ->
+      new Hash(a: 1, b: 2).keys().should.eql ['a', 'b']
+
+    it "should skip prototype keys", ->
+      new Hash(new Foo(a: 1, b: 2)).keys().should.eql ['a', 'b']
+
+  describe '\b#values()', ->
+    it "should return an array of values", ->
+      new Hash(a: 1, b: 2).values().should.eql [1, 2]
+
+    it "should skip any prototype values", ->
+      new Hash(new Foo(a: 1, b: 2)).values().should.eql [1, 2]
+
+  describe '\b#empty()', ->
+    it "should say 'true' for empty hashes", ->
+      new Hash({}).empty().should.be.true
+
+    it "should say 'false' for non empty hashes", ->
+      new Hash(a: 1).empty().should.be.false
+
+    it "should filter out prototype properties", ->
+      new Hash(new Foo()).empty().should.be.true
+      new Hash(new Foo(a: 1)).empty().should.be.false
+
+  describe '\b#clone()', ->
+    data = {a:1, b:2}
+
+    it "should create a new Hash with cloned data", ->
       original = new Hash(data)
       clone    = original.clone()

-      assert.instanceOf clone, Hash
-      assert.notSame    clone, original
-
-      assert.deepEqual  clone._, data
-      assert.notSame    clone._, data
-
-    "should skip any prototype data": (data) ->
+      clone.should.be.instanceOf Hash
+      clone.should.not.equal original
+
+      clone._.should.eql data
+      clone._.should.not.equal data
+
+    it "should skip any prototype data", ->
       original = new Hash(new Foo(data))
       clone    = original.clone()

-      assert.instanceOf clone, Hash
-      assert.notSame    clone, original
-
-      assert.deepEqual  clone._, data
-      assert.notSame    clone._, data
-
-  '#forEach(callback)':
-    "should go through every key-value pair": (result) ->
+      clone.should.be.instanceOf Hash
+      clone.should.not.equal original
+
+      clone._.should.eql data
+      clone._.should.not.equal data
+
+  describe '\b#forEach(callback)', ->
+    it "should go through every key-value pair", ->
       hash   = new Hash(a:1, b:2)
       result = []

       ret = hash.forEach (key, value, hash) ->
         result.push([key, value, hash])

-      assert.same      ret, hash
-      assert.deepEqual result, [
+      ret.should.equal hash
+      result.should.eql [
         ['a', 1, {a:1, b:2}],
         ['b', 2, {a:1, b:2}]
       ]

-    "should skip the prototype keys and values": ->
+    it "should skip the prototype keys and values", ->
       result = []

       new Hash(new Foo(a:1, b:2)).forEach (key, value) ->
         result.push([key, value])

-      assert.deepEqual result, [['a', 1], ['b', 2]]
-
-
-  '#map(callback)':
-    "should map result of key-value calls on the callback": ->
+      result.should.eql [['a', 1], ['b', 2]]
+
+
+  describe '\b#map(callback)', ->
+    it "should map result of key-value calls on the callback", ->
       hash = a:1, b:2

       result = new Hash(hash).map (key, value, hash) ->
         [key, value, hash]

-      assert.deepEqual result, [
+      result.should.eql [
         ['a', 1, hash], ['b', 2, hash]
       ]

-    "should skip prototype key-value pairs": ->
+    it "should skip prototype key-value pairs", ->
       data   = a: 1, b:2
       result = new Hash(new Foo(data)).map (key, value, hash) ->
         [key, value, hash]

-      assert.deepEqual result, [
+      result.should.eql [
         ['a', 1, data], ['b', 2, data]
       ]

-  '#filter(callback)':
-    topic: ->
-      this.original = new Hash(a:1, b:2, c:3)
-      this.original.filter (key, value) ->
+  describe '\b#filter(callback)', ->
+    original = new Hash(a:1, b:2, c:3)
+    hash = original.filter (key, value) ->
         key is 'a' || key is 'c'

-    "should create a new Hash object": (hash) ->
-      assert.instanceOf hash, Hash
-      assert.notSame    hash, this.original
-
-    "should filter the related object": (hash) ->
-      assert.deepEqual hash._, {a:1, c:3}
-
-
-  '#reject(callback)':
-    topic: ->
-      this.original = new Hash(a:1, b:2, c:3)
-      this.original.reject (key, value) ->
+    it "should create a new Hash object", ->
+      hash.should.be.instanceOf Hash
+      hash.should.not.equal original
+
+    it "should filter the related object", ->
+      hash._.should.eql {a:1, c:3}
+
+
+  describe '\b#reject(callback)', ->
+    original = new Hash(a:1, b:2, c:3)
+    hash = original.reject (key, value) ->
         key is 'a' || key is 'c'

-    "should create a new Hash object": (hash) ->
-      assert.instanceOf hash, Hash
-      assert.notSame    hash, this.original
-
-    "should filter the related object": (hash) ->
-      assert.deepEqual hash._, {b:2}
-
-
-  '#merge(o1, o2,..)':
-    topic: ->
-      this.original = new Hash(a:3)
-      this.original.merge(
-        {a:1, b:3},
-        new Foo(b:2, c:4), # checking prototypes filtering
-        new Hash(c:3)      # checking Hashes processing
-      )
-
-    "should create a new Hash object": (hash) ->
-      assert.instanceOf hash, Hash
-      assert.notSame    hash, this.original
-
-    "should merge the objects": (hash) ->
-      assert.deepEqual hash._, {a:1, b:2, c:3}
-
-
-  '#merge(o1, o2, ..) - deep':
-    topic: ->
-      this.o1 = {a: {b: {c: 'd'}, e: 'f'}}
-      this.o2 = {a: {b: {c: 'd', e: 'f'}}}
-
-      new Hash({}).merge(this.o1, this.o2)
-
-    "should perform a deep merge of the data": (hash) ->
-      assert.deepEqual hash._, {a: {b: {c: 'd', e: 'f'}, e: 'f'}}
-
-    "should delink the keys": (hash) ->
+    it "should create a new Hash object", ->
+      hash.should.be.instanceOf Hash
+      hash.should.not.equal original
+
+    it "should filter the related object", ->
+      hash._.should.eql {b:2}
+
+
+  describe '\b#merge(o1, o2,..)', ->
+    original = new Hash(a:3)
+    hash = original.merge(
+      {a:1, b:3},
+      new Foo(b:2, c:4), # checking prototypes filtering
+      new Hash(c:3)      # checking Hashes processing
+    )
+
+    it "should create a new Hash object", ->
+      hash.should.be.instanceOf Hash
+      hash.should.not.equal original
+
+    it "should merge the objects", ->
+      hash._.should.eql {a:1, b:2, c:3}
+
+
+  describe '\b#merge(o1, o2, ..) - deep -', ->
+    o1 = {a: {b: {c: 'd'}, e: 'f'}}
+    o2 = {a: {b: {c: 'd', e: 'f'}}}
+
+    hash = new Hash({}).merge(o1, o2)
+
+    it "should perform a deep merge of the data", ->
+      hash._.should.eql {a: {b: {c: 'd', e: 'f'}, e: 'f'}}
+
+    it "should delink the keys", ->
       o  = hash._
-      o1 = this.o1
-      o2 = this.o2
-
-      assert.isTrue(
+
+      (
         o.a isnt o1.a and o.a isnt o2.a and o.a.b isnt o1.a.b && o.a.b isnt o2.a.b
-      )
-
-
-  '.keys':
-    "should return keys of an object": ->
-      assert.deepEqual ['a', 'b'], Hash.keys(a:1, b:2)
-
-  '.values':
-    "should return values of an object": ->
-      assert.deepEqual [1,2], Hash.values(a: 1, b:2)
-
-  '.empty':
-    "should check if an object is empty": ->
-      assert.isTrue  Hash.empty({})
-      assert.isFalse Hash.empty(a: 1)
-
-  ".clone":
-    "should clone an object": ->
+      ).should.be.true
+
+
+  describe '\b.keys', ->
+    it "should return keys of an object", ->
+      Hash.keys(a:1, b:2).should.eql ['a', 'b']
+
+  describe '\b.values', ->
+    it "should return values of an object", ->
+      Hash.values(a: 1, b:2).should.eql [1,2]
+
+  describe '\b.empty', ->
+    it "should check if an object is empty", ->
+      Hash.empty({}).should.be.true
+      Hash.empty(a: 1).should.be.false
+
+  describe "\b.clone", ->
+    it "should clone an object", ->
       object = a: 1
       clone  = Hash.clone(object)

-      assert.deepEqual clone, object
-      assert.notSame   clone, object
-
-  ".forEach":
-    topic: ->
-      this.args = []
-      this.obj  = a: 1, b: 2
-      Hash.forEach this.obj, (key, value, object) ->
-        this.args.push([key, value, object])
-      , this
-
-    "should run through every key-value pair in the object": ->
-      assert.deepEqual this.args, [
-        ['a', 1, this.obj], ['b', 2, this.obj]
-      ]
-
-    "should return the object itself back": (result) ->
-      assert.same result, this.obj
-
-  ".map":
-    "should map the results of callbacks on every key-value pairs": ->
+      clone.should.eql       object
+      clone.should.not.equal object
+
+  describe "\b.forEach", ->
+    args = []
+    obj  = a: 1, b: 2
+    hash = Hash.forEach obj, (key, value, object) ->
+        args.push([key, value, object])
+
+    it "should run through every key-value pair in the object", ->
+      args.should.eql [
+        ['a', 1, obj], ['b', 2, obj]
+      ]
+
+    it "should return the object itself back": (result) ->
+      result.should.equal obj
+
+  describe "\b.map", ->
+    it "should map the results of callbacks on every key-value pairs", ->
       result = Hash.map {a: 1, b: 2}, (key, value) ->
         key + "-" + value

-      assert.deepEqual result, ["a-1", "b-2"]
-
-  ".filter":
-    "should create a filtered object": ->
+      result.should.eql ["a-1", "b-2"]
+
+  describe "\b.filter", ->
+    it "should create a filtered object", ->
       hash = a: 1, b: 2, c: 3
       data = Hash.filter hash, (key, value) -> value % 2

-      assert.notSame   data, hash
-      assert.deepEqual data, {a:1, c:3}
-
-  ".reject":
-    "should create a new object without rejected pairs": ->
+      data.should.not.equal hash
+      data.should.eql {a:1, c:3}
+
+  describe "\b.reject", ->
+    it "should create a new object without rejected pairs", ->
       hash = a: 1, b: 2, c: 3
       data = Hash.reject hash, (key, value) -> value % 2

-      assert.notSame   data, hash
-      assert.deepEqual data, {b:2}
-
-  ".merge":
-    "should merge objects and hashes and make a new object": ->
+      data.should.not.equal hash
+      data.should.eql {b:2}
+
+  describe "\b.merge", ->
+    it "should merge objects and hashes and make a new object", ->
       object = a:1, b:3
       result = Hash.merge object,
         new Foo(b:2, c:4), # checking prototypes filtering
         new Hash(c:3)      # checking Hashes processing

-      assert.notSame   result, object
-      assert.deepEqual result, {a: 1, b: 2, c: 3}
+      result.should.not.equal object
+      result.should.eql {a: 1, b: 2, c: 3}


code snippet: fakeActivity now is htmlEncoded when posting. no injection anymore, even if it was just happening to local data

--- client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
@@ -15,20 +15,9 @@
     codeSnippetData = @getData().attachments[0]
     codeSnippetData.title = @getData().title

+    if @getData().fake then codeSnippetData.content = Encoder.htmlEncode codeSnippetData.content
+
     @codeSnippetView = new CodeSnippetView {}, codeSnippetData
-
-    # @codeShareBoxView = new CodeShareBox
-    #   allowEditing:no
-    #   allowClosing:no
-    #   hideTabs:yes
-    # ,data
-
-    # log data.meta.tags
-    # @tagGroup = new LinkGroup {
-    #   group         : data.meta.tags
-    #   itemsToShow   : 3
-    #   itemClass  : TagFollowBucketItemView
-    # }

   render:->
     super()


Add fact, observation, proposition environments

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -113,8 +113,8 @@
       else
         value = "-#{value}"
       """#{arg}"""
-  .replace /\\begin\s*{(problem|theorem|conjecture|lemma|corollary)}/g, (m, p1) -> "
#{s.capitalize p1}: "
-  .replace /\\end\s*{(problem|theorem|conjecture|lemma|corollary)}/g, '
'
+  .replace /\\begin\s*{(problem|theorem|conjecture|lemma|corollary|fact|observation|proposition)}/g, (m, p1) -> "
#{s.capitalize p1}: "
+  .replace /\\end\s*{(problem|theorem|conjecture|lemma|corollary|fact|observation|proposition)}/g, '
'
   .replace /\\begin\s*{(proof|pf)}/g, 'Proof: '
   .replace /\\end\s*{(proof|pf)}/g, ' ∎'
   .replace /\\"{(.)}/g, '&$1uml;'


do not hard code pop art

do not hard code pop art

--- models/gene.coffee
+++ models/gene.coffee
@@ -40,5 +40,5 @@
   fetchFilterSuggest: (params, options) ->
     new Backbone.Model().fetch _.extend
       data: params
-      url: "#{sd.ARTSY_URL}/api/v1/search/filtered/gene/pop-art/suggest"
+      url: "#{sd.ARTSY_URL}/api/v1/search/filtered/gene/#{@get 'id'}/suggest"
     , options


index entry url

index entry url

--- models/team.coffee
+++ models/team.coffee
@@ -76,6 +76,7 @@
       default: 0
 TeamSchema.plugin require('../lib/use-timestamps')
 TeamSchema.index updatedAt: -1
+TeamSchema.index 'entry.url': 1

 # class methods
 TeamSchema.static 'findBySlug', (slug, rest...) ->


Removed 2 more properties

Removed 2 more properties

--- SingularityUI/app/collections/TasksActive.coffee
+++ SingularityUI/app/collections/TasksActive.coffee
@@ -10,15 +10,11 @@
             # offer
             'offer.hostname'

-            # taskRequest
-            'taskRequest.request.id'
-
             # taskId
             'taskId'

             # mesosTask
             'mesosTask.resources'
-            'mesosTask.name'
         ]

         propertiesString = "?property=#{ properties.join('&property=') }"
@@ -29,8 +25,8 @@
         _.each tasks, (task, i) =>
             task.JSONString = utils.stringJSON task
             task.id = task.taskId.id
-            task.requestId = task.taskRequest?.request.id
-            task.name = task.mesosTask.name
+            task.requestId = task.taskId.requestId
+            task.name = task.requestId
             task.resources = @parseResources task
             task.memoryHuman = if task.resources?.memoryMb? then "#{ task.resources.memoryMb }Mb" else ''
             task.host = task.offer.hostname?.split('.')[0]


No need to export

No need to export

--- lib/motions.coffee
+++ lib/motions.coffee
@@ -868,8 +868,7 @@
 # Alias
 module.exports = {
   MotionError
-  Motion
-  # MotionWithInput
+
   MoveLeft, MoveRight, MoveUp, MoveDown
   MoveToPreviousWord, MoveToNextWord, MoveToEndOfWord
   MoveToPreviousWholeWord, MoveToNextWholeWord, MoveToEndOfWholeWord


Fix bug: "scrollTop"

Fix bug: "scrollTop"

Fix bug: "document.body.scrollTop" always return 0 in chrome.

--- js/chapters.coffee
+++ js/chapters.coffee
@@ -310,7 +310,7 @@

     positionContextualDisplay = (code) ->
         boundingClientRect = code.getBoundingClientRect()
-        contextualDisplay.style.top = (boundingClientRect.top + document.body.scrollTop) + 'px'
+        contextualDisplay.style.top = (boundingClientRect.top + (document.body.scrollTop || document.documentElement.scrollTop)) + 'px'
         contextualDisplay.clientHeight

         contextualDisplayWrapper.style.height = document.body.clientHeight + 'px'


Added message for keycard not being found and support for multiple swiping.

--- app/assets/javascripts/admin_shortcuts.js.coffee
+++ app/assets/javascripts/admin_shortcuts.js.coffee
@@ -26,6 +26,7 @@
     keycard_entry = @keycard_field.val()
     return if !keycard_entry? || keycard_entry == ""
     @keycard_field.val("")
+    @keycard_field.focus()
     $.post("/admin/key_cards/checkin/#{keycard_entry}", this.keycard_success, 'JSON').fail(this.keycard_failure)
     return
   keycard_success: =>
@@ -38,7 +39,10 @@
     @message.removeClass('text-success')
     @message.addClass('text-error')
     data = data.responseJSON
-    errors = data["errors"].join(", ")
+    if data?["errors"]?
+      errors = data["errors"].join(", ")
+    else
+      errors = "Keycard Not Found"
     @message.text(errors)
     @keycard_field.removeClass("success")
     @keycard_field.addClass("error")


[fix] views events: parallel queries

[fix] views events: parallel queries

--- app/scripts/views/layouts/main.coffee
+++ app/scripts/views/layouts/main.coffee
@@ -48,8 +48,9 @@

     initViewsEvents: ->
       @models.area.on 'change:position', (model, value) =>
+        @views.weather_info.updateWithPosition value
+      @models.area.on 'change:position', (model, value) =>
         @views.panorama.updateWithPosition value
-        @views.weather_info.updateWithPosition value

     onRender: ->
       $('body')


Show errors from trying to sign in

Show errors from trying to sign in

--- app/assets/javascripts/routes/application.coffee
+++ app/assets/javascripts/routes/application.coffee
@@ -16,3 +16,7 @@
       @router.one('didTransition', loading, 'deactivate')

       return true
+
+    sessionAuthenticationFailed: (error)->
+      Bootstrap.NM.set('content', Ember.A())
+      Bootstrap.NM.push(Ember.I18n.t('sessions.new.invalid_password'), 'danger')


Add title to the chart

Add title to the chart

--- public/js/main.coffee
+++ public/js/main.coffee
@@ -1,18 +1,20 @@
 $ ->
-	data = [
-			name: 'Original'
-			participants: 100
-			conversions: 45
-			color: '#00aa00'
-		,
-			name: 'Variation'
-			participants: 100
-			conversions: 50
-			color: 'blue'
-	]
+	data =
+		title: 'Original vs Variation'
+		variations: [
+				name: 'Original'
+				participants: 100
+				conversions: 45
+				color: '#00aa00'
+			,
+				name: 'Variation'
+				participants: 100
+				conversions: 50
+				color: '#0000ff'
+		]

 	variations = []
-	variations.push new App.Variation item.name, item.color, item.participants, item.conversions for item in data
+	variations.push new App.Variation item.name, item.color, item.participants, item.conversions for item in data.variations

-	calculator = new App.Calculator variations
-	calculator.renderGraph()+	calculator = new App.Calculator data.title, variations
+	calculator.render()


Use relative depth in refresh task

Use relative depth in refresh task

--- lib/session.coffee
+++ lib/session.coffee
@@ -247,7 +247,7 @@
         when 'refresh'
           path = common.parsePath(provision[1]).slice()
           l = path.length
-          path.length = 10
+          path.length += 10
           path.fill('*', l)
           t = provision[2]
           t += sessionData.timestamp if t <= 0
@@ -272,7 +272,7 @@
             when 'refreshObject'
               path = common.parsePath(provision[3]).slice()
               l = path.length
-              path.length = 10
+              path.length += 10
               path.fill('*', l)
               for i in [l...path.length] by 1
                 p = common.addPathMeta(path.slice(0, i))


Add new node parsing code

Add new node parsing code

--- source/coffee/index.coffee
+++ source/coffee/index.coffee
@@ -13,6 +13,30 @@

     @getMappedData = (url, pin, map={}, wrap=true) =>
       @getData url, (data) => @mapData(data, pin, map, wrap)
+
+    @getNodes = (data, pin, keyed={}, root={}) ->
+      # Index node list by ID
+      for node in data.nodes
+        keyed[node.id] = node
+
+      # Step through IDs
+      for id of keyed
+        node = keyed[id]
+        pid = node.facts?.parent_id
+        if pid? # Has parent?
+          pnode = keyed?[pid]
+          pnode.children ?= [] # Initialize if first child
+          pnode.children.push node # Add to parent's children
+        else # Must be root node
+          root = node # Point at it
+
+      pin keyed if pin?
+      children: root
+
+    # Be sure to use a harness for the chillin'folk!
+    #@getTree = (start) =>
+    #  if start?.children?
+    #    @getTree c for c in start.children

     @wsTemp = ko.observableArray()
     @wsItems = ko.computed =>
@@ -48,22 +72,7 @@
         status: "good"
       , {}, ko.mapping.fromJS @node, mapping

-    # Long-poller
-    #(poll = =>
-    #  $.ajax
-    #    url: "/roush/nodes/4?poll"
-    #    success: (data) =>
-    #      console.log "Zomg polled!"
-    #      @mapData data, @wsTemp, mapping, wrap=false
-    #    dataType: "json"
-    #    complete: (xhr, txt) ->
-    #      console.log "Txt: ", txt
-    #      if txt is not "success"
-    #        setTimeout poll, 1000
-    #      else
-    #        setTimeout poll, 0
-    #    timeout: @config?.timeout?.long ? 30000
-    #)()
+    @wsKeys = ko.observable()

     ntrapy.pollTree = =>
       unless ntrapy.poller? then ntrapy.poller = setInterval @getMappedData, @config.interval, "/roush/nodes/1/tree", @wsTemp, mapping
@@ -76,8 +85,10 @@
       @config = data

       # Load initial data, and poll every config.interval ms
-      @getMappedData "/roush/nodes/1/tree", @wsTemp, mapping
-      ntrapy.pollTree()
+      #@getMappedData "/roush/nodes/1/tree", @wsTemp, mapping
+      #ntrapy.pollTree()
+      @getData "/roush/nodes", (data) =>
+        @mapData @getNodes(data, @wsKeys), @wsTemp, mapping

     @siteActive = ntrapy.selector (data) =>
       null


use quad dynos

use quad dynos

--- lib/spawner.coffee
+++ lib/spawner.coffee
@@ -35,7 +35,7 @@
     data["ps_env[#{key}]"] = val for key, val of options.env # only add desired env
     data["attach"] = "true"
     data["command"] = command
-    data["size"] = 2
+    data["size"] = 4

     request = restler.post "https://#{host}/apps/#{app}/ps",
       headers:


nitrogen8mm: Switch state to 'New' in coffee file

Signed-off-by: Alexandru Costache <2d2dfcfad7b9832058fd41884a8955d042a85275@balena.io>

--- nitrogen8mm.coffee
+++ nitrogen8mm.coffee
@@ -14,8 +14,7 @@
 	slug: 'nitrogen8mm'
 	name: 'Nitrogen8M Mini SBC'
 	arch: 'aarch64'
-	state: 'experimental'
-	community: true
+	state: 'new'

 	stateInstructions:
 		postProvisioning: postProvisioningInstructions


Fixed.

Fixed.

--- reactive-table.coffee
+++ reactive-table.coffee
@@ -9,9 +9,6 @@
   constructor: (@_options = {}) ->
     unless @_options.collection
       throw new Error "ReactiveTable needs a collection!"
-
-    unless @_options.collection instanceof Meteor.Collection
-      throw new Error "ReactiveTable collection needs to be Meteor.Collection"

     @_options.page ?= 1
     check @_options.page, Number


Remove course store since it's unused

Remove course store since it's unused

--- test/current-user-store.spec.coffee
+++ test/current-user-store.spec.coffee
@@ -3,7 +3,6 @@

 {CourseListingActions, CourseListingStore} = require '../src/flux/course-listing'
 {CurrentUserActions, CurrentUserStore} = require '../src/flux/current-user'
-{CourseActions, CourseStore} = require '../src/flux/course'

 USER_MODEL = require '../api/user.json'
 {


deindent

deindent

--- test/power_coffee/mocha_node.coffee
+++ test/power_coffee/mocha_node.coffee
@@ -1,18 +1,15 @@
 assert = require 'power-assert'

-describe 'Array', ->
-
+describe 'Array#indexOf()', ->
     beforeEach ->
         this.ary = [1,2,3]

-    describe '#indexOf()', ->
+    it 'should return -1 when the value is not present', ->
+        minusOne = -1
+        two = 2
+        assert.ok this.ary.indexOf(two) is minusOne, 'THIS IS AN ASSERTION MESSAGE'

-        it 'should return -1 when the value is not present', ->
-            minusOne = -1
-            two = 2
-            assert.ok this.ary.indexOf(two) is minusOne, 'THIS IS AN ASSERTION MESSAGE'
-
-        it 'should return index when the value is present', ->
-            minusOne = -1
-            zero = 0
-            assert this.ary.indexOf(zero) isnt minusOne
+    it 'should return index when the value is present', ->
+        minusOne = -1
+        zero = 0
+        assert this.ary.indexOf(zero) isnt minusOne


Sometimes `code` isnt set before the fetch

--- apps/settings/components/coupon_code/view.coffee
+++ apps/settings/components/coupon_code/view.coffee
@@ -21,8 +21,9 @@

   checkCode: (_model, code) ->
     return unless code?
+    return if code is ''

-    return if code is ''
+    @model.set id: code, code: code

     Promise(@model.fetch())
       .catch ({ responseJSON: { description }}) =>


removed test duplication

removed test duplication

--- test/transaction-stream-test.coffee
+++ test/transaction-stream-test.coffee
@@ -11,10 +11,10 @@
   clock = null

   beforeEach ->
+    clock = sinon.useFakeTimers()
     world =
       given:
         fakeWebSocketConstructor: ->
-
           world.fakeWebSocketInstance =
             handlers: {}
             on: sinon.spy (event, callback) ->
@@ -25,81 +25,102 @@

           world.fakeWebSocketConstructor =
             sinon.stub().returns(world.fakeWebSocketInstance)
-          world
+          world.given

-    clock = sinon.useFakeTimers()
+        testSubjectCalledWithSomeAccount: ->
+          world.someAccount = 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q'
+          world.returnedStream =
+            transactionStream(world.fakeWebSocketConstructor, world.someAccount)
+          world.given
+
+        socketOpensSuccessFully: ->
+          world.fakeWebSocketInstance.fakeEmit 'open'
+          world.given
+
+        sendCommandYieldsSuccessfully: ->
+          world.fakeWebSocketInstance.send.yield null
+          world.given
+
+        fakeErrorHandler: ->
+          world.fakeErrorHandler = sinon.spy()
+          world.returnedStream.on 'error', world.fakeErrorHandler
+          world.given
+
+        fakeDataHandler: ->
+          world.fakeDataHandler = sinon.spy()
+          world.returnedStream.on 'data', world.fakeDataHandler
+          world.given
+
+    world.when = world.given
+

   afterEach -> clock.restore()

-  it 'subscribes to account and streams it', (done) ->
-    world.given.fakeWebSocketConstructor()
+  describe 'given test subject is called with a web socket constructor', ->
+    beforeEach ->
+      world.given
+        .fakeWebSocketConstructor()
+        .testSubjectCalledWithSomeAccount()
+        .fakeErrorHandler()
+        .fakeDataHandler()

-    stream = transactionStream(world.fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')
+    it 'constructs a stream', () ->
+      assert(world.fakeWebSocketConstructor.calledWithNew())
+      assert(world.fakeWebSocketConstructor.calledWith('ws://live.stellar.org:9001'))

-    assert(world.fakeWebSocketConstructor.calledWithNew())
-    assert(world.fakeWebSocketConstructor.calledWith('ws://live.stellar.org:9001'))
+    describe 'web socket emits an error', ->
+      fakeError = null
+      beforeEach ->
+        fakeError = new Error()
+        world.fakeWebSocketInstance.fakeEmit 'error', fakeError

-    clock.tick 50
+      it 'then it will be passed to error handler', ->
+        assert(world.fakeErrorHandler.calledWith(fakeError))

-    world.fakeWebSocketInstance.fakeEmit 'open'
+    describe 'given socket has been opened', ->
+      beforeEach ->
+        world.given.socketOpensSuccessFully()

-    clock.tick 100
+      it 'then it sends a subscription command', () ->
+        clock.tick 1
+        expectedPayload = JSON.stringify
+          "command" : "subscribe"
+          "accounts" : [ world.someAccount ]

-    stream.on 'error', -> assert.fail('should not call error')
+        assert world.fakeWebSocketInstance.send.calledWith expectedPayload

-    world.fakeWebSocketInstance.send.yield null
+      describe 'given subscription is successful', ->
+        beforeEach ->
+          world.given.sendCommandYieldsSuccessfully()

-    assert(world.fakeWebSocketInstance.send.calledWith(
-      JSON.stringify({ "command" : "subscribe",   "accounts" : [ "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q" ] })))
+        it 'then it does NOT send error', ->
+          assert world.fakeErrorHandler.callCount is 0

-    stream.on 'data', (data) ->
-      assert.deepEqual data,
-        from: "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q"
-        to: "gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT"
-        date: 461599670
-        amount: 50000000
-      done()
+        describe 'given websocket sends data', ->
+          beforeEach ->
+            world.fakeWebSocketInstance.fakeEmit 'message', JSON.stringify({
+               "transaction":{
+                  "Account":"gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q",
+                  "Amount":"50000000",
+                  "Destination":"gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT",
+                  "TransactionType":"Payment",
+                  "date":461599670,
+                  "hash":"4FE01B5CB153EFC5825DB0BDDD8A4764D63FC55102E25277548A85B2B6277202"
+               }
+            })

-    world.fakeWebSocketInstance.fakeEmit 'message', JSON.stringify({
-       "transaction":{
-          "Account":"gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q",
-          "Amount":"50000000",
-          "Destination":"gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT",
-          "TransactionType":"Payment",
-          "date":461599670,
-          "hash":"4FE01B5CB153EFC5825DB0BDDD8A4764D63FC55102E25277548A85B2B6277202"
-       }
-    })
+          it 'then it sends simplified transaction', ->
+            assert world.fakeDataHandler.calledWith
+              from: "gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q"
+              to: "gM8ZuCaGB7GkCiLz7rW941boyVT2vW1ppT"
+              date: 461599670
+              amount: 50000000

+      describe 'given subscription fails', ->
+        fakeError = null
+        beforeEach ->
+          fakeError = new Error()
+          world.fakeWebSocketInstance.send.yield fakeError

-  it 'forwards errors from on error', (done) ->
-    world.given.fakeWebSocketConstructor()
-
-    stream = transactionStream(world.fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')
-
-    clock.tick 50
-
-    fakeError = new Error()
-
-    stream.on 'error', (error) ->
-      assert.equal fakeError, error
-      done()
-
-    world.fakeWebSocketInstance.fakeEmit 'error', fakeError
-
-  it 'forwards errors from send callback', (done) ->
-
-    world.given.fakeWebSocketConstructor()
-
-    stream = transactionStream(world.fakeWebSocketConstructor, 'gDSSa75HPagWcvQmwH7D51dT5DPmvsKL4q')
-
-    clock.tick 50
-
-    world.fakeWebSocketInstance.fakeEmit 'open'
-
-    fakeError = new Error()
-    stream.on 'error', (error) ->
-      assert.equal fakeError, error
-      done()
-
-    world.fakeWebSocketInstance.send.yield fakeError
+        it 'then it will be passed to error handler', ->
+          assert(world.fakeErrorHandler.calledWith(fakeError))


Show socket connection UI less often

Show socket connection UI less often

--- assets/js/intertwinkles/socket.coffee
+++ assets/js/intertwinkles/socket.coffee
@@ -131,7 +131,7 @@
   <% if (state == "error") { %>
     Sorry, there has been a server error. Reload?
   <% } else if (state == "connecting") { %>
-    Connecting ...
+    Connecting ...
   <% } else if (state == "reconnecting") { %>
     Reconnecting...
   <% } else if (state == "fail") { %>
@@ -159,17 +159,20 @@
     if state == "hide"
       @$el.hide()
     else if state == "ok"
+      clearTimeout(@_showTimeout) if @_showTimeout?
       setTimeout (=> @$el.slideUp()), 300
     else
       if @$el.is(":hidden")
-        @$el.slideDown()
+        unless @_showTimeout?
+          @_showTimeout = setTimeout((=> @$el.slideDown()), 500)
     @$el.html(@template({state}))

-intertwinkles.add_socket_status_view = (socket, state="connecting") ->
+intertwinkles.add_socket_status_view = (socket, state="hide") ->
   status_view = new intertwinkles.SocketStatusView({socket})
   $("body").append(status_view.el)
   status_view.render(state)
-  $(window).on "unload", -> status_view.remove()
+  window.onunload = ->
+    status_view.remove()

 intertwinkles.connect_socket = (cb) ->
   if intertwinkles.socket? and intertwinkles.socket.isAlive()


Don't error out if response is not valid JSON

--- assets/scripts/lib/travis/ajax.coffee
+++ assets/scripts/lib/travis/ajax.coffee
@@ -96,7 +96,10 @@
       if xhr.readyState == 4
         contentType = xhr.getResponseHeader('Content-Type')
         data = if contentType && contentType.match /application\/json/
-          jQuery.parseJSON(xhr.responseText)
+          try
+            jQuery.parseJSON(xhr.responseText)
+          catch e
+            console.log('error while parsing a response', method, options.url, xhr.responseText)
         else
           xhr.responseText


Reset search configs before loading results

--- src/scripts/modules/search/search.coffee
+++ src/scripts/modules/search/search.coffee
@@ -18,7 +18,7 @@
       queryString = linksHelper.serializeQuery(location.search)

       if queryString.q
-        @model = searchResults.load({query: location.search})
+        @model = searchResults.config().load({query: location.search})

       @listenTo(@model, 'change:error', @displayError) if @model


Reload the all list after the default notes are added.

--- app/controllers/upgrader.coffee
+++ app/controllers/upgrader.coffee
@@ -43,6 +43,9 @@
             category: 'General'
             date: Math.round(new Date().getTime()/1000)
           note.saveNote defaultNote.content
+
+        # For whatever reason, it doesn't reload afterwards?
+        $("#notebook-all").trigger("click")

     else if version is "1.0"
       path = window.require 'path'


Do not call vm.set() directly in new VM controller.

--- app/modules/new-vm/index.coffee
+++ app/modules/new-vm/index.coffee
@@ -393,7 +393,7 @@
         if memoryValue
           # FIXME: handles invalid entries.
           data.memoryValue = memoryValue + ' ' + memoryUnit
-        return xoApi.call('vm.set', data)
+        return xo.vm.set(data)
       .then () ->
         # If a CloudConfig drive needs to be created
         if $scope.coreOsCloudConfig


handling logout event

handling logout event

--- app/scripts/views/header.coffee
+++ app/scripts/views/header.coffee
@@ -77,6 +77,7 @@
     e.preventDefault()
     utils.clearAuth()
     @render()
+    @publishEvent "!login:logout"
     utils.gotoUrl "/"

   incCounter: ->


webapp/custom software: just a word

webapp/custom software: just a word

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -706,7 +706,7 @@
     render_available: ->
         avail = @props.available_features
         if not avail?
-            return
After the project is running,{' '}
+            return
When the project is running,{' '}
                 information about available features will show up here.


only show host in project settings if we're using a backend that requires knowing the host in order to ssh in.

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -717,7 +717,7 @@
             <LabeledRow key='project_id' label='Project id'>

{@props.project.get('project_id')}

             </LabeledRow>
-            {@show_host()}
+            {@show_host() if @props.allow_ssh}
             If your project is not working, please create a <ShowSupportLink />.
             {
 if @props.allow_ssh}
             {@ssh_notice() if @props.allow_ssh}


added checkout url.

added checkout url.

--- public/views/cart_detail_view.coffee
+++ public/views/cart_detail_view.coffee
@@ -2,6 +2,9 @@

   initialize: =>
     @listenTo @model, "change update destroy", @render
+
+  events:
+    "click .epages-cart-overlay-checkout-button": "checkout"

   template: _.template """
     <div class="epages-cart-overlay">
@@ -71,3 +74,7 @@
       @$(".epages-cart-overlay-not-empty").show()

     this
+
+  checkout: ->
+    console.log "checkout"
+    window.location.href = @model.get("checkoutUrl")


Shorten variable name

Shorten variable name

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -134,26 +134,28 @@
         index = containerOffset - 1
         offset += nodeLength(node) for node in @getNodesForIndex(index)
     else
-      node = DOM.findNodeForContainerAtOffset(container, containerOffset)
+      targetNode = DOM.findNodeForContainerAtOffset(container, containerOffset)
       walker = DOM.walkTree(@element)

       while walker.nextNode()
-        if nodeIsBlockStartComment(walker.currentNode)
+        node = walker.currentNode
+
+        if nodeIsBlockStartComment(node)
           if currentBlockComment
             index++
           else
-            currentBlockComment = walker.currentNode
-          offset = 0
-
-        if walker.currentNode is node
-          if container.nodeType is Node.TEXT_NODE and not nodeIsCursorTarget(walker.currentNode)
-            string = Trix.UTF16String.box(walker.currentNode.textContent)
+            currentBlockComment = node
+            offset = 0
+
+        if node is targetNode
+          if container.nodeType is Node.TEXT_NODE and not nodeIsCursorTarget(node)
+            string = Trix.UTF16String.box(node.textContent)
             offset += string.offsetFromUCS2Offset(containerOffset)
           else if containerOffset > 0
-            offset += nodeLength(walker.currentNode)
+            offset += nodeLength(node)
           return {index, offset}
         else
-          offset += nodeLength(walker.currentNode)
+          offset += nodeLength(node)

     {index, offset}


minor fix;

minor fix;

--- _assets/javascripts/product.js.coffee
+++ _assets/javascripts/product.js.coffee
@@ -3,7 +3,7 @@
   count = Math.ceil(children / 6)
   for page in [2..count]
     ul = $('<ul />').insertAfter('#showcase > ul:last')
-    for item in [(page-1)*6..page*6]
+    for item in [(page-1)*6..page*6-1]
       child = $('#showcase > ul:first > li').eq(item)
       if child.length == 1
         ul.append(child.clone())


Test error

Test error

--- spec/e2e.stubs.spec.coffee
+++ spec/e2e.stubs.spec.coffee
@@ -279,13 +279,13 @@
                assert context.response.statusCode is 200
                done()

-      ###
+
       describe 'match file as string', ->
          it 'should match the post body with the file properly', (done) ->
             context.url = '/file/body/matchstring'
             context.method = 'post'
             context.post = '''
-            {
+            {
                "data": [ "test" ],
                "hypermedia": [
                  "*",
@@ -300,4 +300,4 @@
                console.log 'STATUS ->', context.response.code
                assert context.response.code is 200
                done()
-      ###
+


Fix expected position replacing selection linewise

The cursor should be placed after the inserted text, not at the start
of it.

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -571,7 +571,7 @@

       it "replaces the current selection", ->
         expect(editor.getText()).toBe "0 345\n2"
-        expect(editor.getCursorScreenPosition()).toEqual [0, 2]
+        expect(editor.getCursorScreenPosition()).toEqual [1, 0]

     describe "with linewise contents", ->
       beforeEach ->


Update test to match its name

Update test to match its name

--- spec/test_case_spec.coffee
+++ spec/test_case_spec.coffee
@@ -110,12 +110,10 @@

   it 'should add a failure node when test failed', ->
     testCase.failure()
-    testCase.skipped()

     testCase.build parentElement

     expect(testCaseElement.ele).toHaveBeenCalledWith('failure', {})
-    expect(testCaseElement.ele).toHaveBeenCalledWith('skipped')

   it 'should add an error node when test errored', ->


plot should only set initial range if the values in the ranges are sane

--- bokehjs/src/coffee/common/plot.coffee
+++ bokehjs/src/coffee/common/plot.coffee
@@ -328,6 +328,18 @@
       @model.set('png', @canvas[0].toDataURL())
       bulk_save([@model])

+    set_initial_range : () ->
+      #check for good values for ranges before setting initial range
+      range_vals = [@x_range.get('start'), @x_range.get('end'),
+        @y_range.get('start'), @y_range.get('end')]
+      good_vals = _.map(range_vals, (val) -> val? and not _.isNaN(val))
+      good_vals = _.all(good_vals)
+      if good_vals
+        @initial_range_info = {
+          xr: { start: @x_range.get('start'), end: @x_range.get('end') }
+          yr: { start: @y_range.get('start'), end: @y_range.get('end') }
+        }
+
     render: (force) ->
       super()
       #newtime = new Date()
@@ -335,11 +347,8 @@
       #   console.log(newtime - @last_render)
       # @last_render = newtime

-      if not @initial_range_info? and @x_range.get('start')?
-        @initial_range_info = {
-          xr: { start: @x_range.get('start'), end: @x_range.get('end') }
-          yr: { start: @y_range.get('start'), end: @y_range.get('end') }
-        }
+      if not @initial_range_info?
+        @set_initial_range()

       @requested_padding = {
         top: 0
@@ -501,4 +510,3 @@
     "Collection": new Plots(),
     "View": PlotView,
   }
-


better sort for shows on an artist cv

better sort for shows on an artist cv

--- apps/artist/view_helpers.coffee
+++ apps/artist/view_helpers.coffee
@@ -12,7 +12,7 @@
   showHelpers: showHelpers

   nShowsByDate: (shows, n) ->
-    _.sortBy(_.take(shows, n), 'end_at').reverse()
+    _.sortBy(_.take(shows, n), 'start_at').reverse()

   pageTitle: (artist) ->
     artist = new Artist


added test

added test

--- test/crystal/validate/exports.coffee
+++ test/crystal/validate/exports.coffee
@@ -45,6 +45,18 @@
         (-> new crystal config)
           .should.not.throw "`exports.ConfigGenerator.description` is required."

+      it 'should not require a schema', () ->
+        # object
+        config = {
+          exports: {
+            ConfigGenerator: {
+              type: 'generator'
+            }
+          }
+        }
+        (-> new crystal config)
+          .should.not.throw "`exports.ConfigGenerator.schema` is required."
+
       it 'should require a type', () ->
         # object
         config = {


ignore gulpfile

ignore gulpfile

--- test/register-coffee-coverage.coffee
+++ test/register-coffee-coverage.coffee
@@ -2,6 +2,6 @@
 require('coffee-coverage').register {
   path: 'relative'
   basePath: require('path').join __dirname, '..'
-  exclude: ['test', 'lib', 'dist', 'bin', 'node_modules', '.git']
+  exclude: ['test', 'lib', 'dist', 'bin', 'node_modules', '.git', 'Gulpfile']
   initAll: true
 }


Upload RELEASES and .nupkg files

Upload RELEASES and .nupkg files

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -55,6 +55,7 @@

 getAssets = ->
   {cp} = require('./task-helpers')(grunt)
+  {version} = grunt.file.readJSON('package.json')

   switch process.platform
     when 'darwin'
@@ -64,19 +65,16 @@
         {assetName: 'atom-api.json', sourcePath: 'atom-api.json'}
       ]
     when 'win32'
-      cp path.join(buildDir, 'installer', 'AtomSetup.exe'), path.join(buildDir, 'AtomSetup.exe')
-
-      [
-        {assetName: 'atom-windows.zip', sourcePath: 'Atom'}
-        {assetName: 'AtomSetup.exe', sourcePath: 'AtomSetup.exe'}
-      ]
+      assets = [{assetName: 'atom-windows.zip', sourcePath: 'Atom'}]
+      for squirrelAsset in ['AtomSetup.exe', 'RELEASES', "atom-#{version}-full.nupkg"]
+        cp path.join(buildDir, 'installer', squirrelAsset), path.join(buildDir, squirrelAsset)
+        assets.push({assetName: squirrelAsset, sourcePath: assetName})
     when 'linux'
       buildDir = grunt.config.get('atom.buildDir')
       if process.arch is 'ia32'
         arch = 'i386'
       else
         arch = 'amd64'
-      {version} = grunt.file.readJSON('package.json')

       # Check for a Debian build
       sourcePath = "#{buildDir}/atom-#{version}-#{arch}.deb"


updated specs to match correct behavior

updated specs to match correct behavior

--- test/tourney/round-robin.spec.coffee
+++ test/tourney/round-robin.spec.coffee
@@ -16,7 +16,7 @@
       expect(roundRobin(0)).to.eql { games: 0, schedule: [], teams: [] }

     it 'given 1 team returns zero', ->
-      expect(roundRobin(1)).to.eql { games: 0, schedule: [], teams: [] }
+      expect(roundRobin(1)).to.eql { games: 0, schedule: [], teams: [1] }

     it 'given 2 teams returns 2 games with numbers for names', ->
       expect(roundRobin(2)).to.eql { games: 1, schedule: [[1,2]], teams: [1,2] }
@@ -38,7 +38,7 @@

   describe 'given team names', ->
     it 'given 1 team returns zero', ->
-      expect(roundRobin(['goodie'])).to.eql { games: 0, schedule: [] }
+      expect(roundRobin(['goodie'])).to.eql { games: 0, schedule: [], teams: ['goodie'] }

     it 'given 2 teams returns 2 games with correct names', ->
       expect(roundRobin(['a', 'b'])).to.eql { games: 1, schedule: [['a','b']], teams: ['a', 'b'] }


test(Document): add outline to unit tests

--- test/unit/model/Document-spec.coffee
+++ test/unit/model/Document-spec.coffee
@@ -175,6 +175,12 @@
       assert.throws ->
         doc.getFull {}
       , /Document doesnt exists/
+
+  describe '#getExpanded()', ->
+    it 'returns unexpanded object'
+    it 'ignores invalid collection keys'
+    it 'returns single expanded object'
+    it 'returns multiple expanded objects'

   describe '#insert()', ->
     it 'should insert new document without _id in database', (done) ->


CallbackWrapper now wraps only root-level describes

--- src/CallbackWrapper.coffee
+++ src/CallbackWrapper.coffee
@@ -61,7 +61,6 @@
       endOfLine      =
       endMatched     =
       inCallback     =
-      inDescribe     =
       inDSLParams    =
       beginMatched   =
       inParenthesis  =
@@ -92,9 +91,13 @@
           "#{p1}var _#{p2}_ = new (#{ContextFactory.toString().replace(/(\s*){1}.*/g, factoryReplacer)})('#{p2}');\n" +
           match.replace(p2, "_#{p2}_")

+        describeReplacer = (match, p1)->
+          match.replace(p1, "_#{p1}_") if p1?
+
         pushToResult = ->
           joined_line = line.join('')
-          joined_line = joined_line.replace(/(\s*)(\w*)\.is\(.*/g, mainReplacer) if not inDescribe?
+          joined_line = joined_line.replace(/(\s*)(\w*)\.is\(.*/g, mainReplacer)
+          joined_line = joined_line.replace(/.*(describe)\(.*/g, describeReplacer)
           result_string.push(joined_line) and clearLine()

         {
@@ -109,36 +112,29 @@

       analize = (char)->
         dump.push char
+        inString = undefined if inString? and inString == false
+        endOfLine = undefined if endOfLine?
         endMatched = undefined if endMatched?
-        endOfLine = undefined if endOfLine?
+        inDSLParams = undefined if inDSLParams? and inDSLParams == false
         beginMatched = undefined if beginMatched?
         callbackBegins = undefined if callbackBegins?
-        inDSLParams = undefined if inDSLParams? and inDSLParams == false
-        inDescribe = undefined if inDescribe? and inDescribe == false
-        inString = undefined if inString? and inString == false

         switch
           when dump.buffer() == 'tion () {' and not inCallback?
             callbackBegins = inCallback = true
-          when dump.buffer() == 'describe(' and not inDescribe?
-            inDescribe = true
-          when char == '(' and inDSLParams? and not inString? and not inDescribe?
+          when char == '(' and inDSLParams? and not inString?
             parentheses.push char
-          when char == ')' and inDSLParams? and not inString? and not inDescribe?
+          when char == ')' and inDSLParams? and not inString?
             inDSLParams = parentheses.pop()
             endMatched = true unless inDSLParams?
-          when char == '(' and inDescribe? and not inString?
-            parentheses.push char
-          when char == ')' and inDescribe? and not inString?
-            inDescribe = parentheses.pop()
           when dump.buffer().substring(8) == "\n" and not inString?
             endOfLine = true
-          when dump.buffer().substring(5) == '.is(' and not inDSLParams? and not inDescribe?
+          when dump.buffer().substring(5) == '.is(' and not inDescribe?
             inDSLParams = beginMatched = true
-          when char.match(/'|"/)? and (inDSLParams? or inDescribe?) and strings.indexOf(char) < 0 and not dump.buffer(7) == "\\"
+          when char.match(/'|"/)? and inDSLParams? and strings.indexOf(char) < 0 and not dump.buffer(7) == "\\"
             strings.push(char)
             inString = true
-          when char.match(/'|"/)? and (inDSLParams? or inDescribe?) and not dump.buffer(7) == "\\"
+          when char.match(/'|"/)? and inDSLParams? and not dump.buffer(7) == "\\"
             strings.splice(strings.indexOf(char), 1)
             inString = strings.length > 0

@@ -174,9 +170,12 @@
       # console.log res.toString()
       # res
       # console.log Result.result()
-      eval "(#{Result.result()});"
+      # eval "(#{Result.result()});"
+      # console.log Result.result()
+      eval("(#{Result.result()});")

     @run = ->
+      console.log 'callback wrapper called'
       @prepareCallback().call Context.get()

     this


remove unused component

remove unused component

--- app/assets/javascripts/components/mark/tools/rectangle-tool/delete-button.cjsx
+++ app/assets/javascripts/components/mark/tools/rectangle-tool/delete-button.cjsx
@@ -1,38 +0,0 @@
-React = require 'react'
-
-RADIUS = 8
-STROKE_COLOR = '#000'
-FILL_COLOR = '#f00'
-STROKE_WIDTH = 1.5
-
-CROSS_PATH = "
-  M #{-1 * RADIUS * 0.7 } 0
-  L #{RADIUS * 0.7 } 0
-  M 0 #{-1 * RADIUS * 0.7 }
-  L 0 #{RADIUS * 0.7 }
-"
-
-DESTROY_TRANSITION_DURATION = 0
-
-module.exports = React.createClass
-  displayName: 'DeleteButton'
-
-  getDefaultProps: ->
-    x: 0
-    y: 0
-    rotate: 0
-
-  render: ->
-    transform = "
-      translate(#{@props.x+40}, #{@props.y-40})
-      rotate(#{@props.rotate})
-      scale(#{1 / @props.tool.props.xScale}, #{1 / @props.tool.props.yScale})
-    "
-    <g className="mark-tool delete-button" transform={transform} stroke={STROKE_COLOR} strokeWidth={STROKE_WIDTH} onClick={@destroyTool}>
-      <circle r={RADIUS} fill={FILL_COLOR} />
-      <path d={CROSS_PATH} transform="rotate(45)" />
-    </g>
-
-  destroyTool: ->
-    @props.tool.setState destroying: true, =>
-      setTimeout @props.tool.props.onDestroy, DESTROY_TRANSITION_DURATION


Options may be well, 'optional'

Options may be well, 'optional'

--- client/lanes/models/Collection.coffee
+++ client/lanes/models/Collection.coffee
@@ -80,7 +80,7 @@
             )
             this.remove(models)
         else
-            update = if options.reset then 'reset' else 'set'
+            update = if options?.reset then 'reset' else 'set'
             this[update](data, options)
         if this._loaded_callbacks
             cb(this) for cb in this._loaded_callbacks


ui: fmt member counts according to the user's locale

--- client/landing/app/Applications/Members.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Members.kdapplication/AppController.coffee
@@ -34,7 +34,7 @@
           placement         : "above"
       filter                :
         everything          :
-          title             : "All Members "
+          title             : "All members "
           optional_title    : if @_searchValue then "" else null
           dataSource        : (selector, options, callback)=>
             {JAccount} = KD.remote.api
@@ -249,7 +249,8 @@
     return contentDisplay

   setCurrentViewNumber:(type, count)->
-    @getView().$(".feeder-header span.member-numbers-#{type}").html count ? "n/a"
+    countFmt = count.toLocaleString() ? "n/a"
+    @getView().$(".feeder-header span.member-numbers-#{type}").text countFmt

   setCurrentViewHeader:(count)->
     if typeof 1 isnt typeof count


empty feedback modal when feedback is completed

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -6,7 +6,9 @@
     $.ajax form.attr("target"),
       method: 'POST'
       dataType: 'json',
-      complete: -> $("#feedback_form").modal("hide")
+      complete: ->
+        $("#feedback_form").modal("hide")
+        $("#message").val('')
       data: {
         name: $("#name", form).val(),
         page: $("#page", form).val(),


[#157] Fixes markers rendering on map

[#157] Fixes markers rendering on map

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -44,7 +44,7 @@
     })

   if $("#reports-map").length > 0
-    reports_markers = $(".report").map ->
+    reports_markers = $(".recent-report-sum").map ->
       return { lat: $(this).data("lat"), lng: $(this).data("lng"), description: $(this).data("description") }

     $map = $(".map")


Cleanup PodsController js.

Cleanup PodsController js.

--- app/assets/javascripts/pods.js.coffee
+++ app/assets/javascripts/pods.js.coffee
@@ -12,10 +12,12 @@
     xhr.open('GET', '/pods/'+chunk_id+'.mpac', true)
     xhr.responseType = 'arraybuffer'
     xhr.onload = (e) ->
-      controller.renderPodsChunk(@.response, chunk_id)
+      pods = msgpack.decode(@.response)
+      controller.podsChunkDidLoad(chunk_id, pods)
     xhr.send()
-  renderPodsChunk: (response, chunk_id) ->
-    pods = msgpack.decode(response)
+  podsChunkDidLoad: (chunk_id, pods) ->
+    @.renderPodsChunk(chunk_id, pods)
+  renderPodsChunk: (chunk_id, pods) ->
     html = JST['pods_tpl']
       pods: pods
     $('#list_placeholder').append(html)


Fix a bug on folding comments

Fix a bug on folding comments

--- app/assets/javascripts/toolbar.coffee
+++ app/assets/javascripts/toolbar.coffee
@@ -97,20 +97,21 @@
       items = $(@options.folding)
       items.find(".folding").remove()
       for i in items
-        item = $(i)
-        score = parseInt(item.find(".score:first").text(), 10)
-        link = item.children("h2").prepend('[-]').children(".folding")
-        fold = (b) ->
-          if b
-            item.addClass "fold"
-            link.text("[+]").attr "title", "Déplier"
-          else
-            item.removeClass "fold"
-            link.text("[-]").attr "title", "Plier"
-        link.click ->
-          fold link.text() == "[-]"
-          false
-        fold score < @threshold
+        do (i) ->
+          item = $(i)
+          score = parseInt(item.find(".score:first").text(), 10)
+          link = item.children("h2").prepend('[-]').children(".folding")
+          fold = (b) ->
+            if b
+              item.addClass "fold"
+              link.text("[+]").attr "title", "Déplier"
+            else
+              item.removeClass "fold"
+              link.text("[-]").attr "title", "Plier"
+          link.click ->
+            fold link.text() == "[-]"
+            false
+          fold score < @threshold

   Toolbar.defaultOptions =
     folding: null


bug in level

bug in level

--- app/assets/jax/models/level.js.coffee
+++ app/assets/jax/models/level.js.coffee
@@ -151,33 +151,33 @@

     # create object
     if type != ' ' and not @objects[cols_number*m + n]
-      object = window.raphael_div.image('images/box64.png', start.x, start.y, size, size)
+      object = window.raphael_div.image('/images/box64.png', start.x, start.y, size, size)
       @objects[cols_number*m + n] = object
     else if type != ' '
       object = @objects[cols_number*m + n]

     # refresh material
-    if type == 's' and object.attrs.src != 'images/floor64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/floor64.png', start.x, start.y, size, size)
-    else if type == '#' and object.attrs.src != 'images/wall64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/wall64.png', start.x, start.y, size, size)
-    else if type == '$' and object.attrs.src != 'images/box64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/box64.png', start.x, start.y, size, size)
-    else if type == '*' and object.attrs.src != 'images/boxgoal64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/boxgoal64.png', start.x, start.y, size, size)
-    else if type == '.' and object.attrs.src != 'images/goal64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/goal64.png', start.x, start.y, size, size)
-    else if type == '@' and object.attrs.src != 'images/pusher64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/pusher64.png', start.x, start.y, size, size)
-    else if type == '+' and object.attrs.src != 'images/pushergoal64.png'
-      object.remove()
-      @objects[cols_number*m + n] = object = window.raphael_div.image('images/pushergoal64.png', start.x, start.y, size, size)
+    if type == 's' and object.attrs.src != '/images/floor64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/floor64.png', start.x, start.y, size, size)
+    else if type == '#' and object.attrs.src != '/images/wall64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/wall64.png', start.x, start.y, size, size)
+    else if type == '$' and object.attrs.src != '/images/box64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/box64.png', start.x, start.y, size, size)
+    else if type == '*' and object.attrs.src != '/images/boxgoal64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/boxgoal64.png', start.x, start.y, size, size)
+    else if type == '.' and object.attrs.src != '/images/goal64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/goal64.png', start.x, start.y, size, size)
+    else if type == '@' and object.attrs.src != '/images/pusher64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/pusher64.png', start.x, start.y, size, size)
+    else if type == '+' and object.attrs.src != '/images/pushergoal64.png'
+      object.remove()
+      @objects[cols_number*m + n] = object = window.raphael_div.image('/images/pushergoal64.png', start.x, start.y, size, size)

   # Render every squares of the level (the level itself is just a mesh container)
   # ONLY IF IN 3D MODE !


Fixing us creation problem with points form

--- app/coffee/controllers/backlog.coffee
+++ app/coffee/controllers/backlog.coffee
@@ -167,7 +167,7 @@
         $scope.milestones = data

     initializeUsForm = ->
-        points = []
+        points = {}
         for role in $scope.constants.computableRolesList
             points[role.id] = $scope.project.default_points


deleted unneeded lines

deleted unneeded lines

--- app/controllers/patients/index.coffee
+++ app/controllers/patients/index.coffee
@@ -4,11 +4,6 @@
   selectedItems: Em.computed.filterBy('model.populations', 'selected', true)
   selectedItemsCount: (-> @get('selectedItems.length')).property('selectedItems.[]')
   populations: []
-  currentPatient: null
-
-  # actions:
-  #   showPatient: (patient) ->
-  #     @set('currentPatient', patient)
 )

 `export default PatientsIndexController`


Dont display toggle if all boxes are favorites

--- client/app/components/menu.coffee
+++ client/app/components/menu.coffee
@@ -199,6 +199,7 @@
         accountClasses = classer
             active: isActive

+
         if @state.onlyFavorites
             mailboxes = @state.favorites
             icon = 'fa-ellipsis-h'
@@ -207,6 +208,8 @@
             mailboxes = @state.mailboxes
             icon = 'fa-ellipsis-h'
             toggleFavoritesLabel = t 'menu favorites on'
+
+        allBoxesAreFavorite = @state.mailboxes.length is @state.favorites.length

         configMailboxUrl = @buildUrl
             direction: 'first'
@@ -292,16 +295,17 @@
                             refreshes:         refreshes,
                             displayErrors:     @displayErrors,
                     .toJS()
-                    li className: 'toggle-favorites',
-                        a
-                            role: 'menuitem',
-                            tabIndex: 0,
-                            onClick: toggleFavorites,
-                            key: 'toggle',
-                                i className: 'fa ' + icon
-                                span
-                                    className: 'item-label',
-                                    toggleFavoritesLabel
+                    unless allBoxesAreFavorite
+                        li className: 'toggle-favorites',
+                            a
+                                role: 'menuitem',
+                                tabIndex: 0,
+                                onClick: toggleFavorites,
+                                key: 'toggle',
+                                    i className: 'fa ' + icon
+                                    span
+                                        className: 'item-label',
+                                        toggleFavoritesLabel

     _initTooltips: ->
         #jQuery('#account-list [data-toggle="tooltip"]').tooltip()


Dock: taken to sidebar added VMs list

Dock: taken to sidebar added VMs list

--- client/Main/maincontroller/dockcontroller.coffee
+++ client/Main/maincontroller/dockcontroller.coffee
@@ -1,38 +1,65 @@
 class DockController extends KDViewController

+  isRunning = (item) -> item?.state is 'running'
+
   defaultItems = [
-    { title : "Activity",  path : "/Activity", order : 10, type :"persistent" }
-    { title : "Teamwork",  path : "/Teamwork", order : 20, type :"persistent" }
-    { title : "Terminal",  path : "/Terminal", order : 30, type :"persistent" }
-    { title : "Editor",    path : "/Ace",      order : 40, type :"persistent" }
-    { title : "Apps",      path : "/Apps",     order : 50, type :"persistent" }
-    { title : "DevTools",  path : "/DevTools", order : 60, type :"persistent" }
+    { title : 'Activity',  path : '/Activity', order : 10, type :'persistent' }
+    { title : 'IDE',       path : '/IDE',      order : 40, type :'persistent' }
+    { title : 'Terminal',  path : '/Terminal', order : 30, type :'persistent' }
+    # { title : 'Teamwork',  path : '/Teamwork', order : 20, type :'persistent' }
+    # { title : 'Apps',      path : '/Apps',     order : 50, type :'persistent' }
+    # { title : 'DevTools',  path : '/DevTools', order : 60, type :'persistent' }
   ]

+
   constructor:(options = {}, data)->
+
     options.view or= new KDCustomHTMLView domId : 'dock'

     super options, data

     @storage = new AppStorage "Dock", "1.0.3"

+    loaderOptions =
+      spinnerOptions    :
+        size            :
+          width         : 16
+        loaderOptions   :
+          color         : '#B8B8B8'
+
     @navController = new MainNavController
-      view         : new NavigationList
-        domId      : 'main-nav'
-        testPath   : 'navigation-list'
-        type       : 'navigation'
-        itemClass  : NavigationLink
-        testPath   : 'navigation-list'
-      wrapper      : no
-      scrollView   : no
+      view                : new NavigationList
+        domId             : 'main-nav'
+        testPath          : 'navigation-list'
+        type              : 'navigation'
+        itemClass         : NavigationLink
+        testPath          : 'navigation-list'
+      wrapper             : no
+      scrollView          : no
+      startWithLazyLoader : yes
+      lazyLoaderOptions   : loaderOptions
+
     ,
       id           : 'navigation'
       title        : 'navigation'
       items        : []

-    mainController = KD.getSingleton 'mainController'
-
-    mainController.ready @bound 'accountChanged'
+
+    @vmsList = new KDListViewController
+      wrapper             : no
+      scrollView          : no
+      startWithLazyLoader : yes
+      itemClass           : NavigationVMItem
+      lazyLoaderOptions   : loaderOptions
+    ,
+      id           : 'vms'
+      title        : 'vms'
+      items        : []
+
+
+    {mainController} = KD.singletons
+
+    # mainController.ready @bound 'accountChanged'

     @trackStateTransitions()
     @bindKeyCombos()
@@ -60,6 +87,7 @@

     return finalItems

+
   saveItemOrders:(items)->

     items or= @getItems()
@@ -73,6 +101,7 @@
     @storage.setValue 'navItems', navItems, (err)->
       warn "Failed to save navItems order", err  if err

+
   resetItemSettings:->
     item.order = index  for own index, item of defaultItems

@@ -83,9 +112,11 @@
       @navController.reset()
       "Navigation items has been reset."

+
   setNavItems:(items)->
     KD.setNavItems items
     @navController.reset()
+

   addItem:(item)->

@@ -94,6 +125,7 @@
       @navController.addItem item
       @saveItemOrders()

+
   removeItem:(item)->

     return  if item.data.type is 'persistent'
@@ -109,10 +141,11 @@
     @navController.removeItem item
     @saveItemOrders()

+
   accountChanged:->

     @navController.reset()
-    @storage.fetchValue 'navItems', (usersNavItems)=>
+    @storage.fetchValue 'navItems', (usersNavItems) =>

       unless usersNavItems
         @setNavItems defaultItems
@@ -155,14 +188,47 @@
           @addItem { title : name, path, \
                      order : 60 + KD.utils.uniqueId(), type :"" }

-  loadView:(dock)->
+  listVMs: (vms) ->
+
+    @vmsList.hideLazyLoader()
+
+    @vmsList.addItem vm  for vm in vms
+
+
+  loadView: (dock) ->
+
+    dock.addSubView new KDCustomHTMLView
+      tagName  : 'h3'
+      cssClass : 'sidebar-title'
+      partial  : 'MY APPS'
+
+    dock.addSubView @navController.getView()
+
+    dock.addSubView new KDCustomHTMLView
+      tagName  : 'h3'
+      cssClass : 'sidebar-title'
+      partial  : 'MY VMs'
+
+    dock.addSubView @vmsList.getView()
+
+    # @navController.reset()
+    # @setNavItems defaultItems
+    @setNavItems defaultItems
+    @emit 'ready'
+
+    {vmController} = KD.singletons
+
+    vmController.fetchVMs no, (err, vms) =>
+      if err
+        ErrorLog.create 'terminal: Couldn\'t fetch vms', reason : err
+        return new KDNotificationView title : 'Couldn\'t fetch your VMs'
+
+      vms.sort (a,b) -> a.hostnameAlias > b.hostnameAlias
+
+      @listVMs vms
+

     @ready =>
-
-      @scrollView = new KDCustomScrollView
-
-      dock.addSubView @scrollView
-      @scrollView.wrapper.addSubView @navController.getView()

       # Listen appManager to update dock items states
       {appManager, kodingAppsController} = KD.singletons
@@ -170,20 +236,22 @@
       for name of appManager.appControllers
         @setNavItemState {name}, 'active'

+      @navController.hideLazyLoader()
+
       {appManager, kodingAppsController} = KD.singletons

-      appManager.on "AppRegistered", (name, options) =>
+      appManager.on 'AppRegistered', (name, options) =>
         @setNavItemState {name, options}, 'running'

-      appManager.on "AppUnregistered", (name, options) =>
+      appManager.on 'AppUnregistered', (name, options) =>
         @setNavItemState {name, options}, 'initial'

-      appManager.on "AppIsBeingShown", (instance, view, options) =>
+      appManager.on 'AppIsBeingShown', (instance, view, options) =>
         @setNavItemState {name:options.name, options}, 'active'

-  isRunning = (item) -> item?.state is 'running'

   getRelativeItem: (increment, predicate) ->
+
     i = @activeIndex
     len = @navController.itemsOrdered.length
     loop
@@ -195,20 +263,28 @@
       item = @itemStates[i]
       return item  if item is this or predicate item

+
   activatePreviousApp: (e) ->
+
     e.preventDefault()
     item = @getRelativeItem -1, isRunning
     @setActiveItem item.item

+
   activateNextApp: (e) ->
+
     e.preventDefault()
     item = @getRelativeItem 1, isRunning
     @setActiveItem item.item

+
   setActiveItem: (item) ->
+
     KD.singletons.router.handleRoute item.getData().path

+
   trackStateTransitions: ->
+
     @itemStates = []
     @activeIndex = null
     @on 'NavItemStateChanged', (info) ->
@@ -216,7 +292,9 @@
       @itemStates[index] = info
       @activeIndex = index  if state is 'running'

+
   openApp: (index) ->
+
     len = @navController.itemsOrdered.length
     index += len  if index < 0
     item = @navController.itemsOrdered[index]
@@ -224,7 +302,9 @@
     { path } = item.getData()
     KD.singletons.router.handleRoute path

+
   bindKeyCombos: ->
+
     { globalKeyCombos } = KD.singletons

     globalKeyCombos


fixed highlighting of matching braces

fixed highlighting of matching braces

CodeMirror treats the token type as a sequence of whitespace-separated CSS class names and order shouldn't matter,
but the scanner for a matching bracket relies on the two brackets having the *exact* same token type.

Mantis 12475

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -85,7 +85,7 @@
         when '{' then a.push t: c, oi: n, ii: n + sw; "apl-dfn#{dfnDepth a} apl-dfn"
         when ')' then (if la.t == '(' then a.pop(); 'apl-par'  else 'apl-err')
         when ']' then (if la.t == '[' then a.pop(); 'apl-sqbr' else 'apl-err')
-        when '}' then (if la.t == '{' then a.pop(); "apl-dfn apl-dfn#{1 + dfnDepth a}" else 'apl-err')
+        when '}' then (if la.t == '{' then a.pop(); "apl-dfn#{1 + dfnDepth a} apl-dfn" else 'apl-err')
         when ';' then la.t == '[' && 'apl-semi' || 'apl-err'
         when '⋄' then la.t !in ['(', '['] && 'apl-diam' || 'apl-err'
         when '⎕' then stream.match(/[áa-z0-9]*/i)?[0].toLowerCase() in quadNames && 'apl-quad' || 'apl-err'


Send HTML content even for simple text

Send HTML content even for simple text

--- client/app/actions/message_action_creator.coffee
+++ client/app/actions/message_action_creator.coffee
@@ -24,6 +24,11 @@

     send: (action, message, callback) ->
         conversationID = message.conversationID
+
+        # Message should have a html content
+        # event if it is a simple text
+        unless message.composeInHTML
+            message.html = message.text

         XHRUtils.messageSend message, (error, message) =>
             if error? or not message?


Fix for new layout builder in Panel.

Fix for new layout builder in Panel.

--- client/ide/workspace/panel.coffee
+++ client/ide/workspace/panel.coffee
@@ -14,10 +14,13 @@

   createLayout: ->
     {layoutOptions}  = @getOptions()
+
     unless layoutOptions
       throw new Error 'You should pass layoutOptions to create a panel'

-    @layout = new IDE.WorkspaceLayoutBuilder { delegate: this, layoutOptions }
+    layoutOptions.delegate = this
+
+    @layout = new IDE.WorkspaceLayoutBuilder layoutOptions
     @addSubView @layout

   createPane: (paneOptions) ->


client: update google default template

client: update google default template

--- client/app/lib/providers/templates/google.coffee
+++ client/app/lib/providers/templates/google.coffee
@@ -14,7 +14,7 @@
   "resource": {
     "google_compute_instance": {
       "google-instance": {
-        "name": "google-instance",
+        "name": "koding-${var.koding_group_slug}-${var.koding_stack_id}-${count.index+1}",
         "machine_type": "f1-micro",
         "disk": {
           "image": "ubuntu-1404-lts"
@@ -46,7 +46,7 @@
     # this is the name of your VM
     google-instance:
       # and this is its identifier (required)
-      name: google-instance
+      name: 'koding-${var.koding_group_slug}-${var.koding_stack_id}-${count.index+1}'
       # select your instance_type here: eg. n1-standard-1
       machine_type: f1-micro
       # base image for your instance


Make IR query reactive when smart event is edited

--- client/controllers/smartEvents/smartEvent.coffee
+++ client/controllers/smartEvents/smartEvent.coffee
@@ -7,18 +7,20 @@
 Template.smartEvent.onCreated ->
   @editState = new ReactiveVar false
   @eventId = new ReactiveVar()
+
+Template.smartEvent.onRendered ->
+  eventId = Router.current().getParams()._id
+  @eventId.set(eventId)
+  @subscribe 'smartEvents', eventId
   @autorun =>
-    eventId = Router.current().getParams()._id
-    @eventId.set eventId
-    @subscribe 'smartEvents', eventId,
-      onReady: =>
-        event = SmartEvents.findOne(eventId)
-        eventDateRange = event.dateRange
-        query = disease: event.disease
-        if eventDateRange
-          query['dateRange.start'] = $lte: eventDateRange.end
-          query['dateRange.end'] = $gte: eventDateRange.start
-        @subscribe 'smartEventIncidents', query
+    event = SmartEvents.findOne(eventId)
+    if event
+      eventDateRange = event.dateRange
+      query = disease: event.disease
+      if eventDateRange
+        query['dateRange.start'] = $lte: eventDateRange.end
+        query['dateRange.end'] = $gte: eventDateRange.start
+      @subscribe 'smartEventIncidents', query

 Template.smartEvent.onRendered ->
   new Clipboard '.copy-link'
@@ -37,7 +39,7 @@
     Incidents.find().count()

   incidentReportsTemplateData: ->
-    incidents: Incidents.find({}, {sort: {'dateRange.end': 1}})
+    incidents: Incidents.find({}, sort: 'dateRange.end': 1)
     eventType: 'smart'

 Template.smartEvent.events


Use 100 points as the "I don't have data" marker.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -2980,7 +2980,7 @@
                 'Crew'
                 'Crew'
             ]
-            points: 99
+            points: 100
         }
         {
             name: 'Hera Syndulla (Attack Shuttle)'
@@ -3061,7 +3061,7 @@
             slots: [
                 'Missile'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Baron of ???"
@@ -3072,7 +3072,7 @@
             slots: [
                 'Missile'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Sienar ???"
@@ -3083,7 +3083,7 @@
             slots: [
                 'Missile'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Zuckuss"
@@ -3112,7 +3112,7 @@
                 'System'
                 'Illicit'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Gand ???"
@@ -3125,7 +3125,7 @@
                 'System'
                 'Illicit'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Ruth???"
@@ -3138,7 +3138,7 @@
                 'System'
                 'Illicit'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Dengar"
@@ -3172,7 +3172,7 @@
                 'Salvaged Astromech'
                 'Illicit'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Manaroo"
@@ -3189,7 +3189,7 @@
                 'Salvaged Astromech'
                 'Illicit'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Contrac???"
@@ -3205,7 +3205,7 @@
                 'Salvaged Astromech'
                 'Illicit'
             ]
-            points: 99
+            points: 100
         }

         # T-70
@@ -3454,7 +3454,7 @@
                 'Missile'
                 'Bomb'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Maarek Stele (TIE Defender)"
@@ -3468,7 +3468,7 @@
                 'Cannon'
                 'Missile'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Glaive Squa???"
@@ -3480,7 +3480,7 @@
                 'Cannon'
                 'Missile'
             ]
-            points: 99
+            points: 100
         }
         {
             name: "Count???"
@@ -3493,7 +3493,7 @@
                 'Cannon'
                 'Missile'
             ]
-            points: 99
+            points: 100
         }
     ]


add ctrl-a/e for atm editor

add ctrl-a/e for atm editor

--- atom/linux/keymap.cson
+++ atom/linux/keymap.cson
@@ -28,3 +28,5 @@
     'shift-alt-t': 'date:time'
     'shift-alt-f': 'narrow:search'
     'shift-alt-w': 'tabs:close-other-tabs'
+    'ctrl-a': 'editor:move-to-first-character-of-line'
+    'ctrl-e': 'editor:move-to-end-of-line'


started on a editor manager but temporarily aborted

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -1,5 +1,12 @@
 String.prototype.startsWith ?= (str) ->
     return @lastIndexOf(str, 0) == 0
+
+# class window.EditorManager
+#     ###
+#         Manages the code editor.
+#     ###
+#     constructor: (@editorDivId) ->
+

 class window.PlayerCodeEditor


More robust mechanism for handling next step messages

--- src/coffee/controllers/history.coffee
+++ src/coffee/controllers/history.coffee
@@ -7,6 +7,7 @@
     scope.providers = []
     scope.collapsed = true # Hide details in reduced real-estate view.
     scope.items = {}
+    scope.messages = {}
     scope.state = {}

     toolNotFound = (e) -> to -> scope.error = e
@@ -24,14 +25,20 @@

     scope.nextSteps = []

+    scope.watchDeeply = (name, f) -> scope.$watch ((s) -> JSON.stringify s[name]), -> f s[name]
+
     scope.$watchCollection 'items', ->
-      console.log "Items changed"
       exporters = []
       for tool in scope.nextTools when tool.handles is 'items'
         for key, data of scope.items when data.ids.length
           exporters.push {key, data, tool}
       otherSteps = (s for s in scope.nextSteps when s.tool.handles isnt 'items')
       scope.nextSteps = otherSteps.concat(exporters)
+
+    scope.$watchCollection 'messages', (msgs) ->
+      handlers = L.values msgs
+      otherSteps = (s for s in scope.nextSteps when s.kind isnt 'msg')
+      scope.nextSteps = otherSteps.concat(handlers)

     scope.$watch 'list', ->
       listHandlers = []
@@ -50,9 +57,16 @@

     scope.setItems = (key, type, ids) -> to -> scope.items[key] = {type, ids}

-    scope.hasSomething = (what, data) ->
+    scope.hasSomething = (what, data, key) ->
       if what is 'list'
         to -> scope.list = data
+      else
+        for tool in scope.nextTools when tool.handles is what then do (tool, what, data, key) ->
+          idx = tool.ident + what + key
+          if scope.messages[idx]?
+            scope.messages[idx].data = data
+          else
+            to -> scope.messages[idx] = {tool, data, kind: 'msg'}

     letUserChoose = (tools) ->
       dialogue = $modal.open


updating label for description field

updating label for description field

--- src/components/task-plan/builder.cjsx
+++ src/components/task-plan/builder.cjsx
@@ -131,7 +131,7 @@
       </BS.Row><BS.Row>
         <BS.Col xs=12>
           <TutorTextArea
-            label='Description'
+            label='Instructions for students'
             className='assignment-description'
             id='assignment-description'
             default={TaskPlanStore.getDescription(@props.id)}


no more default labels

no more default labels

--- src/components/task-plan/builder.cjsx
+++ src/components/task-plan/builder.cjsx
@@ -15,11 +15,6 @@
 {TutorInput, TutorDateInput, TutorDateFormat, TutorTextArea} = require '../tutor-input'
 {CourseStore}   = require '../../flux/course'
 {UnsavedStateMixin} = require '../unsaved-state'
-
-DEFAULT_LABELS =
-  assignment: 'Assignment'
-  openDate: 'Open Date'
-  dueDate: 'Due Date'

 module.exports = React.createClass
   displayName: 'TaskPlanBuilder'


Only keep menu for login

Only keep menu for login

--- resources/assets/coffee/_classes/nav.coffee
+++ resources/assets/coffee/_classes/nav.coffee
@@ -73,7 +73,7 @@

   gracefulHidePopup: =>
-    return if @currentMode() != 'default'
+    return if @currentMode() == 'user' && !currentUser.id?

     Timeout.clear @hideTimeout
     @hideTimeout = Timeout.set 250, @hidePopup


DateTime: Change input_types default, default to all available input types.

--- src/elements/DateTime/DateTime.coffee
+++ src/elements/DateTime/DateTime.coffee
@@ -31,7 +31,6 @@
 				check: (v) ->
 					CUI.isArray(DateTimeFormats[v]?.formats)
 			input_types:
-				default: ["date_time"]
 				check: Array
 			display_type:
 				default: "long"


throw error if no groupId in logging

throw error if no groupId in logging

--- lib/logging.coffee
+++ lib/logging.coffee
@@ -12,7 +12,7 @@

   unless groupId
     throw new Meteor.Error(403, ErrMsg.userIdErr) unless userId
-    groupId = Grouping.findOne(userId).groupId
+    groupId = Grouping.findOne(userId)?.groupId
     throw new Meteor.Error(403, ErrMsg.groupErr) unless groupId

   doc._userId = userId if userId


add roles to user options

add roles to user options

--- lib/options.coffee
+++ lib/options.coffee
@@ -18,6 +18,7 @@
   userKeys: [
     'id', 'type', 'name', 'email', 'phone', 'lab_features',
     'default_profile_id', 'has_partner_access', 'collector_level',
-    'recaptcha_token'
+    'recaptcha_token',
+    'roles'
   ]
 }


need an extra timeout for some reason

need an extra timeout for some reason

--- lib/plastic.coffee
+++ lib/plastic.coffee
@@ -18,8 +18,12 @@
     initPlastic = ->
       goog.require("plastic.main.loop")
       goog.require("plastic.worker.loop") if plastic.env.run_worker_on_main_thread
-      bridge.send "apis", apis
-      bridge.send "init", state # TODO: unserialize our part and pass rest
+
+      initAPIs = ->
+        bridge.send "apis", apis
+        bridge.send "init", state # TODO: unserialize our part and pass rest
+
+      setTimeout initAPIs, 100

     setTimeout initPlastic, 1000 # TODO: this is temporary, give figwheel and cljs-devtools some time to intialize


Fixes unreferred variable

Fixes unreferred variable

Signed-off-by: Robert Wettstädt <c6f9a55af61d8b6c89c7755b13e23873e9280669@gmail.com>

--- lib/printer.coffee
+++ lib/printer.coffee
@@ -3,7 +3,7 @@
 commands    = require './commands'
 config      = require './config'

-module.exports =
+module.exports =

     log : ( namespace, msg ) ->

@@ -57,10 +57,7 @@
         for index in [followingCount...streams.length]
             printStream streams[index], index

-        if debug
-            console.log "\t#{chalk.green 'Results'}"
-        else
-            console.log output
+        console.log output
         @help()

@@ -94,7 +91,7 @@

             if command.args?
                 output += '<'
-
+
                 for arg, index in command.args
                     output += "#{arg}"

@@ -146,7 +143,7 @@

         console.log output

-        setTimeout =>
+        setTimeout =>
             @help()
         , 300


Detect ruby file by current grammar

Detect ruby file by current grammar

--- lib/rubocop.coffee
+++ lib/rubocop.coffee
@@ -7,10 +7,6 @@
       title: 'Rubocop command'
       type: 'string'
       default: 'rubocop'
-    additionalRubyFiles:
-      title: 'Additional Ruby files'
-      type: 'array'
-      default: ['Gemfile', 'Rakefile', 'Guardfile']

   subscriptions: null

@@ -27,8 +23,7 @@
   serialize: ->

   isRubyFile: (filePath) ->
-    atom.config.get('rubocop.additionalRubyFiles').indexOf(path.basename(filePath)) > -1 or
-      path.extname(filePath) == '.rb'
+    atom.workspace.getActiveTextEditor().getGrammar().scopeName == 'source.ruby'

   autocorrectCurrentFile: ->
     editor = atom.workspace.getActivePaneItem()


Refactored hulk.coffee

Refactored hulk.coffee

--- hulk.coffee
+++ hulk.coffee
@@ -1,6 +1,6 @@
 {join} = require 'path'

-getPartials = (source)->  # Return list of patial filenames in the source.
+parsePartials = (source)->  # Return list of patial filenames in the source.
 	{scan} = require 'hogan.js'
 	result = []
 	scanned = scan source
@@ -13,6 +13,17 @@
 	{readFileSync} = require 'fs'
 	return readFileSync(file).toString() # TODO better way to read file, especially large ones.

+{existsSync} = require 'path'
+makePartials = (partials, list, root, defaultEngine)->
+	for file in list
+		filepath = join root, file
+
+		if existsSync filepath
+			partials[file] = readFile filepath
+		else if defaultEngine and existsSync filepath+=".#{defaultEngine}"
+			partials[file] = readFile filepath
+
+
 exports.compile = (source='', options)->
 	hogan = require 'hogan.js'
 	compiled = hogan.compile source
@@ -21,17 +32,10 @@

 	partials = {}
 	# Get Partials #
-	partial_files = getPartials source
+	partial_files = parsePartials source

 	if partial_files.length
-		{existsSync} = require 'path'
-		for file in partial_files
-			filepath = join options.root, file
+		makePartials partials, partial_files, options.root, options.defaultEngine

-			if existsSync filepath
-				partials[file] = readFile filepath
-			else if options.defaultEngine and existsSync filepath+=".#{options.defaultEngine}"
-				partials[file] = readFile filepath
-
 	return ->
 		return compiled.render options, partials


Added __trim method

Added __trim method

--- meta-engine.coffee
+++ meta-engine.coffee
@@ -275,12 +275,16 @@
       right = content.slice tagLineEndIndex, content.length
       content = left + middle + right

-
-
     return content

-  __processSync: (resourcePath, isolated = false)->
+  __trim: (content)->
+    content = content.replace /^\s*/g, ''
+    content = content.replace /\s*$/g, ''
+    return content
+
+
+  __processSync: (resourcePath, isolated = false, trim = true)->

     content = @contentProvider.getContentSync resourcePath

@@ -292,6 +296,10 @@

       content = @__processSyncUseTag resourcePath, content, regionMap

+      if trim
+
+        content = @__trim content
+
     return content


fixed code style on servers.lib.crawlers.staticpages.bodyrenderer.coffee

--- servers/lib/crawler/staticpages/bodyrenderer.coffee
+++ servers/lib/crawler/staticpages/bodyrenderer.coffee
@@ -1,8 +1,8 @@
 # This code block is copied from client side (activitylistitem).
 # As a future work it could be implemented as a node module.

-{argv} = require 'optimist'
-{uri}  = require('koding-config-manager').load("main.#{argv.c}")
+{ argv } = require 'optimist'
+{ uri }  = require('koding-config-manager').load("main.#{argv.c}")

 # We're using an older version of highlight.js that has the method
 # `getLanguage`. I wasn't able to find out which version, therefore
@@ -14,12 +14,12 @@
   # If href goes to outside of koding, add rel=nofollow.
   # this is necessary to prevent link abusers.
   renderer = new marked.Renderer()
-  renderer.link= (href, title, text)->
+  renderer.link = (href, title, text) ->
     linkHTML = "#{text}"
     else
@@ -39,7 +39,7 @@

   # default case for regular text
   body.replace /\B\@([\w\-]+)/gim, (u) ->
-    username = u.replace "@", ""
+    username = u.replace '@', ''
     "#{u}"

 formatBody = (body = '') ->


neo4j: log fetchAll query

neo4j: log fetchAll query

--- server/lib/server/graph/graph.coffee
+++ server/lib/server/graph/graph.coffee
@@ -49,6 +49,8 @@
       groupId   : @groupId
       startDate : startDate

+    console.log query, startDate, @groupId
+
     @db.query query, params, (err, results)=>
       tempRes = []
       if err then callback err


Refactored the glsl api a bit (using helper functions to simplify)

--- src/generator/compile.glsl.api.coffee
+++ src/generator/compile.glsl.api.coffee
@@ -24,7 +24,7 @@
           when -1 then "-#{b}"
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{if (a | 0) == a then (a + '.0') else a} * #{b}"
+            "#{glsl.floatLit a} * #{b}"
       else if typeof b == 'number'
         switch b
           when 0 then 0
@@ -32,7 +32,7 @@
           when -1 then "-#{a}"
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{a} * #{if (b | 0) == b then (b + '.0') else b}"
+            "#{a} * #{glsl.floatLit b}"
       else
         "#{a} * #{b}"
       # TODO: handle vectors
@@ -75,13 +75,13 @@
           when 0 then 0
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{if (a | 0) == a then (a + '.0') else a} / #{b}"
+            "#{glsl.floatLit a} / #{b}"
       else if typeof b == 'number'
         switch b
           when 0 then "#{a} / 0.0" # infinity
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{a} / #{if (b | 0) == b then (b + '.0') else b}"
+            "#{a} / #{glsl.floatLit b}"
       else
         "#{a} / #{b}"
       # TODO: handle vectors
@@ -99,7 +99,7 @@
           when 0 then b
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{if (a | 0) == a then (a + '.0') else a} + #{b}"
+            "#{glsl.floatLit a} + #{b}"
       else if typeof b == 'number'
         glsl.add b, a
       else
@@ -119,13 +119,13 @@
           when 0 then glsl.neg b
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{if (a | 0) == a then (a + '.0') else a} - #{b}"
+            "#{glsl.floatLit a} - #{b}"
       else if typeof b == 'number'
         switch b
           when 0 then a
           else
             # Number must not be integers (bitwise op converts operand to integer)
-            "#{a} - #{if (b | 0) == b then (b + '.0') else b}"
+            "#{a} - #{glsl.floatLit b}"
       else
         "#{a} - #{b}"
       # TODO: handle vectors
@@ -157,12 +157,7 @@
       else if Array.isArray a and Array.isArray b
         if a.length != b.length
           throw "Cannot perform min operation with array operands of different lengths"
-        numbersOnly = true
-        for i in [0...a.length]
-          if typeof a[i] != 'number' or typeof b[i] != 'number'
-            numbersOnly = false
-            break
-        if numbersOnly
+        if (isArrayType a, 'number') and (isArrayType b, 'number')
           (Math.min a[i], b[i]) for i in [0...a.length]
         else
           "min(#{glsl.vec3Lit a}, #{glsl.vec3Lit b})"
@@ -181,12 +176,7 @@
       else if Array.isArray a and Array.isArray b
         if a.length != b.length
           throw "Cannot perform operation with arrays of different lengths"
-        numbersOnly = true
-        for i in [0...a.length]
-          if typeof a[i] != 'number' or typeof b[i] != 'number'
-            numbersOnly = false
-            break
-        if numbersOnly
+        if (isArrayType a, 'number') and (isArrayType b, 'number')
           (Math.max a[i], b[i]) for i in [0...a.length]
         else
           "max(#{glsl.vec3Lit a}, #{glsl.vec3Lit b})"
@@ -203,12 +193,7 @@
       else if Array.isArray a and Array.isArray b
         if a.length != b.length
           throw "Cannot perform operation with arrays of different lengths"
-        numbersOnly = true
-        for i in [0...a.length]
-          if typeof a[i] != 'number' or typeof b[i] != 'number'
-            numbersOnly = false
-            break
-        if numbersOnly
+        if (isArrayType a, 'number') and (isArrayType b, 'number')
           (Math.max a[i], b[i]) for i in [0...a.length]
         else
           "max(vec3(#{a}), vec3(#{b}))"
@@ -225,12 +210,7 @@
       else if Array.isArray a and Array.isArray b
         if a.length != b.length
           throw "Cannot perform operation with arrays of different lengths"
-        numbersOnly = true
-        for i in [0...a.length]
-          if typeof a[i] != 'number' or typeof b[i] != 'number'
-            numbersOnly = false
-            break
-        if numbersOnly
+        if (isArrayType a, 'number') and (isArrayType b, 'number')
           (Math.max a[i], b[i]) for i in [0...a.length]
         else
           "max(vec3(#{a}), vec3(#{b}))"


fixed race condition with rendered and content helper

--- helpers.coffee
+++ helpers.coffee
@@ -45,12 +45,15 @@
     if (func = @currentLoadFunc())?
       Deps.nonreactive(func)

+    # don't rely on templateInstance.$ from rendered callback
+    # since sometimes it gets called after the Meteor.defer
+    selector = Template.instance();
+
     # Move things where they should go, after the template renders
     Meteor.defer =>
       # Animate spotlight and modal to appropriate positions
-      $spot = @templateInstance.$(".spotlight")
-      $modal = @templateInstance.$(".modal-dialog")
-
+      $spot = selector.$(".spotlight")
+      $modal = selector.$(".modal-dialog")
       # Move things where they should go
       [spotCSS, modalCSS] = @getPositions()
       $spot.animate(spotCSS)


Removed the regions directory

Removed the regions directory

--- core/app/backbone/layouts/main.coffee
+++ core/app/backbone/layouts/main.coffee
@@ -1,5 +1,3 @@
-#= require_directory ./../regions
-
 FactlinkApp.startSiteRegions = ->
   FactlinkApp.addRegions
     mainRegion:          '#main-wrapper'


Allow group save

Allow group save

--- core/app/backbone/models/group.coffee
+++ core/app/backbone/models/group.coffee
@@ -1,3 +1,4 @@
 class window.Group extends Backbone.Model
+  urlRoot: "/api/beta/groups"
   defaults:
     groupname: ''


Fixed out of bounds error in series graph

--- widgets/series_graph/series_graph.coffee
+++ widgets/series_graph/series_graph.coffee
@@ -4,7 +4,7 @@
     return @get('displayedValue') if @get('displayedValue')
     series = @get('series')
     if series
-      series[0][series.length - 1].y
+      series[0][series[0].length - 1].y

   ready: ->
     container = $(@node).parent()


Depending on the options passed through, we might not have stdout and stderr.

--- build/tasks/task-helpers.coffee
+++ build/tasks/task-helpers.coffee
@@ -52,8 +52,10 @@
     stderr = []
     error = null
     proc = childProcess.spawn(options.cmd, options.args, options.opts)
-    proc.stdout.on 'data', (data) -> stdout.push(data.toString())
-    proc.stderr.on 'data', (data) -> stderr.push(data.toString())
+    if proc.stdout?
+      proc.stdout.on 'data', (data) -> stdout.push(data.toString())
+    if proc.stderr?
+      proc.stderr.on 'data', (data) -> stderr.push(data.toString())
     proc.on 'error', (processError) -> error ?= processError
     proc.on 'close', (exitCode, signal) ->
       error ?= new Error(signal) if exitCode isnt 0