Added a failing test concerning shared observers on classes with Observable mixed in.

--- tests/batman/object_test.coffee
+++ tests/batman/object_test.coffee
@@ -32,11 +32,35 @@
   ok  @subSubClass.get
   ok  @subClass.set
   ok  @subSubClass.set
+
+  debugger

 test "classes shouldn't share attributes", ->
   @subSubClass.set("foo", "bar")
   equal @subSubClass.get("foo"), "bar"
   equal @subClass.get("foo"), undefined
+
+test "classes shouldn't share observables", ->
+  @subClass.observe 'foo', spy = createSpy()
+  @subSubClass.observe 'foo', subSpy = createSpy()
+  @subSubClass.set 'foo', 'bar'
+  Batman.Object.set 'foo', 'bar'
+
+  ok !spy.called
+  ok subSpy.called
+
+  @subClass.set 'foo', 'bar'
+  ok spy.called
+
+test "newly created classes shouldn't share observables", ->
+  @subClass.observe 'foo', spy = createSpy()
+
+  newSubClass = class TestSubClass extends @subClass
+
+  newSubClass.observe 'foo', subSpy = createSpy()
+  newSubClass.set 'foo', 'bar'
+  ok !spy.called
+  ok subSpy.called

 QUnit.module "Batman.Object properties"
   setup: ->
@@ -97,7 +121,7 @@

   @obj.set("foo", "something")
   equal @obj.somethingElse, "something"
-
+
 test "one object should not affect the other", ->
   @obj2 = new @klass

@@ -113,20 +137,20 @@
       get: () -> @test
       set: (value) ->
         @test = value * 2
-
+
     bar: @property
       get: () ->
         "silly"
       set: (value) ->
         "silly"
-
+
   @obj = new Custom
   @obj.set 'foo', 1
   @obj.observe 'foo', (value, oldValue) ->
     equals value, 4
     equals oldValue, 2
   @obj.set 'foo', 2
-
+
   @obj.observe 'bar', (value, oldValue) ->
     ok false, "Observer isn't supposed to be called because set doesn't return a different value!"
   @obj.set 'bar', 'weird'


<debug> Adding forgotten attribute call.

--- tests/controllers/driver.coffee
+++ tests/controllers/driver.coffee
@@ -64,7 +64,7 @@

         it 'should reply with the corresponding driver', ->
             expect(@err).to.not.exist
-            expect(@body.name).to.equal fixturesDriver.basicDriver
+            expect(@body.name).to.equal fixturesDriver.basicDriver.name
             expect(@body.id).to.equal store.driverId


settings: save "newsletter" checkbox state

--- www/src/views/user/settings.coffee
+++ www/src/views/user/settings.coffee
@@ -61,6 +61,7 @@
                 notify_likes: @$("[name=notify-likes]").is(":checked")
                 notify_invites: @$("[name=notify-invites]").is(":checked")
                 notify_followers: @$("[name=notify-followers]").is(":checked")
+                newsletter: @$("[name=newsletter]").is(":checked")

             settings


updated methods

updated methods


refactor

refactor

--- examples/lib/a-star/Engine.coffee
+++ examples/lib/a-star/Engine.coffee
@@ -16,154 +16,148 @@
 #   3. f: g + h
 ###

-AStar.Engine = (net) ->
-	Bu.Event.apply this
+class AStar.Engine

-	self = this
-	_net = net
+	constructor: (net) ->
+		Bu.Event.apply this

-	finished = false
+		_net = net

-	FPS = 10
+		@finished = false
+		@isRunning = false

-	listOpen = []
-	listClose = []
+		@fps = 10

-	nodeId = 0 # Add an id to every node for comparing when their "f" is equal: recent node is prior.
-	stepCount = 0
-
-
-	@noSolution = false
-	@result = {} # compute result contains: consumed time and step etc. TODO not added the time and step
-
-	init = ->
 		listOpen = []
 		listClose = []

-		_net.startNode.g = 0
-		_net.startNode.h = parseInt(_net.calcShortestDistance(_net.startNode, _net.endNode))
-		self.trigger 'nodeChanged', _net.startNode.position
+		nodeId = 0 # Add an id to every node for comparing when their "f" is equal: recent node is prior.
+		stepCount = 0

-		listOpen.push _net.startNode

-	# run the engine by one step
-	@step = ->
-		if !finished
-			_step()
-		else
-			console.log 'It has been finished.'
-		return
+		@noSolution = false
+		@result = {} # compute result contains: consumed time and step etc. TODO not added the time and step

-	# run the engine all the way to finish
-	@run = (fps) ->
-		FPS = fps or FPS
-		if fps
-			autoStep()
-		else
-			while !finished
+		init = =>
+			listOpen = []
+			listClose = []
+
+			_net.startNode.g = 0
+			_net.startNode.h = parseInt(_net.calcShortestDistance(_net.startNode, _net.endNode))
+			@trigger 'nodeChanged', _net.startNode.position
+
+			listOpen.push _net.startNode
+
+		# run the engine by one step
+		@step = =>
+			if !@finished
 				_step()
-		return
+			else
+				console.log 'It has been finished.'

-	autoStep = ->
-		if !finished
-			_step()
-			setTimeout autoStep, 1000 / FPS
-		return
+		# run the engine all the way to finish
+		@run = (fps) =>
+			@fps = fps if fps?
+			if not @isRunning
+				@isRunning = true
+				if fps
+					autoStep()
+				else
+					while not @finished
+						_step()

-	_step = ->
-		return if self.noSolution # TODO remove this line
+		autoStep = =>
+			if not @finished
+				_step()
+				setTimeout autoStep, 1000 / @fps

-		init() if listOpen.length == 0
+		_step = =>
+			return if @noSolution # TODO remove this line

-		currentNode = listOpen.shift()
+			init() if listOpen.length == 0

-		if currentNode == undefined
-			self.noSolution = true
-			finished = true
-			self.trigger 'finish', self.result
-			self.trigger 'noSolution', self.result
-			return
+			currentNode = listOpen.shift()

-		if currentNode.state != AStar.NODE_STATE_START
-			currentNode.state = AStar.NODE_STATE_DETECTED
-			self.trigger 'nodeChanged', currentNode.position
+			if not currentNode?
+				@noSolution = true
+				@finished = true
+				@isRunning = false
+				@trigger 'finish', @result
+				@trigger 'noSolution', @result
+				return

-		listClose.push currentNode
+			if currentNode.state != AStar.NODE_STATE_START
+				currentNode.state = AStar.NODE_STATE_DETECTED
+				@trigger 'nodeChanged', currentNode.position

-		recalculateLastNode currentNode
+			listClose.push currentNode

-		neighbours = _net.getNeighboursOf(currentNode)
+			recalculateLastNode currentNode

-		for own i of neighbours
-			neighbour = neighbours[i]
+			neighbours = _net.getNeighboursOf(currentNode)

-			if listClose.indexOf(neighbour) > -1
-			else if listOpen.indexOf(neighbour) > -1
-			else
-				neighbour.prevNode = currentNode
-				neighbour.id = nodeId++
+			for own i of neighbours
+				neighbour = neighbours[i]

-				if neighbour.state != AStar.NODE_STATE_END
-					neighbour.state = AStar.NODE_STATE_REACHABLE
+				if listClose.indexOf(neighbour) > -1
+				else if listOpen.indexOf(neighbour) > -1
+				else
+					neighbour.prevNode = currentNode
+					neighbour.id = nodeId++

-				neighbour.g = currentNode.g + neighbour.calcShortestDistanceTo(currentNode)
-				neighbour.h = neighbour.calcShortestDistanceTo(_net.endNode)
-				self.trigger 'nodeChanged', neighbour.position
+					if neighbour.state != AStar.NODE_STATE_END
+						neighbour.state = AStar.NODE_STATE_REACHABLE

-				recalculateLastNode neighbour
-				listOpen.push neighbour
+					neighbour.g = currentNode.g + neighbour.calcShortestDistanceTo(currentNode)
+					neighbour.h = neighbour.calcShortestDistanceTo(_net.endNode)
+					@trigger 'nodeChanged', neighbour.position

-		listOpen.sort compareNodes
+					recalculateLastNode neighbour
+					listOpen.push neighbour

-		# detect end
-		if listOpen[0] == _net.endNode
-			_net.endNode.state = AStar.NODE_STATE_END
-			self.trigger 'nodeChanged', _net.endNode.position
+			listOpen.sort compareNodes

-			traceShortestPath()
+			# detect end
+			if listOpen[0] == _net.endNode
+				_net.endNode.state = AStar.NODE_STATE_END
+				@trigger 'nodeChanged', _net.endNode.position

-			finished = true
+				traceShortestPath()

-			self.trigger 'finish', self.result
-			self.trigger 'reachEnd', stepCount
-		stepCount++
-		return
+				@finished = true

-	compareNodes = (node1, node2) ->
-		delta = node1.f() - node2.f()
-		# without this step it will shows some random which seems stupid
-		delta = node2.id - (node1.id) if delta == 0
-		return delta
+				@trigger 'finish', @result
+				@trigger 'reachEnd', stepCount
+			stepCount++

-	recalculateLastNode = (node) ->
-		neighbours = _net.getNeighboursOf(node)
+		compareNodes = (node1, node2) ->
+			delta = node1.f() - node2.f()
+			# without this step it will shows some random which seems stupid
+			delta = node2.id - (node1.id) if delta == 0
+			return delta

-		# detect from its neighbours to see whether there is a nearer node to be its parent node
-		for own i of neighbours
-			neighbour = neighbours[i]
-			# reached nodes: start node, being reaching, has reached
-			if neighbour.state > AStar.NODE_STATE_DEFAULT and neighbour.state < AStar.NODE_STATE_OBSTACLE and neighbour.state != AStar.NODE_STATE_END
+		recalculateLastNode = (node) =>
+			neighbours = _net.getNeighboursOf(node)

-				g0 = node.g
-				g1 = neighbour.g + node.calcShortestDistanceTo(neighbour)
-				if g0 > g1
-					node.prevNode = neighbour
-					node.g = g1
-					self.trigger 'nodeChanged', node.position
-		return
+			# detect from its neighbours to see whether there is a nearer node to be its parent node
+			for own i of neighbours
+				neighbour = neighbours[i]
+				# reached nodes: start node, being reaching, has reached
+				if neighbour.state > AStar.NODE_STATE_DEFAULT and neighbour.state < AStar.NODE_STATE_OBSTACLE and neighbour.state != AStar.NODE_STATE_END

-	# trace the shortest path and change the state from DETECTED to SHORTEST
-	traceShortestPath = ->
-		middleNode = _net.endNode.prevNode
-		count = 0
+					g0 = node.g
+					g1 = neighbour.g + node.calcShortestDistanceTo(neighbour)
+					if g0 > g1
+						node.prevNode = neighbour
+						node.g = g1
+						@trigger 'nodeChanged', node.position

-		while middleNode != _net.startNode and count < 100
-			middleNode.state = AStar.NODE_STATE_SHORTEST
+		# trace the shortest path and change the state from DETECTED to SHORTEST
+		traceShortestPath = =>
+			middleNode = _net.endNode.prevNode

-			self.trigger 'nodeChanged', middleNode.position
-			middleNode = middleNode.prevNode
+			while middleNode != _net.startNode
+				middleNode.state = AStar.NODE_STATE_SHORTEST

-			count++
-		return
-
-	return
+				@trigger 'nodeChanged', middleNode.position
+				middleNode = middleNode.prevNode


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -52,7 +52,8 @@
         I.currentState.cameras()
     fadeIn: (options={}) ->
       self.cameras.invoke('fadeIn', options)
-    fadeOut: (options={}) -
+    fadeOut: (options={}) ->
+      self.cameras.invoke('fadeOut', options)
     flash: (options={}) ->
       self.camera(options.camera).flash(options)
     objects: ->


engine_save_state

--- src/engine_save_state.coffee
+++ src/engine_save_state.coffee
@@ -61,6 +61,17 @@
   ###*
   Reloads the current engine state, useful for hotswapping code.

+

+  engine.I.objects.each (object) ->
+    # bring all objects to (0, 0) for some reason
+    object.I.x = 0
+    object.I.y = 0
+
+  # reload all objects to make sure
+  # they are at (0, 0)
+  engine.reload()
+

+
   @name reload
   @methodOf Engine.SaveState#
   ###


Client: Activity: Feed: Added updateComment method

--- client/activity/lib/components/comments/commentlistitem.coffee
+++ client/activity/lib/components/comments/commentlistitem.coffee
@@ -27,6 +27,14 @@

     onMentionClick comment

+
+  updateComment: ->
+
+    commentId = @props.comment.get '_id'
+    { message } = ActivityFlux.actions
+
+    message.unsetMessageEditMode commentId, @props.channelId
+    message.editMessage commentId, @state.commentValue.trim()

   render: ->


Admin: Stacks: Fix aws provider template.

--- client/admin/lib/views/stacks/onboarding/onboardingview.coffee
+++ client/admin/lib/views/stacks/onboarding/onboardingview.coffee
@@ -118,8 +118,9 @@
       owngitserver      : 'git clone git@yourgitserver.com/reponame.git'
     providerTemplates   =
       aws               :
-        'access_key'    : '${var.aws_access_key}'
-        'secret_key'    : '${var.aws_secret_key}'
+        aws             :
+          access_key    : '${var.aws_access_key}'
+          secret_key    : '${var.aws_secret_key}'

     serverConfigPanes.forEach (pane, index) ->


Updated to call goto initialise

Updated to call goto initialise

--- lib/config.coffee
+++ lib/config.coffee
@@ -44,7 +44,7 @@
         namespace.createNamespace(atom.workspace.getActivePaneItem())

     atom.commands.add 'atom-workspace', 'atom-autocomplete-php:goto': =>
-        goto.goto(atom.workspace.getActivePaneItem())
+        goto.gotoFromEditor(atom.workspace.getActivePaneItem())

     @writeConfig()

@@ -56,3 +56,5 @@

     atom.config.onDidChange 'atom-autocomplete-php.autoloadPaths', () =>
       @writeConfig()
+
+    goto.init()


don't provide on non-scala files

don't provide on non-scala files

--- lib/ensime.coffee
+++ lib/ensime.coffee
@@ -371,19 +371,23 @@
     {
       providerName: 'ensime-atom'
       getSuggestionForWord: (textEditor, text, range) =>
-        client = @clientOfEditor(textEditor)
-        console.log("client " + client)
-        {
-          range: range
-          callback: () ->
-            if(client)
-              client.goToTypeAtPoint(textEditor.getBuffer(), range.start)
-            else
-              atom.notifications.addError("Ensime not started! :(", {
-                dismissable: true
-                detail: "There is no running ensime instance for this particular file. Please start ensime first!"
-                })
-        }
+        if isScalaSource(textEditor)
+          client = @clientOfEditor(textEditor)
+          console.log("client " + client)
+          {
+            range: range
+            callback: () ->
+              if(client)
+                client.goToTypeAtPoint(textEditor.getBuffer(), range.start)
+              else
+                atom.notifications.addError("Ensime not started! :(", {
+                  dismissable: true
+                  detail: "There is no running ensime instance for this particular file. Please start ensime first!"
+                  })
+          }
+        else
+          undefined
+
     }


Fixed filename error on dev tags.

Fixed filename error on dev tags.

--- lib/kelvin.coffee
+++ lib/kelvin.coffee
@@ -59,8 +59,8 @@
       for file in files
         source = fs.readFileSync(file, 'utf8')
         hash = Kelvin.hashContents source
-        filename = Kelvin.formatFilename file, hash, type
-        output += Kelvin.formatTag(filename, type) + '\n'
+        f = Kelvin.formatFilename file.replace(@contentsDir, ''), hash, type
+        output += Kelvin.formatTag(f, type) + '\n'
     output

   transform: (files, type) ->


reset_abilities is now sent on every connect event

--- lib/worker.coffee
+++ lib/worker.coffee
@@ -122,7 +122,6 @@
   constructor: (@name, @fn, @options) ->
     @work_in_progress = false
     @active = true
-    @first_connect = true
     @options = _.defaults (@options or {}),
       host: 'localhost'
       port: 4730
@@ -145,13 +144,11 @@
     )

   _register_worker: =>
-    if not @first_connect
-      @sendCommand 'RESET_ABILITIES'
+    @sendCommand 'RESET_ABILITIES'
     if @options.timeout?
       @sendCommand 'CAN_DO_TIMEOUT', @name, @options.timeout
     else
       @sendCommand 'CAN_DO', @name
-    @first_connect = false
     @_get_next_job()

   _get_next_job: =>


Karma: Remove not needed comments at the karma.conf.coffee

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -1,45 +1,21 @@
-# Karma configuration
-
 module.exports = (config) ->
   config.set
-    # base path, that will be used to resolve files and exclude
     basePath: ''

-    # list of files / patterns to load in the browser
     files: [
-      # source
       'dist/specs/specs.js'
     ]

-    # list of files to exclude
-    exclude: [
-
-    ]
-
-    # web server port
     port: 9876

-    # enable / disable colors in the output (reporters and logs)
     colors: yes

-    # level of logging
-    # possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
     logLevel: config.LOG_INFO

-    # enable / disable watching file and executing tests whenever any file changes
     autoWatch: no

-    # Start these browsers, currently available:
-    # - Chrome
-    # - ChromeCanary
-    # - Firefox
-    # - Opera
-    # - Safari
-    # - PhantomJS
     browsers: ['PhantomJS']

-    # Continuous Integration mode
-    # if true, it capture browsers, run tests and exit
     singleRun: yes

     reporters: ['spec']
@@ -50,4 +26,4 @@
       'karma-mocha'
       'karma-phantomjs-launcher'
       'karma-spec-reporter'
-    ]+    ]


Validations sorted by date DESC

Validations sorted by date DESC

--- app/assets/javascripts/backbone/views/validations/index_view.js.coffee
+++ app/assets/javascripts/backbone/views/validations/index_view.js.coffee
@@ -22,7 +22,8 @@
       sPaginationType: "bootstrap"
       oLanguage:
         sLengthMenu: "_MENU_ records per page"
-      "aoColumns": [
+      aaSorting: [[0, 'desc']]
+      aoColumns: [
         null,
         null,
         null,


Remove require tree to eliminate unneeded scripts

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -12,4 +12,3 @@
 #
 #= require jquery
 #= require jquery_ujs
-#= require_tree .


Fixed stacked bar chart tooltips

Fixed stacked bar chart tooltips

Closes #1133

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -79,12 +79,15 @@
         .attr('class', 'serie')
         .attr("width", @x.rangeBand() * 0.5)
         .attr('x', (s) => @x(s.x) + 10)
+        .attr('data-tooltip-title', (s) => s.label)
         .attr('y', @series_height)
         .style('fill', (d) => d.color)
         .style('opacity', 0.8)

       $('rect.serie').qtip
-        content: -> $(this).attr('data-tooltip')
+        content:
+          title: -> $(this).attr('data-tooltip-title')
+          text: -> $(this).attr('data-tooltip-text')
         position:
           my: 'bottom center'
           at: 'top center'
@@ -142,10 +145,8 @@
         .transition()
         .attr('y', (d) => @series_height - @y(d.y0 + d.y))
         .attr('height', (d) => @y(d.y))
-        .attr("data-tooltip", (d) =>
-          html = d.label
-          html += "<br/>"
-          html += Metric.autoscale_value d.y, @model.get('unit')
+        .attr("data-tooltip-text", (d) =>
+          Metric.autoscale_value d.y, @model.get('unit')
         )

       # move the target lines


remove debug log output

remove debug log output

--- app/assets/javascripts/edit_member.js.coffee
+++ app/assets/javascripts/edit_member.js.coffee
@@ -2,7 +2,6 @@
   constructor: (@member_id, @member_update_url, @auth_token) ->

   init: ->
-    console.log("inside: Member ID: #{@member_id} member_upadte_url: #{@member_update_url} auth_token: #{@auth_token}")
     for field in ['first_name', 'last_name']
       $("##{field}").editable
         type: "text"


refactored the gzip decompression

refactored the gzip decompression

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -174,13 +174,13 @@
 		response.status = routeRes.statusCode
 		response.headers = routeRes.headers

-		uncompressedBody = ''
+		uncompressedBodyBufs = []
 		if routeRes.headers['content-encoding'] == 'gzip' #attempt to gunzip
 			routeRes.pipe(gunzip);

-		gunzip.on "data", (data) ->
-			uncompressedBody += data
-			return
+			gunzip.on "data", (data) ->
+				uncompressedBodyBufs.push data
+				return

 		bufs = []
 		routeRes.on "data", (chunk) ->
@@ -192,7 +192,8 @@
 			charset = obtainCharset(routeRes.headers)
 			if routeRes.headers['content-encoding'] == 'gzip'
 				gunzip.on "end", ->
-					response.body = uncompressedBody
+					uncompressedBody =	Buffer.concat uncompressedBodyBufs
+					response.body = uncompressedBody.toString charset
 					if not defered.promise.isRejected()
 						defered.resolve response
 					return


Issue #225: Check for content-type substring match

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -89,7 +89,7 @@
 		if route.primary
 			promise = sendRequest(ctx, route, options)
 			.then (response) ->
-				if response.headers and response.headers['content-type'] is 'application/json+openhim'
+				if response.headers?['content-type']?.indexOf('application/json+openhim') > -1
 					# handle mediator reponse
 					responseObj = JSON.parse response.body
 					ctx.mediatorResponse = responseObj
@@ -111,7 +111,7 @@
 					querystring: ctx.request.querystring
 					method: ctx.request.method

-				if response.headers and response.headers['content-type'] is 'application/json+openhim'
+				if response.headers?['content-type']?.indexOf('application/json+openhim') > -1
 					# handle mediator reponse
 					responseObj = JSON.parse response.body
 					routeObj.orchestrations = responseObj.orchestrations


don't add tooltips to select2 boxes

don't add tooltips to select2 boxes

--- app/assets/javascripts/registrants.js.coffee
+++ app/assets/javascripts/registrants.js.coffee
@@ -1,6 +1,7 @@
 $ ->
-  $(document).tooltip position:
-    at: "left bottom-10"
+  $("[title]").filter(":not(.select2-selection__rendered)").tooltip
+    position:
+      at: "left bottom-10"

 $ ->
   $("#show-option").tooltip show:


Adding more snippets

Adding more snippets

--- snippets/save.cson
+++ snippets/save.cson
@@ -1,77 +1,77 @@
 '.source.feature':

   'save101':
-    'prefix': 'save the text content the element found by "identifier" to the alias "alias"'
-    'body': 'save the text content the element found by "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element found by "identifier" to the alias "alias"'
+    'body': 'save the text content of the element found by "$1" to the alias "$2"'
   'save102':
-    'prefix': 'save the text content the element found by alias "alias identifier" to the alias "alias"'
-    'body': 'save the text content the element found by alias "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element found by alias "alias identifier" to the alias "alias"'
+    'body': 'save the text content of the element found by alias "$1" to the alias "$2"'
   'save103':
-    'prefix': 'save the text content the element found by "identifier" to the alias "$2" if it exists'
-    'body': 'save the text content the element found by "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element found by "identifier" to the alias "$2" if it exists'
+    'body': 'save the text content of the element found by "$1" to the alias "$2" if it exists'
   'save104':
-    'prefix': 'save the text content the element found by alias "alias identifier" to the alias "alias" if it exists'
-    'body': 'save the text content the element found by alias "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element found by alias "alias identifier" to the alias "alias" if it exists'
+    'body': 'save the text content of the element found by alias "$1" to the alias "$2" if it exists'
   'save110':
-    'prefix': 'save the text content the element with the ID of "identifier" to the alias "alias"'
-    'body': 'save the text content the element with the ID of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the ID of "identifier" to the alias "alias"'
+    'body': 'save the text content of the element with the ID of "$1" to the alias "$2"'
   'save111':
-    'prefix': 'save the text content the element with the class of "identifier" to the alias "alias"'
-    'body': 'save the text content the element with the class of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the class of "identifier" to the alias "alias"'
+    'body': 'save the text content of the element with the class of "$1" to the alias "$2"'
   'save112':
-    'prefix': 'save the text content the element with the xpath of "identifier" to the alias "alias"'
-    'body': 'save the text content the element with the xpath of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the xpath of "identifier" to the alias "alias"'
+    'body': 'save the text content of the element with the xpath of "$1" to the alias "$2"'
   'save113':
-    'prefix': 'save the text content the element with the name of "identifier" to the alias "alias"'
-    'body': 'save the text content the element with the name of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the name of "identifier" to the alias "alias"'
+    'body': 'save the text content of the element with the name of "$1" to the alias "$2"'
   'save114':
-    'prefix': 'save the text content the element with the css selector of "identifier" to the alias "alias"'
-    'body': 'save the text content the element with the css selector of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the css selector of "identifier" to the alias "alias"'
+    'body': 'save the text content of the element with the css selector of "$1" to the alias "$2"'
   'save120':
-    'prefix': 'save the text content the element with the ID of "identifier" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the ID of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the ID of "identifier" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the ID of "$1" to the alias "$2" if it exists'
   'save121':
-    'prefix': 'save the text content the element with the class of "identifier" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the class of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the class of "identifier" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the class of "$1" to the alias "$2" if it exists'
   'save122':
-    'prefix': 'save the text content the element with the xpath of "identifier" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the xpath of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the xpath of "identifier" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the xpath of "$1" to the alias "$2" if it exists'
   'save123':
-    'prefix': 'save the text content the element with the name of "identifier" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the name of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the name of "identifier" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the name of "$1" to the alias "$2" if it exists'
   'save124':
-    'prefix': 'save the text content the element with the css selector of "identifier" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the css selector of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the css selector of "identifier" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the css selector of "$1" to the alias "$2" if it exists'
   'save130':
-    'prefix': 'save the text content the element with the ID alias of "identifier alias" to the alias "alias"'
-    'body': 'save the text content the element with the ID alias of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the ID alias of "identifier alias" to the alias "alias"'
+    'body': 'save the text content of the element with the ID alias of "$1" to the alias "$2"'
   'save131':
-    'prefix': 'save the text content the element with the class alias of "identifier alias" to the alias "alias"'
-    'body': 'save the text content the element with the class alias of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the class alias of "identifier alias" to the alias "alias"'
+    'body': 'save the text content of the element with the class alias of "$1" to the alias "$2"'
   'save132':
-    'prefix': 'save the text content the element with the xpath alias of "identifier alias" to the alias "alias"'
-    'body': 'save the text content the element with the xpath alias of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the xpath alias of "identifier alias" to the alias "alias"'
+    'body': 'save the text content of the element with the xpath alias of "$1" to the alias "$2"'
   'save133':
-    'prefix': 'save the text content the element with the name alias of "identifier alias" to the alias "alias"'
-    'body': 'save the text content the element with the name alias of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the name alias of "identifier alias" to the alias "alias"'
+    'body': 'save the text content of the element with the name alias of "$1" to the alias "$2"'
   'save134':
-    'prefix': 'save the text content the element with the css selector alias of "identifier alias" to the alias "alias"'
-    'body': 'save the text content the element with the css selector alias of "$1" to the alias "$2"'
+    'prefix': 'save the text content of the element with the css selector alias of "identifier alias" to the alias "alias"'
+    'body': 'save the text content of the element with the css selector alias of "$1" to the alias "$2"'
   'save140':
-    'prefix': 'save the text content the element with the ID alias of "identifier alias" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the ID alias of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the ID alias of "identifier alias" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the ID alias of "$1" to the alias "$2" if it exists'
   'save141':
-    'prefix': 'save the text content the element with the class alias of "identifier alias" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the class alias of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the class alias of "identifier alias" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the class alias of "$1" to the alias "$2" if it exists'
   'save142':
-    'prefix': 'save the text content the element with the xpath alias of "identifier alias" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the xpath alias of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the xpath alias of "identifier alias" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the xpath alias of "$1" to the alias "$2" if it exists'
   'save143':
-    'prefix': 'save the text content the element with the name alias of "identifier alias" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the name alias of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the name alias of "identifier alias" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the name alias of "$1" to the alias "$2" if it exists'
   'save144':
-    'prefix': 'save the text content the element with the css selector alias of "identifier alias" to the alias "alias" if it exists'
-    'body': 'save the text content the element with the css selector alias of "$1" to the alias "$2" if it exists'
+    'prefix': 'save the text content of the element with the css selector alias of "identifier alias" to the alias "alias" if it exists'
+    'body': 'save the text content of the element with the css selector alias of "$1" to the alias "$2" if it exists'

   'save201':
     'prefix': 'save the text content the hidden element found by "identifier" to the alias "alias"'


Try fix audit tests

Try fix audit tests

--- test/integration/auditingIntegrationTests.coffee
+++ test/integration/auditingIntegrationTests.coffee
@@ -11,7 +11,9 @@
 describe "Auditing Integration Tests", ->

-  beforeEach (done) -> Audit.remove {}, -> server.start auditUDPPort: 5050, auditTlsPort: 5051, auditTcpPort: 5052, -> done()
+  beforeEach (done) ->
+    Audit.remove ->
+      server.start auditUDPPort: 5050, auditTlsPort: 5051, auditTcpPort: 5052, done

   afterEach (done) -> server.stop -> done()


Removed empty comment

Removed empty comment

--- src/pex/geom/Geometry.coffee
+++ src/pex/geom/Geometry.coffee
@@ -95,4 +95,4 @@
           @addEdge(face.a, face.b)
           @addEdge(face.b, face.c)
           @addEdge(face.c, face.d)
-          @addEdge(face.d, face.a)#+          @addEdge(face.d, face.a)


HIRE-16-frontend : override new profile to originprofile after successfully saving

--- src/main/java/app/profile/profile.coffee
+++ src/main/java/app/profile/profile.coffee
@@ -40,6 +40,7 @@
       $scope.showme = false
       $document.duScrollTopAnimated(0)
       $('.form-group').removeClass('has-warning')
+      $scope.originProfile = angular.copy($scope.profile)
       $scope.success = true
       $timeout (->
         $scope.success = false


Add serialization version to root view

Add serialization version to root view

--- src/app/root-view.coffee
+++ src/app/root-view.coffee
@@ -16,6 +16,8 @@
 module.exports =
 class RootView extends View
   registerDeserializers(this, Pane, PaneRow, PaneColumn, Editor)
+
+  @version: 1

   @configDefaults:
     ignoredNames: [".git", ".svn", ".DS_Store"]
@@ -68,6 +70,7 @@
       @panes.reopenItem()

   serialize: ->
+    version: @constructor.version
     deserializer: 'RootView'
     panes: @panes.serialize()
     packages: atom.serializeAtomPackages()


Fix arguments number

Fix arguments number

--- lib/Bumped.config.coffee
+++ lib/Bumped.config.coffee
@@ -14,7 +14,7 @@

   constructor: (bumped) ->
     @bumped = bumped
-    @rc = require('rc') bumped.pkg.name, DEFAULT.scaffold(), CSON.parse
+    @rc = require('rc') bumped.pkg.name, DEFAULT.scaffold(), null, CSON.parse

   ###*
    * Special '.add' action that try to autodetect common configuration files


Fix little style issues

Fix little style issues

--- lib/Bumped.config.coffee
+++ lib/Bumped.config.coffee
@@ -47,7 +47,7 @@

     if @hasFile opts.file
       message = MSG.NOT_ALREADY_ADD_FILE opts.file
-      @bumped.logger.errorHandler message
+      @bumped.logger.errorHandler message, lineBreak:false
       return cb message, @rc.files

     opts
@@ -57,7 +57,7 @@
       (exists, next) =>
         return @addFile opts, next if exists
         message = MSG.NOT_ADD_FILE opts.file
-        @bumped.logger.errorHandler message
+        @bumped.logger.errorHandler message, lineBreak:false
         return cb message, @rc.files
       (next) => unless opts.save then next() else @save opts, next
     ]
@@ -70,7 +70,7 @@

     unless @hasFile opts.file
       message = MSG.NOT_REMOVE_FILE opts.file
-      @bumped.logger.errorHandler message
+      @bumped.logger.errorHandler message, lineBreak:false
       return cb message, @rc.files

     tasks = [
@@ -119,7 +119,7 @@
   set: =>
     [opts, cb] = DEFAULT.args arguments

-    setProperty = (file, done) =>
+    setProperty = (file, done) ->
       util.updateJSON
         filename : file
         property : opts.property
@@ -127,11 +127,13 @@
         force    : true
       , done

-    return @bumped.logger.errorHandlerHandler MSG.NOT_SET_PROPERTY(), cb unless opts.property or opts.value
-    return @bumped.logger.errorHandlerHandler MSG.NOT_SET_VERSION(), cb if opts.property is 'version'
+    message = null
+    message = MSG.NOT_SET_PROPERTY() unless opts.property or opts.value
+    message = MSG.NOT_SET_VERSION() if opts.property is 'version'
+    return @bumped.logger.errorHandler message, lineBreak:false, cb if message

     async.each @bumped.config.rc.files, setProperty, (err) =>
-      return @bumped.logger.errorHandlerHandler err, cb if err
+      return @bumped.logger.errorHandler err, cb if err
       @bumped.logger.success MSG.SET_PROPERTY opts.property, opts.value
       cb null, opts


Improve plugin settings

Improve plugin settings

--- lib/Bumped.plugin.coffee
+++ lib/Bumped.plugin.coffee
@@ -39,17 +39,24 @@
     npmInstallGlobal plugin
     path.resolve globalNpmPath, plugin

+  buildOptions: (opts) ->
+    obj = clone opts
+    obj.logger = @bumped.logger
+    obj.path = @pluginPath opts.plugin
+    obj
+
   exec: (opts, cb) ->
     pluginType = @[opts.type]
     return cb null if isEmpty Object.keys pluginType

     async.forEachOfSeries pluginType, (settings, description, next) =>
-      pluginPath = @pluginPath settings.plugin
-      @notifyPlugin pluginPath
-      plugin = @cache[settings.plugin] ?= require pluginPath
+      pluginOptions = @buildOptions settings

-      pluginObjt = clone settings
-      pluginObjt.logger = @bumped.logger
+      if @cache[settings.plugin]
+        plugin = @cache[settings.plugin]
+      else
+        plugin = @cache[settings.plugin] = require pluginOptions.path
+        @notifyPlugin pluginOptions.path

       animation = new Animation
         text   : description
@@ -58,7 +65,7 @@
         type   : opts.type

       animation.start =>
-        plugin @bumped, pluginObjt, (err) ->
+        plugin @bumped, pluginOptions, (err) ->
           animation.stop err, next
     , cb


clear when finished

clear when finished

--- src/buffer-stream.coffee
+++ src/buffer-stream.coffee
@@ -102,6 +102,8 @@
             if @enabled
                 # we buffer everything
                 split.call(this)
+                if @finished # currently finishing
+                    return @clear()
                 return true # it's safe to immediately write again
             else
                 return @clear()


Code review: move jsPlumb connection lists to object properties. Also, adjust transfer-modifier locations so they're less likely to cross flow

--- src/code/utils/js-plumb-diagram-toolkit.coffee
+++ src/code/utils/js-plumb-diagram-toolkit.coffee
@@ -17,6 +17,19 @@
       ConnectionsDetachable: true,
       DoNotThrowErrors: false
     @registerListeners()
+
+    # transfer-modifier links attach to fixed locations on the left or right of the flow node
+    @flowNodeModifierAnchors = [[0, 0.4, -1, 0, 8, 0], [1, 0.7, 1, 0, -8, 0]]
+    # flow links attach to fixed locations on the left or right of the flow node
+    @flowNodeFlowAnchors = [[0, 0.6, -1, 0, 16, 0], [1, 0.6, 1, 0, -16, 0]]
+    # other links attach to three fixed locations on the top or bottom of the flow node or
+    # two fixed locations on the left or right of the flow node chosen to not overlap the others
+    @flowNodeLinkAnchors = [[0.3, 0, 0, -1, 0, 12], [0.5, 0, 0, -1, 0, 12], [0.7, 0, 0, -1, 0, 12],
+                            [0, 0.25, -1, 0, 8, 0], [0, 0.75, -1, 0, 8, 0],
+                            [0.3, 1, 0, 1, 0, -10], [0.5, 1, 0, 1, 0, -10], [0.7, 1, 0, 1, 0, -10],
+                            [1, 0.25, 1, 0, -8, 0], [1, 0.4, 1, 0, -8, 0], [1, 0.8, 1, 0, -8, 0]]
+    # links to non-flow nodes link to locations assigned by jsPlumb on the left, top, or right faces
+    @standardAnchors = ["Continuous", { faces:["top","left","right"] }]

   registerListeners: ->
     @kit.bind 'connection', @handleConnect.bind @
@@ -239,28 +252,16 @@
     linkTarget = opts.linkModel?.targetNode
     linkRelation = opts.linkModel?.relation

-    # transfer-modifier links attach to fixed locations on the left or right of the flow node
-    flowNodeModifierAnchors = [[0, 0.4, -1, 0, 8, 0], [1, 0.4, 1, 0, -8, 0]]
-    # flow links attach to fixed locations on the left or right of the flow node
-    flowNodeFlowAnchors = [[0, 0.6, -1, 0, 16, 0], [1, 0.6, 1, 0, -16, 0]]
-    # other links attach to three fixed locations on the top or bottom of the flow node or
-    # two fixed locations on the left or right of the flow node chosen to not overlap the others
-    flowNodeLinkAnchors = [[0.3, 0, 0, -1, 0, 12], [0.5, 0, 0, -1, 0, 12], [0.7, 0, 0, -1, 0, 12],
-                           [0, 0.25, -1, 0, 8, 0], [0, 0.75, -1, 0, 8, 0],
-                           [0.3, 1, 0, 1, 0, -10], [0.5, 1, 0, 1, 0, -10], [0.7, 1, 0, 1, 0, -10],
-                           [1, 0.25, 1, 0, -8, 0], [1, 0.75, 1, 0, -8, 0]]
-    # links to non-flow nodes link to locations assigned by jsPlumb on the left, top, or right faces
-    standardAnchors = ["Continuous", { faces:["top","left","right"] }]
     isLinkToFlowNode = linkTarget?.isTransfer
     isModifierToFlowNode = linkRelation?.isTransferModifier or
                             # transfer-modifier link isn't identified as such until it's defined
                             linkTarget?.isTransfer and linkTarget.transferLink?.sourceNode is linkSource
     isTransferToFlowNode = opts.isTransfer and opts.fromSource
     isTransferFromFlowNode = opts.isTransfer and not opts.fromSource
-    sourceAnchors = if isTransferFromFlowNode then flowNodeFlowAnchors else standardAnchors
-    targetAnchors = if isTransferToFlowNode then flowNodeFlowAnchors else \
-                      if isModifierToFlowNode then flowNodeModifierAnchors else \
-                        if isLinkToFlowNode then flowNodeLinkAnchors else standardAnchors
+    sourceAnchors = if isTransferFromFlowNode then @flowNodeFlowAnchors else @standardAnchors
+    targetAnchors = if isTransferToFlowNode then @flowNodeFlowAnchors else \
+                      if isModifierToFlowNode then @flowNodeModifierAnchors else \
+                        if isLinkToFlowNode then @flowNodeLinkAnchors else @standardAnchors

     connection = @kit.connect
       source: opts.source


Show correct number of fetched products

Show correct number of fetched products

--- src/coffee/export.coffee
+++ src/coffee/export.coffee
@@ -69,7 +69,7 @@
           if products.body.total is 0
             Promise.resolve 'No products found.'
           else
-            console.log "Number of products: #{products.body.total}."
+            console.log "Number of fetched products: #{products.body.count}."
             @typesService.buildMaps productTypes.body.results
             @categoryService.buildMaps categories.body.results
             @channelService.buildMaps channels.body.results


init: fix vagrant path

init: fix vagrant path

--- lib/commands/init.coffee
+++ lib/commands/init.coffee
@@ -53,7 +53,7 @@
     destination: "#{params.path}/bin/vagrant"
     content: """
     #!/bin/bash
-    cd $( dirname "${BASH_SOURCE}" )/../resources
+    cd $( dirname "${BASH_SOURCE}" )/../conf
     vagrant $@
     """
     mode: 0o0755


[logger] add logs path definition in configuration

--- src/common/config.coffee
+++ src/common/config.coffee
@@ -66,6 +66,7 @@
         @Paths.Webroot          = Path.join @Paths.Root, 'www'
         @Paths.Libroot          = Path.join @Paths.Root, 'lib'
         @Paths.Views            = Path.join @Paths.Webroot, 'views'
+        @Paths.Logs             = Path.join @Paths.Root, 'logs'

         do @loadJSONConfig
         @Voicious.Title = 'voıċıoųs'


cleanup the way auth token is captured

cleanup the way auth token is captured

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -299,8 +299,7 @@
 Clever.me = (token, url_base..., cb) ->
   url_base = url_base[0]
   url_base ?= API_BASE
-  auth = {token: token} if _.isString(token)
-  auth ?= {token: token.access_token or token.token}
+  auth = {token: token?.access_token or token?.token or token}
   opts =
     method: 'get'
     ca: certs
@@ -338,8 +337,7 @@
     make_request opts, cb

   @tokenInfo: (token, cb) ->
-    auth = {token: token} if _.isString(token)
-    auth ?= {token: token.access_token or token.token}
+    auth = {token: token?.access_token or token?.token or token}
     opts =
       json: true
       ca: certs


fix for counts of 0

fix for counts of 0

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -143,7 +143,7 @@
             Klass = @_uri_to_class(doc.uri)
             new Klass doc.data, doc.uri, doc.links
           cb_post null, results
-        else if body.count
+        else if body.count?
           cb_post null, body.count
         else
           throw Error "Could not parse query response: #{body}, #{JSON.stringify q, undefined, 2}"


:bug: Correct the order in EditorLinter::destroy We can't emit when we've disposed it

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -34,8 +34,8 @@
     return @emitter.on('did-destroy', callback)

   destroy: ->
+    @emitter.emit('did-destroy')
     @deactivate()
-    @emitter.emit('did-destroy')

   deactivate: ->
     @emitter.dispose()


Tests: Add some tests for Choice.get/set

--- spec/Choice.coffee
+++ spec/Choice.coffee
@@ -259,3 +259,39 @@
       header =
         type: 'h1'
       chai.expect(c.isSubtypeOf(header, 'textual')).to.equal true
+
+
+  describe 'get/set attributes', ->
+    describe 'getting attribute on instance', ->
+      it 'should return value', ->
+        p = new Choice 'hello'
+        p.set 'attr', 'bar'
+        chai.expect(p.get('attr')).to.eql 'bar'
+    describe 'getting attribute in child', ->
+      it 'should return value', ->
+        p = new Choice 'hello'
+        p.set 'attr', 'bar'
+        c = new Choice p, 'world'
+        chai.expect(c.get('attr')).to.eql 'bar'
+    describe 'getting attribute in grand-grand child', ->
+      it 'should return value', ->
+        p = new Choice 'hello'
+        p.set 'attr', 'bar'
+        c = new Choice p, 'child'
+        gc = new Choice c, 'grand-child'
+        ggc = new Choice gc, 'g-grand-child'
+        chai.expect(ggc.get('attr')).to.eql 'bar'
+
+    describe 'getting non-existant attribute', ->
+      it 'should return null', ->
+        p = new Choice 'hello'
+        p.set 'existant', 'bar'
+        chai.expect(p.get('non-existant')).to.be.a 'null'
+    describe 'getting non-existant attribute in grand-grand child', ->
+      it 'should return null', ->
+        p = new Choice 'hello'
+        c = new Choice p, 'child'
+        gc = new Choice c, 'grand-child'
+        ggc = new Choice gc, 'g-grand-child'
+        chai.expect(ggc.get('color2')).to.be.a 'null'
+


black text doesn't show up on the blue background

--- js/libs/terminal.coffee
+++ js/libs/terminal.coffee
@@ -22,7 +22,7 @@
           when 3 then @italic = true
           when 22 then @bold = false
           when 23 then @italic = false
-          when 30 then @color = "black"
+          when 30 then @color = "darkgrey"
           when 31 then @color = "red"
           when 32 then @color = "green"
           when 33 then @color = "yellow"


Tests for start() and stop()

Tests for start() and stop()

--- spec/simrou.coffee
+++ spec/simrou.coffee
@@ -249,4 +249,104 @@
             expect(router.resolve('foo')).toBeFalsy()

-# To do: start() / stop()
+    describe 'start()', ->
+        router = null
+        spies = (jasmine.createSpy('eventHandler' + i) for i in [1..2])
+        $form = $('<form action="foo" method="put"></form>')
+
+        beforeEach ->
+            router = new Simrou()
+            spy.reset() for spy in spies
+            $form.appendTo('body')
+
+            runs -> location.hash = ''
+            waits(250)  # wait for the event to be distributed (async process!)
+
+        afterEach ->
+            $(window).off('hashchange')
+            $('body').off('submit', 'form')
+            $form.remove()
+
+        it 'resolves location.hash if it is not empty', ->
+            router.addRoute('baz').get(spies[0])
+            router.addRoute('foo').any(spies[1])
+
+            runs ->
+                location.hash = 'baz'
+                router.start('foo')
+            waitsFor (-> spies[0].calls.length > 0), 'Route getting resolved', 250
+            runs -> expect(spies[1]).not.toHaveBeenCalled()
+
+        it 'navigates to an initial hash, if one is specified and location.hash is empty', ->
+            router.addRoute('bar').get(spies[0])
+            runs -> router.start('bar')
+            waitsFor (-> spies[0].calls.length > 0), 'Route getting resolved', 250
+            runs -> expect(location.hash).toBe('#bar')
+
+        it 'makes the router listen to hash changes by default', ->
+            router.addRoute('foo').get(spies[0])
+            runs ->
+                router.start()
+                location.hash = 'foo'
+            waitsFor (-> spies[0].calls.length > 0), 'Route getting resolved', 250
+
+        it 'can be told to not make the router listen to hash changes', ->
+            router.addRoute('baz').any(spies[0])
+            runs ->
+                router.start(null, false)
+                location.hash = 'baz'
+            waits(250)
+            runs -> expect(spies[0]).not.toHaveBeenCalled()
+
+        it 'makes the router listen to form submissions (no redirect, not update of location.hash)', ->
+            router.addRoute('foo').put(spies[0])
+            runs ->
+                router.start()
+                $form.submit()
+            waitsFor (-> spies[0].calls.length > 0), 'Route getting resolved', 250
+            runs -> expect(location.hash).not.toBe('#foo')
+
+        it 'can be told not to make the router listen to form submissions', ->
+            router.addRoute('foo').put(spies[0])
+            runs ->
+                router.start(null, false, false)
+                $form.on('submit', ((e) -> do e.preventDefault)).submit()
+            waits(250)
+            runs ->
+                expect(spies[0]).not.toHaveBeenCalled()
+
+
+    describe 'stop()', ->
+        router = new Simrou()
+        spy = jasmine.createSpy('eventHandler')
+        router.addRoute('bar').any(spy)
+
+        beforeEach ->
+            runs -> location.hash = ''
+            waits(250)
+            runs ->
+                router.start()
+                spy.reset()
+
+        afterEach ->
+            $(window).off('hashchange')
+            $('body').off('submit', 'form')
+
+        it 'makes the router stop listening to hash changes', ->
+            runs ->
+                router.stop()
+                location.hash = 'bar'
+            waits(250)
+            runs -> expect(spy).not.toHaveBeenCalled()
+
+        it 'makes the router stop listening to form submissions', ->
+            $form = $('<form action="bar" method="post"></form>').appendTo('body')
+            $form.on('submit', ((e) -> do e.preventDefault))
+
+            runs ->
+                router.stop()
+                $form.submit()
+            waits(250)
+            runs ->
+                expect(spy).not.toHaveBeenCalled()
+                $form.remove()


Implement Before and After methods. Implement some logs

--- konecty:methods-middleware.coffee
+++ konecty:methods-middleware.coffee
@@ -1,6 +1,48 @@
+utils = Npm.require 'util'
+
 Middlewares = {}
 BeforeMethods = {}
 AfterMethods = {}
+
+
+formatJsonWithPrefixAndColor = (value, prefix, color) ->
+	value = JSON.stringify value, null, '  '
+	value.replace /(.*\n)|(.+)/g, (value) ->
+		return (prefix + value)[color]
+
+
+removeInternalProperties = (value) ->
+	value = utils._extend {}, value
+	delete value.__methodName__
+	return value
+
+
+logBeforeExecution = (methodName, scope, args) ->
+	console.log "#{methodName} [will call]".cyan
+	if scope?
+		console.log '  > SCOPE'.cyan
+		console.log formatJsonWithPrefixAndColor removeInternalProperties(scope), '  | ', 'cyan'
+	if args?
+		console.log '  > ARGUMENTS'.cyan
+		console.log formatJsonWithPrefixAndColor args, '  | ', 'cyan'
+
+logAfterExecution = (methodName, scope, args, result) ->
+	console.log "#{methodName} [called]".magenta
+	if scope?
+		console.log '  > SCOPE'.magenta
+		console.log formatJsonWithPrefixAndColor removeInternalProperties(scope), '  | ', 'magenta'
+	if args?
+		console.log '  > ARGUMENTS'.magenta
+		console.log formatJsonWithPrefixAndColor args, '  | ', 'magenta'
+	if result?
+		console.log '  > RESULT'.magenta
+		console.log formatJsonWithPrefixAndColor args, '  | ', 'magenta'
+
+processResult = (result) ->
+	if _.isObject(result) and _.isFunction(result.fetch)
+		return result.fetch()
+
+	return result

 Meteor.registerAfterMethod = (name, method) ->
@@ -24,21 +66,53 @@
 	Middlewares[name] = method

-Meteor.registerMethod = (name, middlewares..., method) ->
-	m = {}
-	scope = {}
-	m[name] = ->
-		for middleware in middlewares
-			fn = Middlewares[middleware]
-			result = fn.apply scope, arguments
+Meteor.registerMethod = (name, middlewareNames..., mainMethod) ->
+	middlewares = {}
+
+	for middlewareName in middlewareNames
+		middleware = Middlewares[middlewareName]
+		if not middleware?
+			console.error "[konecty:methods-middleware] Middleware not registered: #{middlewareName}".red
+		else
+			middlewares[middlewareName] = middleware
+
+	meteorMethods = {}
+	meteorMethods[name] = ->
+		scope =
+			__methodName__: name
+
+		for beforeMethodName, beforeMethod of BeforeMethods
+			logBeforeExecution beforeMethodName, scope, arguments
+			result = beforeMethod.apply scope, arguments
+			logAfterExecution beforeMethodName, scope, arguments, result
 			if result?
-				if _.isObject(result) and _.isFunction(result.fetch)
-					return result.fetch()
-				return result
+				return processResult result

-		result = method.apply scope, arguments
-		if _.isObject(result) and _.isFunction(result.fetch)
-			return result.fetch()
-		return result
+		for middlewareName, middleware of middlewares
+			logBeforeExecution middlewareName, scope, arguments
+			result = middleware.apply scope, arguments
+			logAfterExecution middlewareName, scope, arguments, result
+			if result?
+				return processResult result

-	Meteor.methods m+		logBeforeExecution name, scope, arguments
+		result = mainMethod.apply scope, arguments
+		logAfterExecution name, scope, arguments, result
+
+		for afterMethodName, afterMethod of AfterMethods
+			afterMethodParams =
+				result: result
+				arguments: arguments
+
+			logBeforeExecution afterMethodName, scope, afterMethodParams
+			afterMethodResult = afterMethod.call scope, afterMethodParams
+			logAfterExecution afterMethodName, scope, afterMethodParams
+
+			result = afterMethodParams.result
+
+			if afterMethodResult?
+				return processResult result
+
+		return processResult result
+
+	Meteor.methods meteorMethods


add isInteractive toggle

add isInteractive toggle

--- src/components/demo.cjsx
+++ src/components/demo.cjsx
@@ -154,25 +154,17 @@
   getInitialState: ->
     isSelected: false
     toggles:
-      feedback: false
-      tags:     false
-      formats:  false
-      height:   false
+      feedback:    false
+      interactive: false
+      tags:        false
+      formats:     false
+      height:      false

   onToggle: (ev) ->
     toggles = @state.toggles
     toggles[ev.target.name] = ev.target.checked
     @setState({toggles})

-  toggleFeedbackDisplay: (ev) ->
-    @setState(displayFeedback: not @state.displayFeedback)
-
-  toggleFormatsDisplay: (ev) ->
-    @setState(displayFormats: not @state.displayFormats)
-
-  toggleTagsDisplay: (ev) ->
-    @setState(displayTags: not @state.TagsFormats)
-
   onSelection: ->
     @setState(isSelected: not @state.isSelected)

@@ -180,17 +172,23 @@
     console.warn "Exercise details was clicked"

   render: ->
-
-
     <ExercisePreview exercise={exercisePreviewStub}
       onSelection={@onSelection}
       onDetailsClick={@onDetailsClick}
       isSelected={@state.isSelected}
+      isInteractive={@state.toggles.interactive}
       displayFormats={@state.toggles.formats}
       displayAllTags={@state.toggles.tags}
       displayFeedback={@state.toggles.feedback}
       isVerticallyTruncated={@state.toggles.truncated}
     >
+      <label>
+        <input type="checkbox"
+          onChange={@onToggle} name='interactive'
+          checked={@state.toggles.interactive}
+        /> Interactive
+      </label>
+
       <label>
         <input type="checkbox"
           onChange={@onToggle} name='feedback'


Update blink to the newer api for device actions

Change-type: patch

--- lib/models/device.coffee
+++ lib/models/device.coffee
@@ -708,7 +708,7 @@
 		exports.get(uuidOrId).then (device) ->
 			return request.send
 				method: 'POST'
-				url: '/blink'
+				url: '/supervisor/v1/blink'
 				baseUrl: apiUrl
 				body:
 					uuid: device.uuid


Include reference items in current buffer

--- lib/autocomplete/reference.coffee
+++ lib/autocomplete/reference.coffee
@@ -18,11 +18,22 @@
       suggestions.sort((a, b) ->
         return a.text.indexOf(prefix) - b.text.indexOf(prefix))
       return suggestions
+
     if !@latex.manager.findAll()
       return suggestions
+
     items = []
     for tex in @latex.texFiles
       items = items.concat @getRefItems tex
+
+    editor = atom.workspace.getActivePaneItem()
+    currentPath = editor?.buffer.file?.path
+    currentContent = editor?.getText()
+
+    if currentPath and currentContent
+      if (path.extname(currentPath) == '.tex')
+        items = items.concat @getItems currentContent
+
     for item in items
       suggestions.push
         text: item
@@ -34,11 +45,8 @@
     @suggestions = suggestions
     return suggestions

-  getRefItems: (tex) ->
+  getItems: (content) ->
     items = []
-    if !fs.existsSync(tex)
-      return items
-    content = fs.readFileSync tex, 'utf-8'
     itemReg = /(?:\\label(?:\[[^\[\]\{\}]*\])?){([^}]*)}/g
     loop
       result = itemReg.exec content
@@ -46,3 +54,9 @@
       if items.indexOf result[1] < 0
         items.push result[1]
     return items
+
+  getRefItems: (tex) ->
+    if !fs.existsSync(tex)
+      return []
+    content = fs.readFileSync tex, 'utf-8'
+    return @getItems(content)


remove status indicator when no status

remove status indicator when no status

in core themes that can’t be installed/disabled

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -64,6 +64,10 @@
       @installButton.hide()
       @uninstallButton.hide()

+    # core themes only have a settings option, no status
+    if atom.packages.isBundledPackage(@pack.name) and @type is 'theme'
+      @statusIndicator.hide()
+
     if opts?.onSettingsView
       @settingsButton.remove()
     else


Fix stats position

Fix stats position

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -14,7 +14,7 @@
     description ?= ''

     @div class: 'available-package-view col-lg-8', =>
-      @div class: 'stats top-meta meta-right', =>
+      @div class: 'stats pull-right', =>
         @span class: "stats-item", =>
           @span class: 'icon icon-versions'
           @span class:'value', version


fixed bug

fixed bug

--- src/javascripts/controller/card-builder.coffee
+++ src/javascripts/controller/card-builder.coffee
@@ -7,7 +7,6 @@
     @history.fetch
       success: =>
         lastModel = @history.last()
-        console.log(@history.length, lastModel.attributes)
         if lastModel
           @model.set(lastModel.attributes)
     @formView = new App.Views.FormView(model: @model)


Improve ffi and protect against no type in top level expression

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1035,7 +1035,7 @@
       return malformed ctx, to, 'Not an assignable pattern'
     map compileVariableAssignment, (join translationCache, assigns)
   else
-    if ctx.isAtBareDefinition()
+    if ctx.isAtBareDefinition() and expression.tea
       # Force context reduction
       inferredType = (substitute ctx.substitution, expression.tea)
       deferConstraints ctx,
@@ -1499,7 +1499,11 @@
   [dotMethod, object, args...] = _validTerms call
   labelOperator dotMethod
   call.tea = toConstrained jsType
-  (jsMethod (termCompile ctx, object), dotMethod.symbol[1...], (termsCompile ctx, args))
+  if object
+    (jsMethod (termCompile ctx, object), dotMethod.symbol[1...], (termsCompile ctx, args))
+  else
+    malformed ctx, call, 'Missing an object'
+    jsNoop()

 # Adds a class to the scope or defers if superclass doesn't exist
 ms.class = ms_class = (ctx, call) ->


Make BeanBuilder more flexible, fix members' scopes.

--- lib/helpers/bean_builder.coffee
+++ lib/helpers/bean_builder.coffee
@@ -3,7 +3,11 @@

 class BeanBuilder

-  constructor: ({@basePath, @suffix}) ->
+  basePath: null
+  suffix: null
+  createObjectCallback: null
+
+  constructor: ({@basePath, @suffix, @createObjectCallback}) ->
     if @suffix[0] is '_'
       @suffix = @suffix[1..]

@@ -17,7 +21,10 @@
     requiredScope = require filePath
     Clazz = requiredScope[className]
     throw new Error "Type #{className} was not found in file #{filePath}." unless Clazz
-    new Clazz config, @
+    if typeof @createObjectCallback is 'function'
+      @createObjectCallback Clazz, config
+    else
+      new Clazz

 exports.BeanBuilder = BeanBuilder


Simplified unification in pattern compile to add all deferred names to deferred

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -683,6 +683,15 @@
 #     [x, xs...] = elems
 #     (call_ (token_ 'cons-array'), [x, (arrayToConses xs)])

+
+# Inside definition, we call assignCompile with its RHS
+#   whether to call it and with what expression is left to the RHS expression
+#   essentially assignable macros should call it
+# This then compiles the LHS
+# Possibly defers if RHS or LHS had to defer
+# We then unify LHS with RHS, which will populate context substitution
+#   with the right subs for type vars on the left
+# For each defined name in the LHS, we declare it
 assignCompile = (ctx, expression, translatedExpression) ->
   # if not translatedExpression # TODO: throw here?
   if ctx.isAtDefinition()
@@ -725,21 +734,11 @@
   if pattern.tea
     unify ctx, matched.tea, pattern.tea

-  # And substitute to get correct types TODO: make sure this works for match as well
-
-  # A big map of free typevar names to lists of value names
-  tempVars = concatConcatMaps (for {name, type} in ctx.deferredNames()
-    # ctx.addToDeferred {name, type}
-    # or use the substitution instead of type below:
-    mapMap (-> {name, type}), findFree substitute ctx.substitution, type)
-
   # log "pattern compiel", definedNames, pattern
   for {name, type} in definedNames
     currentType = substitute ctx.substitution, type
-    deps = concat mapToArray intersectRight (findFree currentType), tempVars
-    # log "deciding whether to defer type", name, currentType, deps
+    deps = ctx.deferredNames()
     if deps.length > 0
-      depsNames =  deps
       # log "adding top level lhs to deferred #{name}"
       ctx.addToDeferred {name, type, deps: (map (({name}) -> name), deps)}
       for dep in deps


Removed resolved types before resolving mutual recursion and various fixes to get the tests to run

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -38,17 +38,17 @@
     else if token.symbol in rightDelims
       closed = stack.pop()
       if token.symbol isnt delims[closed[0].symbol]
-        throw "Wrong closing delimiter #{token.symbol} for opening delimiter #{closed[0].symbol}"
+        throw new Error "Wrong closing delimiter #{token.symbol} for opening delimiter #{closed[0].symbol}"
       closed.push token
       closed.end = token.end
       if not stack[stack.length - 1]
-        throw "Missing opening delimeter matching #{token.symbol}"
+        throw new Error "Missing opening delimeter matching #{token.symbol}"
       stack[stack.length - 1].push closed
     else
       stack[stack.length - 1].push token
   ast = stack[0][0]
   if not ast
-    throw "Missing closing delimeter matching #{stack[stack.length - 1][0].symbol}"
+    throw new Error "Missing closing delimeter matching #{stack[stack.length - 1][0].symbol}"
   else
     ast

@@ -152,7 +152,7 @@

   definePattern: (pattern) ->
     if @isDefining()
-      throw "already defining, forgot to leaveDefinition?"
+      throw new Error "already defining, forgot to leaveDefinition?"
     @_scope().definition =
       name: pattern?.symbol
       pattern: pattern
@@ -229,13 +229,13 @@

   newLateScope: ->
     @newScope()
-    @_scope().late = yes
+    @_definition().late = yes

   closeScope: ->
     @scopes.pop()

-  isInLateScope: ->
-    @_scope().late
+  isInsideLateScope: ->
+    @_definition().late

   isInTopScope: ->
     @_scope().topLevel
@@ -252,8 +252,11 @@
       undefined # throw "Could not find declaration for #{name}"

   assignType: (name, type) ->
-    if lookupInMap @_scope(), name
-      (lookupInMap @_scope(), name).type = type
+    # log "TYPE OF #{name}", printType type
+    if declaration = (lookupInMap @_scope(), name)
+      if declaration.type
+        throw new Error "assignType: #{name} already has a type"
+      declaration.type = type
     else
       throw new Error "assignType: #{name} is not declared"

@@ -264,10 +267,10 @@
     @_scope().deferredBindings.push binding

   addToDefinedNames: (binding) ->
-    @_definition().definedNames.push binding
+    @_currentDefinition()?.definedNames?.push binding

   definedNames: ->
-    @_definition().definedNames
+    @_currentDefinition()?.definedNames ? []

   deferredNames: ->
     @_definition().deferredBindings
@@ -728,13 +731,14 @@
     # or use the substitution instead of type below:
     mapMap (-> {name, type}), findFree substitute ctx.substitution, type)

-  #log "pattern compiel", definedNames
+  # log "pattern compiel", definedNames, pattern
   for {name, type} in definedNames
     currentType = substitute ctx.substitution, type
     deps = concat mapToArray intersectRight (findFree currentType), tempVars
+    # log "deciding whether to defer type", name, currentType, deps
     if deps.length > 0
       depsNames =  deps
-      #log "adding top level lhs to deferred #{name}"
+      # log "adding top level lhs to deferred #{name}"
       ctx.addToDeferred {name, type, deps: (map (({name}) -> name), deps)}
       for dep in deps
         ctx.addToDeferred {name: dep.name, type: dep.type, deps: [name]}
@@ -785,17 +789,26 @@
     # TODO: proper dependency analysis to get the smallest circular deps
     #       now we are just compiling as if they were all mutually recursive
     names = concatConcatMaps map (({name, type}) -> newMapWith name, type), ctx.deferredBindings()
+    # First get rid of instances of already resolved types
+    unresolvedNames = newMap()
     for name, types of values names
+      if canonicalType = ctx.type name
+        for type in types
+          unify ctx, type, freshInstance ctx, canonicalType
+      else
+        addToMap unresolvedNames, name, types
+
+    # Now assign the same type to all occurences of the given type and unify
+    for name, types of values unresolvedNames
       canonicalType = ctx.freshTypeVariable star
       for type in types
         #log type.constructor
-        # log "unifying", canonicalType, type
         unify ctx, canonicalType, type
         # log "done unifying one"
     # log "done unifying"
-    for name of values names
+    for name of values unresolvedNames
       # All functions must have been declared already
-      ctx.assignType name, substitute ctx.substitution, canonicalType
+      ctx.assignType name, quantifyAll substitute ctx.substitution, canonicalType

 compileDeferred = (ctx) ->
   compiledPairs = []
@@ -823,6 +836,7 @@
   compiledPairs

 definitionPairCompile = (ctx, pattern, value) ->
+  # log "COMPILING", pattern
   ctx.definePattern pattern
   # log "deferrement before assign !!!", ctx.shouldDefer()
   compiled = expressionCompile ctx, value
@@ -878,8 +892,6 @@
       if ctx.isAtDefinition()
         #log "adding arity for #{ctx.definitionName()}", paramNames
         ctx.declareArity ctx.definitionName(), paramNames
-
-      # log "inside #{ctx.definitionName()}", ctx.shouldDefer(), body.tea

       assignCompile ctx, call,
         if ctx.shouldDefer()
@@ -943,7 +955,7 @@
         .map(validIdentifier)
       paramList = paramNames.join(', ')
       paramAssigns = blockOfLines paramNames.map (name) ->
-        "  this.#{name} = name;"
+        "  this.#{name} = #{name};"
       constrFn = """function #{identifier}(#{paramList}) {#{paramAssigns}};"""
       constrValue = if params
         """
@@ -1169,7 +1181,7 @@
         translation: nameTranslate ctx, atom, symbol
       }
     # Inside function only defer compilation if we don't know arity
-    else if ctx.isInLateScope() and (ctx.isDeclared symbol) or contextType instanceof TempType
+    else if ctx.isInsideLateScope() and (ctx.isDeclared symbol) or contextType instanceof TempType
       # Typing deferred, use an impricise type var
       type = ctx.freshTypeVariable star
       ctx.addToDeferredNames {name: symbol, type: type}
@@ -1327,7 +1339,7 @@
   /[A-Z]/.test atom.symbol

 isName = (expression) ->
-  throw "Nothing passed to isName" unless expression
+  throw new Error "Nothing passed to isName" unless expression
   (isAtom expression) and /[^~"'\/].*/.test expression.symbol

 isAtom = (expression) ->
@@ -1524,9 +1536,7 @@

 validIdentifier = (name) ->
   [firstChar] = name
-  if firstChar is '-'
-    throw new Error "Identifier expected, but got minus #{name}"
-  else if firstChar is '/'
+  if firstChar is '/'
     throw new Error "Identifier expected, but found regex #{name}"
   else
     name
@@ -2085,7 +2095,7 @@
 # Type inference and checker ala Mark Jones

 unify = (ctx, t1, t2) ->
-  throw "invalid args to unify" unless ctx instanceof Context and t1 and t2
+  throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
   sub = ctx.substitution
   ctx.extendSubstitution findSubToMatch (substitute sub, t1), (substitute sub, t2)

@@ -2147,13 +2157,13 @@
   (lookupInMap binding, name) or 'unbound name #{name}'

 freshInstance = (ctx, type) ->
-  throw "not a forall in freshInstance" unless type instanceof ForAll
+  throw new Error "not a forall in freshInstance" unless type instanceof ForAll
   freshes = map ((kind) -> ctx.freshTypeVariable kind), type.kinds
   (substitute freshes, type).type

 freshName = (nameIndex) ->
-  suffix = if nameIndex > 25 then Math.floor nameIndex / 25 else ''
-  String.fromCharCode 97 + nameIndex % 25
+  suffix = if nameIndex > 25 then freshName (Math.floor nameIndex / 25) - 1 else ''
+  (String.fromCharCode 97 + nameIndex % 25) + suffix

 # Kind (data
 #   Star
@@ -2204,7 +2214,7 @@
   else if type instanceof TypeApp
     (kind type.op).to
   else
-    throw "Invalid type in kind"
+    throw new Error "Invalid type in kind"

 kindsEq = (k1, k2) ->
   k1 is k2 or
@@ -2268,7 +2278,7 @@
     else if type is undefined
       "undefined"
     else
-      throw "Unrecognized type in printType"
+      throw new Error "Unrecognized type in printType"

 collectArgs = (type) ->
   if type.op?.op?.name is '->'
@@ -2329,17 +2339,17 @@
   };
 };

-var $sequenceSize = function (xs) {
+var seq_size = function (xs) {
   if (typeof xs === "undefined" || xs === null) {
     throw new Error('Pattern matching on size of undefined');
   }
   if (xs.length !== null) {
     return xs.length;
   }
-  return 1 + $sequenceSize(xs.tail);
+  return 1 + seq_size(xs.tail);
 };

-var $sequenceAt = function (i, xs) {
+var seq_at = function (i, xs) {
   if (typeof xs === "undefined" || xs === null) {
     throw new Error('Pattern matching required sequence got undefined');
   }
@@ -2352,14 +2362,14 @@
   if (i === 0) {
     return xs.head;
   }
-  return $sequenceAt(i - 1, xs.tail);
+  return seq_at(i - 1, xs.tail);
 };

-var $sequenceSplat = function (from, leave, xs) {
+var seq_splat = function (from, leave, xs) {
   if (xs.slice) {
     return xs.slice(from, xs.length - leave);
   }
-  return $listSlice(from, $sequenceSize(xs) - leave - from, xs);
+  return $listSlice(from, seq_size(xs) - leave - from, xs);
 };

 // temporary, will be replaced by typed 0-argument function
@@ -2533,7 +2543,8 @@
     return
   try
     log (collapse toHtml compiled.ast)
-    log result is (eval compiled.compiled)
+    if result isnt (eval compiled.compiled)
+      log result
   catch e
     logError "Error in test #{teaSource}", e

@@ -2542,33 +2553,75 @@
     a""", 2
   """a 2
     b 3
-    a""", 2
+    a""", 1
   """Color (data Red Blue)
     r Red
     b Blue
     r2 Red
     (= r r2)""", true
+  """positive (fn [n]
+      (match n
+        0 False
+        m True))
+    (positive 3)""", yes
   """Person (data
     Baby
     Adult [name: String])
     a (Adult "Adam")
     b Baby
-    c (Adult "Adam")
     name (fn [person]
       (match person
         (Adult name) name))
-    (= (name a) (name c))
-  """, true
+    (name a)
+  """, "Adam"
   """Person (record name: String id: Num)
     name (fn [person]
       (match person
-        (Adult name) name))
-    ((Person id: 3) "Mike")
-    """, true
+        (Person name id) name))
+    (name ((Person id: 3) "Mike"))
+    """, "Mike"
   """f (fn [x] (g x))
     g (fn [x] 2)
     (f 4)
   """, 2
+  """[x y] z
+    z [1 2]
+    y
+  """, 2
+  """snd (fn [pair]
+      (match pair
+        [x y] y))
+    (snd [1 2])
+    """, 2
+  """Person (record name: String id: Num)
+    name (fn [person]
+      (match person
+        (Person "Joe" id) 0
+        (Person name id) id))
+    (name (Person "Mike" 3))
+    """, 3
+  """{x y z} list
+     list {1 2 3}
+     z
+    """, 3
+  """tail? (fn [list]
+      (match list
+        {} False
+        xx True))
+    {x ..xs} {1}
+    (tail? xs)""", no
+  """fibonacci (fn [month] (adults month))
+    adults (fn [month]
+      (match month
+        1 0
+        n (+ (adults previous-month) (babies previous-month)))
+      previous-month (- 1 month))
+    babies (fn [month]
+      (match month
+        1 1
+        n (adults (- 1 month))))
+
+    (fibonacci 6)""", 8

 ]

@@ -2584,3 +2637,43 @@
   "Finished"
 # end of tests

+exports.compileTopLevel = compileTopLevel
+exports.compileTopLevelAndExpression = compileTopLevelAndExpression
+exports.astizeList = astizeList
+exports.astizeExpression = astizeExpression
+exports.astizeExpressionWithWrapper = astizeExpressionWithWrapper
+exports.syntaxedExpHtml = syntaxedExpHtml
+
+# exports.compileModule = (source) ->
+#   """
+#   #{library}
+#   var exports = {};
+#   #{compileDefinitionsInModule source}
+#   exports"""
+
+exports.library = library
+
+exports.isForm = isForm
+exports.isAtom = isAtom
+
+
+exports.join = join
+exports.concatMap = concatMap
+exports.concat = concat
+exports.id = id
+exports.map = map
+exports.allMap = allMap
+exports.all = all
+exports.filter = filter
+exports.partition = partition
+exports._notEmpty = _notEmpty
+exports._is = _is
+exports.__ = __
+
+exports._operator = _operator
+exports._arguments = _arguments
+exports._terms = _terms
+exports._snd = _snd
+exports._fst = _fst
+exports._labelName = _labelName
+exports._symbol = _symbol


Make sure bound variables are substituted before being used in quantification and context reduction, like in Jones

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -389,7 +389,7 @@
     addAllToSet @_scope().boundTypeVariables, vars

   allBoundTypeVariables: ->
-    concatSets (for scope in @scopes
+    substituteVarNames this, concatSets (for scope in @scopes
       scope.boundTypeVariables)...

   isClassDefined: (name) ->
@@ -2030,10 +2030,9 @@
 deferConstraints = (ctx, fixedVars, quantifiedVars, constraints) ->
   reducedConstraints = reduceConstraints ctx, constraints
   throw new Error "could not reduce constraints in deferConstraints" unless reducedConstraints
-  subbedFixedVars = substituteVarNames ctx, fixedVars
   isFixed = (constraint) ->
-    # log subbedFixedVars, constraint, (findFree constraint)
-    isSubset subbedFixedVars, (findFree constraint)
+    # log fixedVars, constraint, (findFree constraint)
+    isSubset fixedVars, (findFree constraint)
   [deferred, retained] = partition isFixed, reducedConstraints
   # TODO: handle ambiguity when reducedConstraints include variables not in
   # fixedVars or quantifiedVars


Minimal changes to lib/linter to make it compatible

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -3,9 +3,10 @@
 {CompositeDisposable, Range, Point, BufferedProcess} = require 'atom'
 _ = null
 XRegExp = null
-{MessagePanelView} = require 'atom-message-panel'
-{log, warn} = require './utils'
-
+
+# These are NOOPs in linter-plus
+log = -> undefined
+warn = -> undefined

 # Public: The base class for linters.
 # Subclasses must at a minimum define the attributes syntax, cmd, and regex.
@@ -180,14 +181,20 @@
     process.onWillThrowError (err) =>
       return unless err?
       if err.error.code is 'ENOENT'
-        ignored = atom.config.get('linter.ignoredLinterErrors')
-        subtle = atom.config.get('linter.subtleLinterErrors')
+        # Add defaults because the new linter doesn't include these configs
+        ignored = atom.config.get('linter.ignoredLinterErrors') ? []
+        subtle = atom.config.get('linter.subtleLinterErrors') ? []
         warningMessageTitle = "The linter binary '#{@linterName}' cannot be found."
         if @linterName in subtle
-          # Show a small notification at the bottom of the screen
-          message = new MessagePanelView(title: warningMessageTitle)
-          message.attach()
-          message.toggle() # Fold the panel
+          # Just mark the error on the first line. This has to be the old style
+          callback([{
+            level: 'error'
+            message: warningMessageTitle
+            range: [
+              [ 0, 0],
+              [ 0, 0]
+            ]
+          }])
         else if @linterName not in ignored
           # Prompt user, ask if they want to fully or partially ignore warnings
           atom.confirm


solved the issue of keys starting with .

--- lib/loofah.coffee
+++ lib/loofah.coffee
@@ -19,7 +19,9 @@
 deep_map_objects_and_arrays = (val, fn) ->
   key_helper = (val, fn, key) ->
     switch
-      when _.isPlainObject val then _.deepMapValues val, (subval, subkey) -> key_helper subval, fn, "#{key}.#{subkey}"
+      when _.isPlainObject val
+        _.deepMapValues val, (subval, subkey) ->
+          key_helper subval, fn, if key then "#{key}.#{subkey}" else subkey
       when _.isArray val then _.map val, (subval) -> key_helper subval, fn, key
       else fn val, key
   key_helper val, fn, ''


Fix #10

Fix #10

--- lib/terminal-view.coffee
+++ lib/terminal-view.coffee
@@ -1,5 +1,6 @@
 {CompositeDisposable} = require('atom')
 {$,View}              = require('atom-space-pen-views')
+_                     = require('underscore-plus')

 # ------------------------------------------------------------------------------

@@ -150,8 +151,10 @@
     @putTypedMessage(typedMessage)

   putTypedMessage: (typedMessage) ->
-    headLines = typedMessage.head.split('\n').splice(1).slice(0,-1)
-    bodyLines = typedMessage.body.split('\n').splice(1).slice(0,-1)
+    headLines = _.map typedMessage.head.split('\n').splice(1).slice(0,-1), \
+      (line) -> line.replace(/</g,'<').replace(/>/g,'>')
+    bodyLines = _.map typedMessage.body.split('\n').splice(1).slice(0,-1), \
+      (line) -> line.replace(/</g,'<').replace(/>/g,'>')

     # process execution warnings and errors
     id = typedMessage.id


Fix #441 error on unobserve

Fix #441 error on unobserve

--- src/adapter.coffee
+++ src/adapter.coffee
@@ -82,16 +82,17 @@

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

-    if (idx = callbacks.indexOf(callback)) >= 0
-      callbacks.splice idx, 1
+      if (idx = callbacks.indexOf(callback)) >= 0
+        callbacks.splice idx, 1

-      unless callbacks.length
-        delete map.callbacks[keypath]
+        unless callbacks.length
+          delete map.callbacks[keypath]

-    @unobserveMutations obj[keypath], obj[@id], keypath
-    @cleanupWeakReference map, obj[@id]
+      @unobserveMutations obj[keypath], obj[@id], keypath
+      @cleanupWeakReference map, obj[@id]

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


Support for ios custom swift files added

--- lib/tasks/build/bundle/ios.coffee
+++ lib/tasks/build/bundle/ios.coffee
@@ -8,6 +8,7 @@
 pathUtils = require 'path'

 OUT_DIR = './build/ios/'
+CUSTOM_NATIVE_OUT_DIR = "#{OUT_DIR}Neft/"
 STATIC_OUT_DIR = "#{OUT_DIR}static"
 ANDROID_BUNDLE_DIR = './build/ios/'

@@ -36,6 +37,10 @@
 				mustacheFiles.push path
 				return false
 			true
+	log.end logtime
+
+	logtime = log.time "Copy custom native files into `#{CUSTOM_NATIVE_OUT_DIR}`"
+	fs.copySync "./native/ios", CUSTOM_NATIVE_OUT_DIR
 	log.end logtime

 	# check whether otfinfo is installed


Better variable names. Remove unneccesary function arguments.

--- lib/token/row-token-parser.coffee
+++ lib/token/row-token-parser.coffee
@@ -167,13 +167,13 @@
     isNull: false

 readMaxBinary = (buffer) ->
-  readMax(buffer, (bytesBuffer) ->
-      Array.prototype.slice.call(bytesBuffer, 0, bytesBuffer.length)
+  readMax(buffer, (valueBuffer) ->
+      Array.prototype.slice.call(valueBuffer)
   )

 readMaxChars = (buffer, encoding) ->
-  readMax(buffer, (bytesBuffer) ->
-      bytesBuffer.toString(encoding)
+  readMax(buffer, (valueBuffer) ->
+      valueBuffer.toString(encoding)
   )

 readMax = (buffer, decodeFunction) ->
@@ -204,13 +204,13 @@
         throw new Error("Partially Length-prefixed Bytes unmatched lengths : expected #{expectedLength}, but got #{length} bytes")

     # Assemble all of the chunks in to one Buffer.
-    charBytes = new Buffer(length)
+    valueBuffer = new Buffer(length)
     position = 0
     for chunk in chunks
-      chunk.copy(charBytes, position, 0)
+      chunk.copy(valueBuffer, position, 0)
       position += chunk.length

-    value: decodeFunction(charBytes)
+    value: decodeFunction(valueBuffer)
     isNull: false

 readSmallDateTime = (buffer) ->


Expose row values by column name.

Expose row values by column name.

--- lib/token/row-token-parser.coffee
+++ lib/token/row-token-parser.coffee
@@ -29,6 +29,14 @@
       value: value
       metadata: columnMetaData

+  columns.byName = ->
+    byName = {}
+
+    for column in columns
+      byName[column.metadata.colName] = column
+
+    byName
+
   if error
     token =
       name: 'ROW'


Hiding other dialogs when showing new one

--- lib/vendor/dialog.coffee
+++ lib/vendor/dialog.coffee
@@ -26,9 +26,14 @@
       @miniEditor.getEditor().setSelectedBufferRange([[0, 0], [0, initialText.length]])

   attach: ->
+    @hideOthers()
     atom.workspaceView.append(this)
     @miniEditor.focus()
     @miniEditor.scrollToCursorPosition()
+
+  hideOthers: ->
+    $('.spark-ide-dialog').each (idx, item) ->
+      $(item).data('view').close()

   close: ->
     @remove()


SocialWorker: remove unnecessary modifiedAt field

--- workers/social/lib/social/models/apitoken.coffee
+++ workers/social/lib/social/models/apitoken.coffee
@@ -49,9 +49,6 @@
         type           : ObjectId
         required       : yes
       createdAt        :
-        type           : Date
-        default        : -> new Date
-      modifiedAt       :
         type           : Date
         default        : -> new Date


pull out namespace prefix hints on load

pull out namespace prefix hints on load

--- src/textorum/tinymce/plugin.coffee
+++ src/textorum/tinymce/plugin.coffee
@@ -63,11 +63,20 @@
     schema

   _treeAttributeFilter = (nodes, name) ->
-    i = nodes.length;
+    i = nodes.length
     snip = tree.id_prefix.length
     while (i--)
       if nodes[i].attr('id').substr(0, snip) is tree.id_prefix
         nodes[i].attr(name, null)
+
+  _nsurlAttributeFilterGenerator = (editor) ->
+    _nsurlAttributeFilter = (nodes, name, params) ->
+      i = nodes.length
+
+      while (i--)
+        node = nodes[i]
+        editor.plugins.textorum.nsmap[node.attributes.map['data-textorum-nsurl']] = node.attributes.map['data-textorum-nsprefix']
+        node.remove()

   tinymce.create 'tinymce.plugins.textorum.loader', {
     init: (editor, url) ->
@@ -77,8 +86,10 @@
       editor.onSetContent.add (ed, o) ->
         tree.update '#editortree', ed
       editor.onNodeChange.add tree.navigate
+      editor.onPreInit.add (editor) ->
+        editor.parser.addAttributeFilter 'data-textorum-nsurl', _nsurlAttributeFilterGenerator(editor)
+        editor.serializer.addAttributeFilter 'id', _treeAttributeFilter
       editor.onInit.add (editor) ->
-        editor.serializer.addAttributeFilter 'id', _treeAttributeFilter

       if editor.theme.onResolveName
         editor.theme.onResolveName.add (theme, path_object) ->
@@ -86,7 +97,7 @@
             path_object.name = path_object.node.getAttribute('data-xmlel');
       elementHelper.init editor

-    getInfo : ->
+    getInfo: ->
       {
         longname : 'Textorum',
         author : 'NCBI / Crowd Favorite',
@@ -94,5 +105,6 @@
         infourl : 'https://github.com/Annotum/textorum/',
         version : "0.1"
       }
+    nsmap: {}
   }
   tinymce.PluginManager.add('textorum', tinymce.plugins.textorum.loader)


Internationalised page titles

Internationalised page titles

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -71,8 +71,14 @@
       currentPage = @model.getPageNumber().toString() if @model? and @model.isBook?()
       if Backbone.history.fragment?.match? and linksHelper.getCurrentPathComponents().page?
         historyPage = linksHelper.getCurrentPathComponents().page
+      title = document.querySelector('title')
       if @pageTitle
-        document.title = linksHelper.stripTags(@pageTitle) + settings.titleSuffix
+        title.dataset.l10nId = @pageTitle
+        if @pageTitleArgs
+          console.log(@pageTitleArgs, JSON.stringify(@pageTitleArgs))
+          title.dataset.l10nArgs = JSON.stringify(@pageTitleArgs)
+        else
+          title.dataset.l10nArgs = "{}"

       canonical = @canonical?() or @canonical
       if canonical isnt undefined


Remove coffeescript file

Remove coffeescript file

--- src/capsule.coffee
+++ src/capsule.coffee
@@ -1,32 +0,0 @@
-udefine 'capsule', ['jquery', 'mixedice', 'eventmap'], ($, mixedice, EventMap) ->
-
-  $ify = (elem) -> if elem instanceof $ then elem else $(elem)
-
-  class Capsule
-    constructor: (@factory, target) ->
-      mixedice [@, Capsule::], new EventMap()
-
-      if target?
-        @$target = $ify target
-
-      @data = {}
-
-      @on 'render', =>
-        return unless @template
-        @template @data
-
-    execute: ->
-      @factory.call @, @ if @factory?
-      @trigger.apply @, ['render', arguments]
-
-    bindEvent: ->
-
-    bindEvents: ->
-
-    behavior: ->
-
-  Capsule.TemplateConnector = {}
-
-  Capsule.EventPool = new EventMap()
-
-  Capsule


skip ascend/reforge nodes.

skip ascend/reforge nodes.

--- scripts/bungie-data-helper.coffee
+++ scripts/bungie-data-helper.coffee
@@ -55,7 +55,7 @@
     validNodes = []
     invalid = (node) ->
       name = nodeDefs[node.nodeIndex].steps[node.stepIndex].nodeStepName
-      skip = ["Upgrade Damage", "Void Damage", "Solar Damage", "Arc Damage", "Kinetic Damage", "Ascend"]
+      skip = ["Upgrade Damage", "Void Damage", "Solar Damage", "Arc Damage", "Kinetic Damage", "Ascend", "Reforge Ready"]
       node.stateId is "Invalid" or node.hidden is true or name in skip

     validNodes.push node for node in nodes when not invalid(node)


fix url in catalog

fix url in catalog

--- src/catalog.coffee
+++ src/catalog.coffee
@@ -24,7 +24,7 @@

 	# Gets a products' complete "skuJSON". Returns a promise.
 	getProductWithVariations: (productId) =>
-		@promise(@cache.productWithVariations[productId] or $.ajax("#{@BASE_ENDPOINT}/products/variations/#{productId}"))
+		@promise(@cache.productWithVariations[productId] or $.ajax("#{@_getBaseCatalogSystemURL()}/products/variations/#{productId}"))
 			.done (response) =>
 				@setProductWithVariationsCache(productId, response)


use underscore for array detection

use underscore for array detection

--- app/express.coffee
+++ app/express.coffee
@@ -1,6 +1,7 @@
 express = require 'express'
 flash   = require 'connect-flash'
 http    = require 'http'
+_       = require 'underscore'
 reply   = require '../app/middleware/reply'
 notFound= require '../app/middleware/not_found'
 routes  = require '../routes'
@@ -45,13 +46,10 @@
         # Send argumenta objects with data() methods as plain object data
         if format is 'json' or format is 'jsonp'
           for key, val of opts
-            switch typeof val
-              when 'object'
-                if typeof val.data is 'function'
-                  opts[key] = val.data()
-              when 'array'
-                if 'function' is typeof val[0]?.data
-                  opts[key] = val.map (obj) -> obj.data()
+            if _.isObject(val) and typeof val.data is 'function'
+              opts[key] = val.data()
+            else if _.isArray(val) and typeof val[0]?.data is 'function'
+              opts[key] = val.map (obj) -> obj.data()
         return opts
     app.use notFound view: 'index'
     app.use app.router


minor refactoring in the @available method

--- library/gearbox/behavior.coffee
+++ library/gearbox/behavior.coffee
@@ -89,15 +89,15 @@
         assert _.isString(token or undefined), unnamed
         assert _.isFunction(proof), malforms if proof?
         prefixed = (fx) -> dx = {}; dx[token] = fx; dx
-        @parasite prefixed (hosting, request, decide) ->
+        @parasite prefixed (hosting, request, say) ->
             failed = "cannot get the policy qualifiers"
             qualifiers = try @entityQualifiers request
             assert _.isArray(qualifiers or no), failed
-            ANY_ROOT_SERVICE hosting, request, (decision) ->
-                return decide 0 unless decision # not root
-                return decide 0 unless value in qualifiers
-                return decide 1 unless _.isFunction proof
-                return proof.apply @, arguments # chained
+            ANY_ROOT_SERVICE hosting, request, (root) ->
+                return say 0 unless root # must be root
+                return say 0 unless value in qualifiers
+                return say 1 unless _.isFunction proof
+                return proof.apply this, arguments

     # This method is part of an internal integrity assurance toolkit.
     # Once the behavior-enabled service emits a signal that indicate


better implementation

better implementation

--- library/membrane/emailer.coffee
+++ library/membrane/emailer.coffee
@@ -101,10 +101,9 @@
     # is asynchronously wired in, so consult with `async` package.
     # Please be sure invoke the `next` arg to proceed, if relevant.
     instance: (kernel, service, next) ->
-        located = try _.has service, "emailer"
-        return next undefined if located is yes
-        notify = (s) -> service.emit "emailing", s...
-        define = -> Object.defineProperty arguments...
+        return next undefined if _.has service, "emailer"
+        notify = (seq) -> service.emit "emailing", seq...
+        define = -> try Object.defineProperty arguments...
         sender = -> service.emailer.sendMail arguments...
         @email = -> notify arguments; sender arguments...
         mkp = (prop) -> define service, "emailer", prop


Create channel 'GENERAL' as default channel

--- server/startup/initialData.coffee
+++ server/startup/initialData.coffee
@@ -5,6 +5,7 @@
 		if not ChatRoom.findOne('name': 'general')?
 			ChatRoom.insert
 				_id: 'GENERAL'
+				default: true
 				usernames: []
 				ts: new Date()
 				t: 'c'


default port 3041

default port 3041

--- services/notifications/app.coffee
+++ services/notifications/app.coffee
@@ -39,6 +39,6 @@
 	res.send 404

 host = Settings.internal?.notifications?.host || "localhost"
-port = Settings.internal?.notifications?.port || 3033
+port = Settings.internal?.notifications?.port || 3041
 app.listen port, host, ->
 	logger.info "notifications starting up, listening on #{host}:#{port}"


Remove top level discussions count

Remove top level discussions count

--- app/talk/init.cjsx
+++ app/talk/init.cjsx
@@ -14,14 +14,12 @@

   getInitialState: ->
     boards: []
-    discussionsMeta: {}

   propTypes:
     section: React.PropTypes.string # 'zooniverse' for main-talk, 'project_id' for projects

   componentWillMount: ->
     @setBoards()
-    @setDiscussionsMeta()

   setBoards: ->
     talkClient.type('boards').get(section: @props.section)
@@ -29,13 +27,6 @@
         @setState {boards}
       .catch (e) =>
         console.log "error getting boards"
-
-  setDiscussionsMeta: ->
-    talkClient.type('discussions').get(section: @props.section)
-      .then (discussions) =>
-        @setState {discussionsMeta: discussions[0]?.getMeta()}
-      .catch (e) =>
-        console.log 'Error setting discussions meta'

   onSubmitBoard: (e) ->
     e.preventDefault()
@@ -106,7 +97,6 @@

Talk Sidebar

-
{@state.discussionsMeta?.count} Discussions

           <PromiseRenderer promise={talkClient.type('tags').get(section: @props.section)}>{(tags) =>
             if tags.length


Fixed a problem in the arbotrary precision division code.

--- lib/number.coffee
+++ lib/number.coffee
@@ -297,6 +297,8 @@
   times__: (x) -> new LongInt @sign * x.sign, mul @digits, x.digits

   divmod = (r, s) ->
+    return [ZERO, ZERO] unless cleanup r
+
     scale = Math.floor BASE / (s.last() + 1)
     [r_, s_] = (seq seqTimesDigit(x, scale) for x in [r, s])
     [m, d] = [s_.size(), s_.last() + 1]
@@ -315,7 +317,7 @@
       else
         [cleanup(q), h && div(h, seq [scale])]

-    trampoline step null, null, r_.reverse()
+    trampoline step null, null, r_?.reverse()

   div = (r, s) -> divmod(r, s)[0]

@@ -467,6 +469,7 @@
   show -> number(111111111).div 37
   show -> number(111111111).div 12345679
   show -> number(99980001).div 49990001
+  show -> number(20001).div 10001

   log ''
   show -> number(111).mod 37


Bind listener to the 'reset' event. Fix #5

--- source/coffee/image-picker.coffee
+++ source/coffee/image-picker.coffee
@@ -14,14 +14,15 @@

 sanitized_options = (opts) ->
   default_options = {
-    hide_select:    true,
-    show_label:     false,
-    initialized:    undefined,
-    changed:        undefined,
-    clicked:        undefined,
-    selected:       undefined,
-    limit:          undefined,
-    limit_reached:  undefined,
+    hide_select:      true,
+    show_label:       false,
+    listen_for_reset: true,
+    initialized:      undefined,
+    changed:          undefined,
+    clicked:          undefined,
+    selected:         undefined,
+    limit:            undefined,
+    limit_reached:    undefined,
   }
   jQuery.extend(default_options, opts)

@@ -37,14 +38,21 @@

   build_and_append_picker: () ->
     @select.hide() if @opts.hide_select
-    @select.change {picker: this}, (event) ->
-      event.data.picker.sync_picker_with_select()
+    @select.change =>
+      @sync_picker_with_select()
     @picker.remove() if @picker?
     @create_picker()
     @select.after(@picker)
     @sync_picker_with_select()
+    @listen_for_reset() if @opts.listen_for_reset

-  sync_picker_with_select: () ->
+  listen_for_reset: () ->
+    @select.parents("form").bind "reset", =>
+      setTimeout( =>
+        @sync_picker_with_select()
+      , 1)
+
+  sync_picker_with_select: () =>
     for option in @picker_options
       if option.is_selected()
         option.mark_as_selected()


Fixed tabbing bug

Fixed tabbing bug

--- lib/redeye.coffee
+++ lib/redeye.coffee
@@ -54,7 +54,7 @@
   listen: ->
     @resume.on 'message', (channel, key) =>
       @workers[key]?.resume()
-      @resume.subscribe _('resume').namespace(@options.db_index)
+    @resume.subscribe _('resume').namespace(@options.db_index)

     @control.on 'message', (channel, action) => @perform action
     @control.subscribe _('control').namespace(@options.db_index)


ConfigRouter now reloads data on every request.

--- lib/configRouter.coffee
+++ lib/configRouter.coffee
@@ -53,7 +53,6 @@
     throw new Error('Missing data file path (data_path)')

   routes_ = []
-  data_ = {}

   # Route conditions allow conditional route processing based on the
   # number of object matched by the JSONPath query
@@ -64,8 +63,7 @@

   init = (cb) ->
     loadRoutes () ->
-      loadData () ->
-        cb() if cb?
+      cb() if cb?

   parseConfigLine = (line) ->
@@ -112,7 +110,7 @@

   loadData = (cb) ->
     ###
-    (Re)loads data from data_path.
+    Loads data from data_path.
     ###
     try
       datafile = fs.readFileSync data_path, 'utf8'
@@ -123,22 +121,22 @@
         log.DEBUG "including yaml file #{path}"
         yaml_data = fs.readFileSync path, 'utf8'
         return yaml.load(yaml_data)
-      data_ = yaml.load(datafile)
-      log.DEBUG 'loaded data: ' + util.inspect data_
-      cb() if cb?
+      data = yaml.load(datafile)
+      log.DEBUG 'loaded data: ' + util.inspect data
+      cb null, data
     catch except
-      log.FATAL 'cannot load data file: ' + except
+      log.ERROR 'cannot load data file: ' + except
+      cb except

-  getData = (jpath, cb) ->
+  getData = (data, jpath, cb) ->
     ###
     Retrieves data matching a given jsonpath.
     Callbacks when complete with the matched object(s) and the entire data file
     contents as context.
     ###
-
     try
-      context = data_
+      context = data
       matched = context
       if jpath == '$'
         if not (matched instanceof Array)
@@ -243,28 +241,31 @@
       log.DEBUG "url #{parsedUrl.pathname} not matched"
       next()
       return
-    if not matched.jpath?
-      send matched, {}, data_, req, res, next
-    else
-      getData matched.jpath, (err, obj, context) ->
-        if err
-          next httpError 500, err.message
-          return
-        switch matched.cond
-          when ROUTE_COND_ONE
-            if obj.length != 1
-              log.DEBUG "route condition 'one' not satisfied"
-              next()
-              return
-            obj = obj[0]
-          when ROUTE_COND_MULTI
-            if obj.length < 1
-              log.DEBUG "route condition 'multi' not satisfied"
-              next()
-          when ROUTE_COND_ALL
-          else
-            obj = obj[0]
-        send matched, obj, context, req, res, next
+    loadData (err, data) ->
+      if err?
+        next httpError 500, err.message
+      if not matched.jpath?
+        send matched, {}, data, req, res, next
+      else
+        getData data, matched.jpath, (err, obj, context) ->
+          if err?
+            next httpError 500, err.message
+            return
+          switch matched.cond
+            when ROUTE_COND_ONE
+              if obj.length != 1
+                log.DEBUG "route condition 'one' not satisfied"
+                next()
+                return
+              obj = obj[0]
+            when ROUTE_COND_MULTI
+              if obj.length < 1
+                log.DEBUG "route condition 'multi' not satisfied"
+                next()
+            when ROUTE_COND_ALL
+            else
+              obj = obj[0]
+          send matched, obj, context, req, res, next

   init(cb)
   return processRequest