Refactor Player to be less fragile, more readable.

Break up #debug. Minor rename of animation methods.

Move all state-related conditionals into separate methods. Remove larger
update-movement subroutines. Instead refactor the individual movement
methods into pieces. Call those pieces in the main #update, guarded by
the condition methods. By concentrating most conditions in dedicated
methods, those conditions become clearer and more complete. What happens
on each tick is also more obvious and less dependent on order of
operation.

--- src/player.coffee
+++ src/player.coffee
@@ -21,6 +21,9 @@

   class Player

+    # Dependencies + Properties
+    # -------------------------
+
     constructor: (origin, game, gui) ->
       @sprite = game.add.sprite origin.x, origin.y, 'player', 17
       @sprite.anchor = new Phaser.Point 0.5, 0.5
@@ -38,50 +41,62 @@
       @_initDebugging gui

       # Readonly.
+      # Actions link states. Both correspond to animations.
       @animation = null
       @state = 'still' # still, running, rising, falling
       @nextAction = 'none' # none, start, stop, jump
+      @nextDirection = null
       @nextState = null
+
+    # Public
+    # ------

     debug: (label, value, details) ->
       return unless @debugging

       value = parseFloat value.toFixed(2) if _.isNumber(value)
-
-      prettyPoint = (point) ->
-        _.chain point
-          .pick 'x', 'y'
-          .mapObject (n) -> parseFloat n.toFixed(2)
-          .value()
-
-      prettyHash = (hash) ->
-        JSON.stringify hash
-          .replace RegExps.PrettyHashRemove,''
-          .replace RegExps.PrettyHashPad, '$& '
-
-      value = prettyHash prettyPoint(value) if value instanceof Phaser.Point
+      value = @_prettyHash @_prettyPoint(value) if value instanceof Phaser.Point

       if details?.position and details.position instanceof Phaser.Point
-        details.position = prettyPoint details.position
+        details.position = @_prettyPoint details.position

       if @tracing
         label = "player:#{label}"
         if details? then console.trace label, value, details
         else console.trace label, value
       else
-        details = if details? then prettyHash(details) else ''
+        details = if details? then @_prettyHash(details) else ''
         @debugTextItems[label] = "#{label}: #{value} #{details}"

-    isInMidAir: -> @state is 'rising' or @state is 'falling'
-
     update: ->
-      @_updateBaseMovement()
-
-      @_updateXMovement()
-      @_updateYMovement()
-
-      @_updateAnimations()
-      @_changeState @nextState
+      # First.
+      @nextAction = 'none'
+      @nextDirection = @_xDirectionInput()
+
+      # Second.
+      @nextState = 'running' if @_canKeepRunning()
+      @nextState = 'falling' if @_canFall()
+      @nextState = 'landing' if @_canLand()
+
+      # Third.
+      @_beginTurn() if @_canBeginTurn()
+      @_endTurn() if @_canEndTurn()
+      @_beginRun() if @_canBeginRun()
+      @_endRun() if @_canEndRun()
+      @_beginJump() if @_canBeginJump()
+      @_endJump() if @_canEndJump()
+
+      # Fourth.
+      @acceleration.x = 0 unless @_isInMidAir() # Reset.
+      @_buildRun() if @_canBuildRun()
+      @_buildJump() if @_canBuildJump()
+
+      # Last, if needed.
+      @_changeAnimation()
+      @_changeState()
+
+    # Initialization
+    # --------------

     _initAnimations: ->
       @animations.add 'run', [0..11], 30, on
@@ -144,37 +159,61 @@
       @_jumpTimer = @sprite.game.time.create() # This also acts like a flag.
       @_isTurning = no # Because velocity won't be 0 when turning while running.

-    _changeAnimation: (nameOrFrame, interrupt = yes) ->
-      return if interrupt is no and @animation?.isPlaying
-
-      if _.isNumber(nameOrFrame)
-        frame = nameOrFrame
-        @animations.frame = frame
-        @animation = null
-
-      else
-        name = nameOrFrame
-        return if @animation?.name is name
-        @animation = @animations.play name
-
-      @debug 'animation', nameOrFrame
-
-    _changeState: (state) ->
-      return if state is @state
-
-      @state = state
-      @debug 'state', state
-
-    _jump: (ending = no) ->
-      if ending
-        return unless @_jumpTimer.running
-        @acceleration.y = @gravity.y
-        @debug 'jump:end', @_jumpTimer.ms, { position: @physics.position }
-        @_jumpTimer.stop()
+    # Change
+    # ------
+
+    _changeAnimation: ->
+      unless @_isInMidAir() or @nextAction is 'none'
+        @_playAnimation @nextAction, @animation?.loop
         return

+      switch @nextState
+        when 'running' then @_playAnimation 'run', no
+        when 'still' then @_playAnimation 17, @animation?.loop
+        when 'falling' then @_playAnimation 31, no
+        when 'landing' then @_playAnimation 'land'
+
+    _changeState: ->
+      return if @nextState is @state
+
+      @debug 'jump:peak', @physics.y if @state is 'rising'
+
+      @debug 'state', @nextState
+      @debug 'jump:start', @physics.position if @nextState is 'rising'
+
+      @state = @nextState
+
+    # Computed
+    # --------
+
+    _isInMidAir: -> @state is 'rising' or @state is 'falling'
+    _isLanded: -> @animation?.name is 'land' and @animation.isFinished
+    _isTrulyStill: -> @state is 'still' and @velocity.x is 0
+
+    _xDirectionInput: ->
+      if @cursors.left.isDown then Direction.Left
+      else if @cursors.right.isDown then Direction.Right
+
+    _xOffset: (direction = @direction) -> direction * 10
+
+    # Jump
+    # ----
+
+    _canBeginJump: ->
+      @cursors.up.isDown and not (@_jumpTimer.running or @_isInMidAir())
+    _canBuildJump: ->
+      @cursors.up.isDown and @_jumpTimer.running
+    _canEndJump: ->
+      @nextAction isnt 'jump' and @_jumpTimer.running and
+      (@cursors.up.isUp or @_jumpTimer.ms >= @jumpMaxDuration) # Release to cancel early.
+
+    _canFall: ->
+      not @_jumpTimer.running and @state is 'rising' and @velocity.y > 0
+    _canLand: ->
+      @state is 'falling' and @physics.touching.down
+
+    _beginJump: ->
       @_jumpTimer.start()
-      @debug 'jump:start', @physics.position

       @nextAction = 'jump'
       @nextState = 'rising'
@@ -183,110 +222,105 @@
       kVelocity = (1 - @jumpVelocityFactor) + @jumpVelocityFactor * ratio
       @acceleration.y = @jumpAcceleration * kVelocity

-    _run: (direction) ->
-      # Chain a start animation to the landing.
-      landed = => @animation?.name is 'land' and @animation.isFinished
-      isStill = @state is 'still' and @velocity.x is 0
-      if @direction is direction and (isStill or landed())
-        @nextAction = 'start'
-
-      # Clamp velocity.
-      @velocity.clampX -@maxVelocity.x, @maxVelocity.x
-
-      if @isInMidAir()
-        # No force, just air friction.
-        @acceleration.x = @runAcceleration * @airFrictionRatio * -direction
-        return
-
-      else if isStill or landed()
-        @nextState = 'running'
-
-      # Otherwise, just step on the pedal.
-      @acceleration.x = @runAcceleration * direction
-
-    _turn: (direction) ->
-      if @_isTurning and @animation?.isFinished
-        @nextAction = 'start'
-        @_isTurning = no
-        @debug 'turn:end', @velocity.x
-        # Visualize the turn.
-        @sprite.scale.x = direction
-        @physics.offset = new Phaser.Point @_xOffset(direction), @_yOffset
-
-      else if !@_isTurning and direction isnt @direction
-        @nextAction = 'stop'
-        @_isTurning = yes
-        @direction = direction
-        @debug 'turn:start', @velocity.x
-
-    _updateAnimations: ->
-      unless @isInMidAir() or @nextAction is 'none'
-        @_changeAnimation @nextAction, @animation?.loop
-        return
-
-      switch @nextState
-        when 'running' then @_changeAnimation 'run', no
-        when 'still' then @_changeAnimation 17, @animation?.loop
-        when 'falling' then @_changeAnimation 31, no
-        when 'landing' then @_changeAnimation 'land'
-
-    _updateBaseMovement: ->
-      @nextAction = 'none'
-
-      if @state is 'falling' and @physics.touching.down
-        @nextState = 'landing'
-      else unless @isInMidAir()
-        @nextState = 'running'
-
-    _updateXMovement: ->
-      ###
-      Basically handle switching direction, and running or being still
-      when not in the air. Note the player still runs in midair, but run
-      will behave differently.
-      ###
-      # Revert to still. (Our acceleration updates funny.)
-      @acceleration.x = 0 unless @isInMidAir()
-
-      if @cursors.left.isDown
-        @_turn Direction.Left
-        @_run Direction.Left
-
-      else if @cursors.right.isDown
-        @_turn Direction.Right
-        @_run Direction.Right
-
-      else unless @isInMidAir()
-        # Without user input, there's no force, so stop, then stay still.
-        if @velocity.x isnt 0 then @nextAction = 'stop'
-        else
-          @nextState = 'still'
-          @_isTurning = no # In case of being blocked.
-
-    _updateYMovement: ->
-      ###
-      Basically handle starting and ending of jump, and starting of falling.
-      The possibility of hitting the ceiling during jump is a complexity.
-      ###
-
-      if @cursors.up.isDown and not @isInMidAir() and not @_jumpTimer.running
-        @_jump()
-
-      else if @_jumpTimer.running and (@cursors.up.isUp or @_jumpTimer.ms >= @jumpMaxDuration) # Release to cancel early.
-        @_jump yes
-
-      else if @state is 'rising' and @velocity.y > 0
-        @nextState = 'falling'
-        @debug 'jump:peak', @physics.y
-
+    _buildJump: ->
       # Speed up by persisting the jump acceleration but with quadratic decay.
       # The longer the hold, the higher the final jump, but power decays quickly.
       # Also note that negative is up, positive is down.
-      if @cursors.up.isDown and @_jumpTimer.running
-        kEasing = (1000 - @jumpMaxDuration) + (@jumpMaxDuration - @_jumpTimer.ms)
-        kEasing = Math.pow kEasing / 1000, 2
-        @acceleration.y *= kEasing
-        @debug 'jump:build', kEasing
-
-    _xOffset: (direction = @direction) -> direction * 10
+      kEasing = (1000 - @jumpMaxDuration) + (@jumpMaxDuration - @_jumpTimer.ms)
+      kEasing = Math.pow kEasing / 1000, 2
+      @acceleration.y *= kEasing
+      @debug 'jump:build', kEasing
+
+    _endJump: ->
+      @debug 'jump:end', @_jumpTimer.ms, { position: @physics.position }
+      @acceleration.y = @gravity.y # Reset.
+      @_jumpTimer.stop()
+
+    # Run
+    # ---
+
+    _canBeginRun: ->
+      @nextDirection? and @nextDirection is @direction and
+      not @_isInMidAir() and (@_isTrulyStill() or @_isLanded())
+    _canBuildRun: ->
+      @nextDirection?
+    _canEndRun: ->
+      not (@nextDirection? or @_isInMidAir())
+    _canKeepRunning: ->
+      not (@_canLand() or @_isInMidAir())
+
+    _beginRun: ->
+      @nextAction = 'start'
+      @nextState = 'running'
+
+    _buildRun: ->
+      @velocity.clampX -@maxVelocity.x, @maxVelocity.x
+      @acceleration.x =
+        # No force, just air friction.
+        if @_isInMidAir() then @runAcceleration * @airFrictionRatio * -@nextDirection
+        # Otherwise, just step on the pedal.
+        else @runAcceleration * @nextDirection
+
+    _endRun: ->
+      # Without user input, there's no force, so stop, then stay still.
+      if @velocity.x isnt 0
+        @nextAction = 'stop'
+        return
+      @nextState = 'still'
+      @_isTurning = no # In case of being blocked.
+
+    # Turn
+    # ----
+
+    _canBeginTurn: ->
+      @nextDirection? and @nextAction isnt 'start' and
+      not (@_isTurning or @nextDirection is @direction)
+    _canEndTurn: ->
+      @nextDirection? and @animation?.isFinished and
+      @_isTurning and @nextDirection is @direction
+
+    _beginTurn: ->
+      @nextAction = 'stop'
+      @_isTurning = yes
+      @direction = @nextDirection
+      @debug 'turn:start', @velocity.x
+
+    _endTurn: ->
+      # Visualize the turn here, for now.
+      @sprite.scale.x = @nextDirection
+      @physics.offset = new Phaser.Point @_xOffset(@nextDirection), @_yOffset
+
+      @nextAction = 'start'
+      @_isTurning = no
+      @debug 'turn:end', @velocity.x
+
+    # Helpers
+    # -------
+
+    _playAnimation: (nameOrFrame, interrupt = yes) ->
+      return if interrupt is no and @animation?.isPlaying
+
+      if _.isNumber(nameOrFrame)
+        frame = nameOrFrame
+        @animations.frame = frame
+        @animation = null
+
+      else
+        name = nameOrFrame
+        return if @animation?.name is name
+        @animation = @animations.play name
+
+      @debug 'animation', nameOrFrame
+
+    _prettyPoint: (point) ->
+      _.chain point
+        .pick 'x', 'y'
+        .mapObject (n) -> parseFloat n.toFixed(2)
+        .value()
+
+    _prettyHash: (hash) ->
+      JSON.stringify hash
+        .replace RegExps.PrettyHashRemove,''
+        .replace RegExps.PrettyHashPad, '$& '

   Player


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -114,6 +114,12 @@
       hsl = @toHsl()
       @hue(180)

+    desaturate: (amount) ->
+      hsl = @toHsl()
+      hsl[1] -= amount
+
+      return Color2(hslToRgb(hsl))
+
     equal: (other) ->
       other.r == @r &&
       other.g == @g &&


Make typeahead a bit faster

Make typeahead a bit faster

--- src/search.coffee
+++ src/search.coffee
@@ -3,10 +3,11 @@
     engine = new Bloodhound
         name: 'suggestions'
         remote:
-            url: sm_settings.backend_url + "/search/?language=#{lang}&input=%QUERY"
+            url: sm_settings.backend_url + "/search/?language=#{lang}&page_size=5&input=%QUERY"
             ajax: settings.applyAjaxDefaults {}
             filter: (parsedResponse) ->
                 parsedResponse.results
+            rateLimitWait: 50
         datumTokenizer: (datum) -> Bloodhound.tokenizers.whitespace datum.name[lang]
         queryTokenizer: Bloodhound.tokenizers.whitespace


drain message

drain message

--- src/server.coffee
+++ src/server.coffee
@@ -30,7 +30,9 @@
     socket.on 'connect',             => console.log "connected to socket at #{@_url()}"
     socket.on 'connect_error', (obj) => console.log 'connect error', obj
     socket.on 'disconnect',          => console.log "socket at #{@_url()} disconnected"
-    socket.on 'colorChanged',           @_write_colors_over_tty
+    socket.on 'colorChanged', (data) =>
+      @_write_colors_over_tty(data)
+
     socket.on 'colorSet',               @_write_colors_over_tty

   #
@@ -45,12 +47,13 @@
     )

     serial_port.on "open", =>
-      console.log "Node serialport connected to #{tty}"
+      logger.info "Node serialport connected to #{tty}"
+      logger.debug serial_port

       @_setup_sio()

       serial_port.on 'data', (data) ->
-        console.log 'data received: ' + data
+        logger.info 'data received: ' + data

   #
   # break our colors string into array, write each color to the appropriate address
@@ -64,11 +67,12 @@
     colors = data.color.split '\n'
     colors.pop()

-    # build our TTY instruction, padding with black if necessary
+    # build our TTY instruction
     logger.debug "colors: #{colors} length: #{colors.length}"
     instruction = ''
     instruction += "4,#{i+1},#{color};" for color, i in colors

+    # padding with black if necessary
     instruction_count = (instruction.match(/;/g)||[]).length
     for i in [instruction_count...5]
       instruction += "4,#{i+1},000,000,000,000;"
@@ -78,5 +82,8 @@
     serial_port.write instruction, (err, results) ->
       logger.debug 'serial_port.write err: ' + err if err
       logger.debug 'serial_port.write results: ' + results if results
+      serial_port.drain( (error) ->
+        logger.debug "serial_port drained! with error: #{error}"
+      )

 module.exports = Server


history support

history support

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -248,7 +248,7 @@
       z = url.match(/^([a-zA-Z:]+)(\s+)?(.*)?/)
       event_name = z[1]
       selector   = z[3] || document #when it a custom event: 'custom:event' for example
-      Sirius.Application.adapter.bind(selector, event_name, handler)
+      Sirius.Application.adapter.bind(document, selector, event_name, handler)

     # for cache change obj[k, v] to array [[k,v]]
     array_of_routes = for url, action of routes when @_hash_route(url)
@@ -318,11 +318,19 @@
       # bind all <a> element with dispatch function, but bind only when href not contain "#"
       selector  = "a:not([href^='#'])"

-      Sirius.Application.adapter.bind selector, "click", dispatcher
-
+      Sirius.Application.adapter.bind document, selector, "click", dispatcher
+    `var c = function(m){console.log(m);};`
     window.onhashchange = dispatcher
-#    if push_state_support
-#      Sirius.Application.adapter.bind window, "popstate", dispatcher
+    if push_state_support
+      Sirius.Application.adapter.bind window, null, "popstate", (e) ->
+        # should run only for plain routes, not for hash based!
+        # history.state.href contain url which start from "#", when hash change
+        #                    contain full address otherwise
+        # also when we visit from plain url to hash, then history.state is null
+        if history && history.state && (history.state == null || history.state['href'].indexOf("#") != 0)
+          dispatcher(e)
+
+

     fn()


proper ETA null check for amazon

proper ETA null check for amazon

--- src/amazon.coffee
+++ src/amazon.coffee
@@ -48,12 +48,12 @@
     matchResult = response.toString().match('"promiseMessage":"Arriving (.*?)"')
     matchResult ?= response.toString().match('"promiseMessage":"Now expected (.*?)"')
     arrival = matchResult?[1]
-    if arrival.match('today')
+    if arrival?.match('today')
       eta = moment()
-    else if arrival.match('tomorrow')
+    else if arrival?.match('tomorrow')
       eta = moment().add(1, 'day')
     else
-      if arrival.match('-')
+      if arrival?.match('-')
         arrival = arrival.split('-')[1]
       foundMonth = false
       for month in MONTHS


Add comment env support

Add comment env support

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -159,6 +159,7 @@
       { include: '#envVerbatim' }
       { include: '#envTikz' }
       { include: '#envTable' }
+      { include: '#envComment' }
       { include: '#envGeneric' }

       # Potential additions:
@@ -786,6 +787,24 @@
       5: name: 'punctuation.definition.arguments.end.latex'
     patterns: [{ include: '$self' }]
   }
+  envComment: {
+    name: 'meta.environment.comment.latex'
+    contentName: 'comment.block.latex'
+    begin: '((\\\\)begin)(\\{)(comment)(\\})'
+    beginCaptures:
+      1: name: 'support.function.environment.begin.latex'
+      2: name: 'punctuation.definition.function.begin.latex'
+      3: name: 'punctuation.definition.arguments.begin.latex'
+      4: name: 'variable.parameter.environment.$5.latex'
+      5: name: 'punctuation.definition.arguments.end.latex'
+    end: '((\\\\)end)(\\{)(\\4)(\\})'
+    endCaptures:
+      1: name: 'support.function.environment.end.latex'
+      2: name: 'punctuation.definition.function.latex'
+      3: name: 'punctuation.definition.arguments.begin.latex'
+      4: name: 'variable.parameter.environment.latex'
+      5: name: 'punctuation.definition.arguments.end.latex'
+  }
   envDocument: {
     comment: 'To stop the entire contents of the document environment being labelled. Its useless anyway, as I split up my work and just include it into the main file.'
     match: '((\\\\)(begin|end))(\\{)(document)(\\})'


Update uglify grunt task

Update uglify grunt task

--- grunt/uglify.coffee
+++ grunt/uglify.coffee
@@ -3,7 +3,7 @@
     report: 'gzip'
   all:
     files:
-      'dist/snowflake.all.min.js': 'dist/snowflake.all.js'
+      'dist/<%= package.name %>.all.min.js': 'dist/<%= package.name %>.all.js'
   compact:
     files: [{
       expand: true


Support for im_marked

Support for im_marked

--- src/client.coffee
+++ src/client.coffee
@@ -184,7 +184,7 @@
         else
           console.warn "Could not find channel "+message.channel+" for message"

-      when "channel_marked"
+      when "channel_marked", "im_marked"
         channel = @getChannelGroupOrDMByID message.channel
         if channel
           console.log "Moved cursor to "+message.ts+" in "+channel.name


Revert original Typo issue

Revert original Typo issue

--- src/client.coffee
+++ src/client.coffee
@@ -221,7 +221,7 @@
                 @emit 'new_user', message
             else
                 # Check for `pong` response
-                if message.data.text? and message.data.text == "pong"
+                if message.data?.text and message.data.text == "pong"
                     @logger.info 'ACK ping'
                     @_lastPong = Date.now()
                     @emit 'ping', message


Implement https://api.slack.com/events/star_added and https://api.slack.com/events/star_removed

--- src/client.coffee
+++ src/client.coffee
@@ -341,6 +341,14 @@

       when "error"
         @emit 'error', message.error
+
+      when "star_added"
+        m = new Message @, message
+        @emit 'star_added', m
+
+      when "star_removed"
+        m = new Message @, message
+        @emit 'star_removed', m

       when "message"
         # is this the special message we get on reconnect?


Return response for every multiAction

Return response for every multiAction

--- src/client.coffee
+++ src/client.coffee
@@ -415,7 +415,7 @@
 								continue

 							o = @_parseResponse response.result
-							result.push o if o
+							result.push o or response.result

 					done()
 		, (err) ->


correct reply value in bufferWithTime

correct reply value in bufferWithTime

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -478,6 +478,7 @@
     scheduled = false
     end = null
     values = []
+    reply = Bacon.more
     if not isFunction(delay)
       delayMs = delay
       delay = (f) -> setTimeout(f, delayMs)
@@ -487,24 +488,25 @@
         delay(flush)
       flush = =>
         if values.length > 0
-          @push next(values, event)
+          reply = @push next(values, event)
           values = []
           if end?
             @push end
-          else
+          else if reply != Bacon.noMore
             schedule()
         else
           scheduled = false
       if event.isError()
-        @push event
+        reply = @push event
       else if event.isEnd()
         if scheduled
           end = event
         else
-          @push event
+          reply = @push event
       else
         values.push(event.value())
         schedule() if not scheduled
+      reply

   bufferWithCount: (count) ->
     values = []


simplify cached, as suggested by @philipnilsson

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1166,9 +1166,9 @@
     value = None
     ->
       if value == None
-        value = new Some(f())
+        value = f()
         f = null
-      value.get()
+      value
 }

 Bacon._ = _


add new opsworks resources

add new opsworks resources

--- src/coffin.coffee
+++ src/coffin.coffee
@@ -85,6 +85,11 @@
         MetricFilter: null
       Lambda:
         Function: null
+      OpsWorks:
+        App: null
+        Instance: null
+        Layer: null
+        Stack: null
       RDS:
         DBInstance: null
         DBParameterGroup: null


Fixed application started language

Fixed application started language

--- app/app.coffee
+++ app/app.coffee
@@ -28,8 +28,8 @@
   if config.socket? and fs.existsSync config.socket
     fs.chmodSync config.socket, '777'

-  port = if config.socket? then 'socket' else 'port'
+  portType = if config.socket? then 'socket' else 'port'
   time = new Date - start
-  logger.info "#{config.appName} started on #{port} #{app.settings.port} (#{env} mode) in #{time}ms"
+  logger.info "#{config.appName} started#{if port = app.settings.port then " on #{portType} #{port}" else ''} (#{env} mode) in #{time}ms"

 module.exports = app


update documentation for rowRangeForParagraphAtBufferRow #vc

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -551,7 +551,7 @@

   # Public: Retrieves the range for the current paragraph.
   #
-  # A paragraph is defined as a block of text surrounded by empty lines.
+  # A paragraph is defined as a block of text surrounded by empty lines or comments.
   #
   # Returns a {Range}.
   getCurrentParagraphBufferRange: ->


Rename ALL_CAPS vars to CapCamelCase

Rename ALL_CAPS vars to CapCamelCase

--- src/editor.coffee
+++ src/editor.coffee
@@ -9,9 +9,9 @@
 $ = require './jquery-extensions'
 _ = require './underscore-extensions'

-MEASURE_RANGE = document.createRange()
-TEXT_NODE_FILTER = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
-NO_SCOPE = ['no-scope']
+MeasureRange = document.createRange()
+TextNodeFileter = { acceptNode: -> NodeFilter.FILTER_ACCEPT }
+NoScope = ['no-scope']

 # Private: Represents the entire visual pane in Atom.
 #
@@ -1588,7 +1588,7 @@

   measureToColumn: (lineElement, tokenizedLine, column) ->
     left = oldLeft = index = 0
-    iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TEXT_NODE_FILTER)
+    iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFileter)

     returnLeft = null

@@ -1608,9 +1608,9 @@
           left = oldLeft + cachedVal
         else
           # i + 1 to measure to the end of the current character
-          MEASURE_RANGE.setEnd(textNode, i + 1)
-          MEASURE_RANGE.collapse()
-          left = MEASURE_RANGE.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())
+          MeasureRange.setEnd(textNode, i + 1)
+          MeasureRange.collapse()
+          left = MeasureRange.getClientRects()[0].left - Math.floor(@scrollView.offset().left) + Math.floor(@scrollLeft())

           @setCharacterWidthCache(scopes, char, left - oldLeft) if scopes?

@@ -1619,7 +1619,7 @@
     returnLeft ? left

   checkCharacterWidthCache: (scopes, char) ->
-    scopes ?= NO_SCOPE
+    scopes ?= NoScope
     obj = Editor.characterWidthCache
     for scope in scopes
       obj = obj[scope]
@@ -1627,7 +1627,7 @@
     obj[char]

   setCharacterWidthCache: (scopes, char, val) ->
-    scopes ?= NO_SCOPE
+    scopes ?= NoScope
     obj = Editor.characterWidthCache
     for scope in scopes
       obj[scope] ?= {}


don't let plugin break each other

don't let plugin break each other

--- src/editor.coffee
+++ src/editor.coffee
@@ -151,4 +151,8 @@

   ## load plugins
   plugins = _.map Dir.list(Chrome.appRoot() + "/plugins"), (plugin) ->
-    require plugin
+    try
+      require plugin
+    catch e
+      name = _.last plugin.split '/'
+      console.error "Problem loading plugin #{name}: #{e.message}"


Move hidden input to the same position with cursor view.

This is required to make IME's input bubble follow the cursor.

--- src/editor.coffee
+++ src/editor.coffee
@@ -88,6 +88,7 @@
     @configure()
     @bindKeys()
     @handleEvents()
+    @handleImeEvents()
     @cursorViews = []
     @selectionViews = []
     @pendingChanges = []
@@ -696,6 +697,10 @@
       else
         @gutter.addClass('drop-shadow')

+  handleImeEvents: ->
+    @on 'cursor:moved', =>
+      @hiddenInput.offset(@getCursorView().offset())
+
   selectOnMousemoveUntilMouseup: ->
     lastMoveEvent = null
     moveHandler = (event = lastMoveEvent) =>


Always calculate the soft wrap column

Always calculate the soft wrap column

--- src/editor.coffee
+++ src/editor.coffee
@@ -905,10 +905,7 @@
     @setSoftWrap(not @activeEditSession.getSoftWrap())

   calcSoftWrapColumn: ->
-    if @activeEditSession.getSoftWrap()
-      Math.floor(@scrollView.width() / @charWidth)
-    else
-      Infinity
+    Math.floor(@scrollView.width() / @charWidth)

   # Sets the soft wrap column for the editor.
   #


extracting SingleCamera module

extracting SingleCamera module

--- src/engine.coffee
+++ src/engine.coffee
@@ -2,8 +2,6 @@
   defaults =
     FPS: 30
     age: 0
-    ambientLight: 1
-    cameraTransform: Matrix.IDENTITY
     excludedModules: []
     includedModules: []
     paused: false
@@ -161,18 +159,7 @@
       return unless canvas = I.canvas

       self.trigger "beforeDraw", canvas
-
-      I.canvas.withTransform I.cameraTransform, (canvas) ->
-        if I.zSort
-          drawObjects = I.objects.copy().sort (a, b) ->
-            a.I.zIndex - b.I.zIndex
-        else
-          drawObjects = I.objects
-
-        drawObjects.invoke("draw", canvas)
-
-        self.trigger "draw", I.canvas
-
+      self.trigger "draw", canvas
       self.trigger "overlay", I.canvas

     step = ->
@@ -219,6 +206,12 @@

         return object

+      ###*
+      Returns a copy of the objects array. This prevents unwanted side effects.
+      ###
+      objects: ->
+        I.objects.copy()
+
       eachObject: (iterator) ->
         I.objects.each iterator

@@ -336,10 +329,9 @@
       draw: draw
     }

-    self.attrAccessor "ambientLight", "cameraTransform"
     self.include Bindable

-    defaultModules = ["Keyboard", "Clear", "Delay", "SaveState", "Selector", "Collision"]
+    defaultModules = ["Keyboard", "Clear", "Delay", "SaveState", "Selector", "SingleCamera", "Collision"]
     modules = defaultModules.concat(I.includedModules)
     modules = modules.without([].concat(I.excludedModules))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine.coffee
+++ src/engine.coffee
@@ -291,6 +291,9 @@
     "Mouse"
     "Background"
     "Delay"
-    "GameState", "Selector", "Collision"]
+    "GameState"
+    "Selector"
+    "Collision"
+    ]
   (exports ? this)["Engine"] = Engine
 )()


Add forBase to errors

Add forBase to errors

--- src/errors.coffee
+++ src/errors.coffee
@@ -58,4 +58,12 @@
   # @param [String] attribute the attribute to get errors for
   # @return [Object] the error object for the attribute
   forAttribute: (attribute) ->
-    @__errors[attribute]
+    ActiveResource::Collection.build(@__errors[attribute]).inject {}, (out, error) ->
+      out[error.code] = error.message
+      out
+
+  # Returns the error object for base
+  #
+  # @return [Object] the error object for base
+  forBase: ->
+    @forAttribute('base')


errors: fix error classes

errors: fix error classes

--- src/errors.coffee
+++ src/errors.coffee
@@ -29,31 +29,31 @@
 ##
 # The actual exported error classes

-class exports.Forbidden
+class exports.Forbidden extends ServerError
     condition: "forbidden"
     type: "auth"

-class exports.Conflict
+class exports.Conflict extends ServerError
     condition: "conflict"
     type: "cancel"

-class exports.BadRequest
+class exports.BadRequest extends ServerError
     condition: "bad-request"
     type: "modify"

-class exports.FeatureNotImplemented
+class exports.FeatureNotImplemented extends ServerError
     condition: "feature-not-implemented"
     type: "cancel"

-class exports.InternalServerError
+class exports.InternalServerError extends ServerError
     condition: "internal-server-error"
     type: "cancel"

-class exports.NotFound
+class exports.NotFound extends ServerError
     condition: "item-not-found"
     type: "cancel"

-class exports.NotAllowed
+class exports.NotAllowed extends ServerError
     condition: "not-allowed"
     type: "cancel"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,6 +1,6 @@
 ###*
 The Follow module provides a simple method to set an object's
-direction so that it is pointed
+direction so that it is pointed at another object.

 The calculated velocity is based on the center point of
 each object.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -65,6 +65,6 @@
   @param {GameObject} obj The object you want to follow
   ###
   follow: (obj) ->
-    # Grac
+    #
     if obj
       I.velocity = obj.position().subtract(self.position()).norm(I.speed)


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -16,7 +16,7 @@
   width: 10
   height: 10

-en
+enemy.vol

 player.update()
 </pre></code>


Test with a native function

Test with a native function

--- spec/AsComponent.coffee
+++ spec/AsComponent.coffee
@@ -136,6 +136,23 @@
           return done err if err
           chai.expect(res).to.equal 'Hello there'
           done()
+    describe 'with a built-in function', ->
+      it 'should be possible to componentize', (done) ->
+        component = -> noflo.asComponent Math.random
+        loader.registerComponent 'ascomponent', 'sync-zero', component, done
+      it 'should contain correct ports', (done) ->
+        loader.load 'ascomponent/sync-zero', (err, instance) ->
+          return done err if err
+          chai.expect(Object.keys(instance.inPorts.ports)).to.eql ['in']
+          chai.expect(Object.keys(instance.outPorts.ports)).to.eql ['out', 'error']
+          done()
+      it 'should send to OUT port', (done) ->
+        wrapped = noflo.asCallback 'ascomponent/sync-zero',
+          loader: loader
+        wrapped 'bang', (err, res) ->
+          return done err if err
+          chai.expect(res).to.be.a 'number'
+          done()
   describe 'with an asynchronous function taking a single parameter and callback', ->
     describe 'with successful callback', ->
       func = (hello, callback) ->


Add more tests for GIF frame extractor

Add more tests for GIF frame extractor

--- spec/GetGIFFrame.coffee
+++ spec/GetGIFFrame.coffee
@@ -36,7 +36,7 @@
     it 'should have error port', ->
       chai.expect(c.outPorts.error).to.be.an 'object'

-  describe 'when passed a GIF filepath', ->
+  describe 'when passing a GIF filepath', ->
     describe 'with a valid frame number', ->
       it 'should extract a frame as a temporary filepath', (done) ->
         @timeout 20000
@@ -58,4 +58,65 @@
         frame.send 0
         ins.send inSrc
         ins.endGroup()
+    describe 'with an invalid frame number', ->
+      it 'should output the original GIF with no changes, as a temporary file', (done) ->
+        @timeout 20000
+        groupId = 'gif-filepath-invalid-frame'
+        groups = []
+        out.once 'begingroup', (group) ->
+          groups.push group
+        out.once 'endgroup', (group) ->
+          groups.pop()
+        out.once 'data', (data) ->
+          gm.compare data, inSrc, (err, isEqual, equality) ->
+            chai.expect(isEqual).to.be.true
+            done()
+        error.once 'data', (err) ->
+          done err

+        inSrc = './spec/fixtures/animated.gif'
+        ins.beginGroup groupId
+        frame.send 'foo'
+        ins.send inSrc
+        ins.endGroup()
+    describe 'with no frame number', ->
+      it 'should extract the first frame', (done) ->
+        @timeout 20000
+        groupId = 'gif-filepath-valid-frame'
+        groups = []
+        out.once 'begingroup', (group) ->
+          groups.push group
+        out.once 'endgroup', (group) ->
+          groups.pop()
+        out.once 'data', (data) ->
+          expected = './spec/fixtures/first_frame.gif'
+          gm.compare data, expected, (err, isEqual, equality) ->
+            chai.expect(isEqual).to.be.true
+            done()
+        error.once 'data', (err) ->
+          done err
+
+        inSrc = './spec/fixtures/animated.gif'
+        ins.beginGroup groupId
+        ins.send inSrc
+        ins.endGroup()
+  describe 'when passed a non-GIF filepath', ->
+    it 'should return the original filepath, unchanged', (done) ->
+      @timeout 20000
+      groupId = 'gif-filepath-valid-frame'
+      groups = []
+      out.once 'begingroup', (group) ->
+        groups.push group
+      out.once 'endgroup', (group) ->
+        groups.pop()
+      out.once 'data', (data) ->
+        chai.expect(data).to.be.equal inSrc
+        done()
+      error.once 'data', (err) ->
+        done err
+
+      inSrc = './spec/fixtures/original.jpg'
+      ins.beginGroup groupId
+      frame.send 0
+      ins.send inSrc
+      ins.endGroup()


corrected document.cookie pairs vow after adding ancestor path

--- spec/cookie-spec.coffee
+++ spec/cookie-spec.coffee
@@ -63,7 +63,7 @@
             , {}
           "should include only visible cookies": (pairs)->
             keys = (key for key, value of pairs).sort()
-            assert.deepEqual keys, "_domain1 _expires1 _expires2 _name _path1".split(" ")
+            assert.deepEqual keys, "_domain1 _expires1 _expires2 _name _path1 _path4".split(" ")
           "should match name to value": (pairs)->
            assert.equal pairs._name, "value"
            assert.equal pairs._path1, "yummy"


make decorator pay attention to source.keys()

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -480,7 +480,7 @@
       throw new Error 'argument must be a function, string, object or array of these'

   hinoki.decorateSourceToAlsoLookupWithPrefix = (innerSource, prefix) ->
-    (name) ->
+    source = (name) ->
       result = innerSource(name)
       if result?
         return result
@@ -493,6 +493,9 @@
       wrapperFactory = (wrapped) -> wrapped
       wrapperFactory.__inject = [prefixedName]
       return wrapperFactory
+    if innerSource.keys?
+      source.keys = innerSource.keys
+    return source

 ################################################################################
 # return the hinoki object from the factory


Simplify test

Simplify test

--- spec/parser_spec.coffee
+++ spec/parser_spec.coffee
@@ -22,6 +22,7 @@
     generated = if hasReferences then followReferences(parser) else JSON.stringify(parser.toJSON(), null, 2)

     diff(expected, generated)
+    checkDelta(expected, generated, diff(expected, generated))

   followReferences = (parser) ->
     parser.parseFile "spec/templates/methods/method_example.coffee"
@@ -50,9 +51,10 @@
     # [0], because we don't want the parsed files in the resulting JSON
     JSON.stringify([parser.toJSON()[0]], null, 2)

-  checkDelta = (delta) ->
+  checkDelta = (expected, generated, delta) ->
     if delta?
-      console.error(inspect(delta))
+      console.error expected, generated
+      console.error(delta)
       expect(delta).toBe(undefined)

   beforeEach ->
@@ -64,112 +66,87 @@
       title: ''
       quiet: false
       private: true
+      verbose: true
       github: ''
     })

   describe "Classes", ->
     it 'understands descriptions', ->
-      delta = constructDelta("spec/templates/classes/class_description_markdown.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/class_description_markdown.coffee")

     it 'understands documentation', ->
-      delta = constructDelta("spec/templates/classes/class_documentation.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/class_documentation.coffee")

     it 'understands extends', ->
-      delta = constructDelta("spec/templates/classes/class_extends.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/class_extends.coffee")

     it 'understands empty classes', ->
-      delta = constructDelta("spec/templates/classes/empty_class.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/empty_class.coffee")

     it 'understands exporting classess', ->
-      delta = constructDelta("spec/templates/classes/export_class.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/export_class.coffee")

     it 'understands inner classes', ->
-      delta = constructDelta("spec/templates/classes/inner_class.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/inner_class.coffee")

     it 'understands namespaced classes', ->
-      delta = constructDelta("spec/templates/classes/namespaced_class.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/namespaced_class.coffee")

     it 'understands simple classes', ->
-      delta = constructDelta("spec/templates/classes/simple_class.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/classes/simple_class.coffee")

   describe "non class files", ->
     it 'understands descriptions', ->
-      delta = constructDelta("spec/templates/files/non_class_file.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/files/non_class_file.coffee")

   describe "Methods", ->
     it 'understands assigned parameters classes', ->
-      delta = constructDelta("spec/templates/methods/assigned_parameters.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/assigned_parameters.coffee")

     it 'understands class methods', ->
-      delta = constructDelta("spec/templates/methods/class_methods.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/class_methods.coffee")

     it 'understands curly notation', ->
-      delta = constructDelta("spec/templates/methods/curly_method_documentation.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/curly_method_documentation.coffee")

     it 'understands hash parameters', ->
-      delta = constructDelta("spec/templates/methods/hash_parameters.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/hash_parameters.coffee")

     it 'understands instance methods', ->
-      delta = constructDelta("spec/templates/methods/instance_methods.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/instance_methods.coffee")

     it 'understands links in methods', ->
-      delta = constructDelta("spec/templates/methods/links.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/links.coffee")

     it 'understands method delegation', ->
-      delta = constructDelta("spec/templates/methods/method_delegation.coffee", true)
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/method_delegation.coffee", true)

     it 'understands method delegation from public to private', ->
-      delta = constructDelta("spec/templates/methods/method_delegation_as_private.coffee", true)
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/method_delegation_as_private.coffee", true)

     it 'understands basic methods', ->
-      delta = constructDelta("spec/templates/methods/method_example.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/method_example.coffee")

     it 'understands methods with paragraph descriptions for parameters', ->
-      delta = constructDelta("spec/templates/methods/method_paragraph_param.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/method_paragraph_param.coffee")

     it 'understands methods with no descriptions', ->
-      delta = constructDelta("spec/templates/methods/method_shortdesc.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/method_shortdesc.coffee")

     it 'understands optional arguments', ->
-      delta = constructDelta("spec/templates/methods/optional_arguments.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/optional_arguments.coffee")

     it 'understands paragraph length descriptions', ->
-      delta = constructDelta("spec/templates/methods/paragraph_desc.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/paragraph_desc.coffee")

     it 'understands preprocessor flagging for visibility', ->
-      delta = constructDelta("spec/templates/methods/preprocessor_flagging.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/preprocessor_flagging.coffee")

     fit 'understands prototypical methods', ->
-      delta = constructDelta("spec/templates/methods/prototypical_methods.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/prototypical_methods.coffee")

     it 'understands return values', ->
-      delta = constructDelta("spec/templates/methods/return_values.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/return_values.coffee")

     it 'understands paragraph length return values', ->
-      delta = constructDelta("spec/templates/methods/return_values_long.coffee")
-      checkDelta(delta)
+      constructDelta("spec/templates/methods/return_values_long.coffee")


Add better errors to .toHaveLength spec exception

Returns a helpful error when the actual object does not have a length
var.

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -118,9 +118,13 @@
       @actual instanceof expected

     toHaveLength: (expected) ->
-      notText = if @isNot then " not" else ""
-      this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
-      @actual.length == expected
+      if not @actual?
+        this.message = => "Expected object #{@actual} has no length method"
+        false
+      else
+        notText = if @isNot then " not" else ""
+        this.message = => "Expected object with length #{@actual.length} to#{notText} have length #{expected}"
+        @actual.length == expected

     toExistOnDisk: (expected) ->
       notText = this.isNot and " not" or ""


:bug:

:bug:

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -2,7 +2,6 @@
 _ = require 'underscore-plus'

 class SpecError
-  @extend()
   constructor: (@message) ->
     @name = 'SpecError'


Create layout as child of active level

Create layout as child of active level

--- src/layout.coffee
+++ src/layout.coffee
@@ -14,17 +14,6 @@
 {controls} = require './layout/controls'
 {grid} = require './layout/grid'
 {inspector} = require './layout/inspector'
-
-views = {
-  _EXPORTS: ["render"]
-  render: (world) -> render(world)
-  _CHILDREN: [
-    header
-    controls
-    grid
-    inspector # select which sprite_state to display
-  ]
-}

 # Desired Structure
 # game
@@ -38,7 +27,12 @@

 exports.layout = (game_dict) ->
-  level = game_dict [my.key.children]
-
-  game_level._CHILDREN = [views]
-  +  level_dict = game_dict[my.key.children]
+  level_dict._CHILDREN = [
+    header(game_dict.name, level_dict)
+    controls
+    grid
+    # sprites?
+    # inspector?
+  ]
+  game_dict


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -238,9 +238,9 @@
       else
         name.each (item) ->

-        for(var i=0;i<name.length;i++){
+        `for(var i=0;i<name.length;i++){
           this.add(name[i]);
-        }
+        }`
       }
     },
     all: function() {


Do not run the for/of transform in Babel.

Because the target environment in Atom supports for/of natively,
do not transpile for/of using Babel. Without this change, the following code:

```
var arr = ['foo', 'bar', 'baz'];
for (var item of arr) console.log(item);
```

would be unnecessarily be transpiled to:

```
var arr = ['foo', 'bar', 'baz'];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
  for (var _iterator = arr[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
    var item = _step.value;
    console.log(item);
  }
} catch (err) {
  _didIteratorError = true;
  _iteratorError = err;
} finally {
  try {
    if (!_iteratorNormalCompletion && _iterator['return']) {
      _iterator['return']();
    }
  } finally {
    if (_didIteratorError) {
      throw _iteratorError;
    }
  }
}
```

--- src/babel.coffee
+++ src/babel.coffee
@@ -25,6 +25,7 @@
   # I think this can include es6.arrowFunctions, es6.classes, and
   # possibly others, but I want to be conservative.
   blacklist: [
+    'es6.forOf'
     'useStrict'
   ]


:fire: Remove deprecations from Marker

:fire: Remove deprecations from Marker

--- src/marker.coffee
+++ src/marker.coffee
@@ -1,6 +1,5 @@
 _ = require 'underscore-plus'
 {CompositeDisposable, Emitter} = require 'event-kit'
-Grim = require 'grim'

 # Essential: Represents a buffer annotation that remains logically stationary
 # even as the buffer changes. This is used to represent cursors, folds, snippet
@@ -335,7 +334,6 @@

   destroyed: ->
     delete @displayBuffer.markers[@id]
-    @emit 'destroyed' if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-destroy'
     @emitter.dispose()

@@ -369,35 +367,7 @@
     @oldTailScreenPosition = newTailScreenPosition
     @wasValid = isValid

-    @emit 'changed', changeEvent if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-change', changeEvent

   getPixelRange: ->
     @displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)
-
-if Grim.includeDeprecatedAPIs
-  EmitterMixin = require('emissary').Emitter
-  EmitterMixin.includeInto(Marker)
-
-  Marker::on = (eventName) ->
-    switch eventName
-      when 'changed'
-        Grim.deprecate("Use Marker::onDidChange instead")
-      when 'destroyed'
-        Grim.deprecate("Use Marker::onDidDestroy instead")
-      else
-        Grim.deprecate("Marker::on is deprecated. Use documented event subscription methods instead.")
-
-    EmitterMixin::on.apply(this, arguments)
-
-  Marker::getAttributes = ->
-    Grim.deprecate 'Use Marker::getProperties instead'
-    @getProperties()
-
-  Marker::setAttributes = (properties) ->
-    Grim.deprecate 'Use Marker::setProperties instead'
-    @setProperties(properties)
-
-  Marker::matchesAttributes = (attributes) ->
-    Grim.deprecate 'Use Marker::matchesProperties instead'
-    @matchesProperties(attributes)


Better variable names, rework async logic

--- src/brain.coffee
+++ src/brain.coffee
@@ -9,7 +9,6 @@
 # Brain logic for RiveScript
 utils = require("./utils")
 inherit_utils = require("./inheritance")
-RSVP = require("rsvp")
 q = require("q")
 _ = require("lodash")

@@ -356,7 +355,7 @@
     argsRe = /«__call_arg__»([\s\S]*?)«\/__call_arg__»/ig

     giveup = 0
-    matches = []
+    callSignatures = []
     promises = []

     while match = callRe.exec(reply)
@@ -370,14 +369,13 @@
       # get subroutine name
       subroutineNameMatch = (/(\S+)/ig).exec(text)
       subroutineName = subroutineNameMatch[0]
-
       args = []

       # get arguments
       while m = argsRe.exec(text)
         args.push(m[1])

-      matches.push
+      callSignatures.push
         text: text
         obj: subroutineName
         args: args
@@ -385,7 +383,7 @@
         length: match[0].length

     # go through all the object calls and run functions
-    for data in matches
+    for data in callSignatures
       output = ""
       if @master._objlangs[data.obj]
         # We do. Do we have a handler for it?
@@ -398,35 +396,27 @@
       else
         data.output = @master.errors.objectNotFound

-      # if we get a promise back and we are not in the async mode,
-      # leave an error message to suggest using an async version of rs
-      # otherwise, keep promises tucked into a list where we can check on
-      # them later
-      if async
-        promises.push
-          promise: if utils.isAPromise(data.output) then data.output else q(data.output)
-          data: data
-        continue
-      else if utils.isAPromise(data.output)
+      if not async and utils.isAPromise(data.output)
         data.output = "[ERR: Using async routine with reply: use replyAsync instead]"

-    if not async
-      # cycle through backwards so indexes in replace command stay the same
-      for data in matches by -1
-          reply = @._replaceCallTags(data, reply)
-      return reply
+    if async
+      return @._resolveCallTagsAsync(callSignatures, reply)
     else
-      # wait for all the promises to be resolved and
-      # return a resulting promise with the final reply
-      return new RSVP.Promise (resolve, reject) =>
-        RSVP.all(p.promise for p in promises).then (results) =>
-          for promise, i in promises by -1
-            promise.data.output = results[i]
-            reply = @_replaceCallTags(promise.data, reply)
-
-          resolve(reply)
-        .catch (reason) =>
-          reject(reason)
+      return @._resolveCallTags(callSignatures, reply)
+
+  _resolveCallTagsAsync: (callSignatures, reply) ->
+    return q.all(_.map(callSignatures, 'output'))
+      .then (results) =>
+        for result, i in results by -1
+          callSignatures[i].output = result
+          reply = @._replaceCallTags(callSignatures[i], reply)
+        return reply
+
+  _resolveCallTags: (callSignatures, reply) ->
+    # cycle through backwards so indexes in replace command stay the same
+    for data in callSignatures by -1
+        reply = @._replaceCallTags(data, reply)
+    return reply

   _replaceCallTags: (data, reply) ->
     return reply.slice(0, data.start) + data.output + reply.slice(data.start + data.length)


cleanup + fix <reply> and <input>

cleanup + fix <reply> and <input>

--- src/brain.coffee
+++ src/brain.coffee
@@ -401,7 +401,7 @@
             isMatch = true
         else
           # Non-atomic triggers always need the regexp.
-          match = msg.match(new RegExp("^#{regexp}$"))
+          match = msg.match(new RegExp("^#{regexp}$"), "i")
           if match
             # The regexp matched!
             isMatch = true
@@ -415,16 +415,19 @@
         # We check if there is a trigger condition
         if isMatch and trig[1].triggerCondition isnt null
           condition = trig[1].triggerCondition
-          @say "Checking trigger condition #{condition}"
+          @say "Checking trigger condition"

           # Then we process all tags (forJS)
+          @say "Raw condition: #{condition}"
           condition = @processTags(user, msg, condition, stars, thatstars, step, scope, true)
           condition = @processCallTags(condition, scope, false)
-          @say "#{condition}"
+          @say "Processed condition: #{condition}"
+
           try isMatch = eval(condition)
           catch e
             @say "Error while evaluating triggerCondition: #{e}"
             isMatch = false
+
           @say "Returned: #{isMatch}"
           if !isMatch
             stars = []
@@ -754,7 +757,7 @@
         for i in [1..9]
           if regexp.indexOf("<#{type}#{i}>") > -1
             regexp = regexp.replace(new RegExp("<#{type}#{i}>", "g"),
-              @master._users[user].__history__[type][i])
+              @formatMessage(@master._users[user].__history__[type][i-1]))

     # Recover escaped Unicode symbols.
     if @utf8 and regexp.indexOf("\\u") > -1
@@ -832,10 +835,10 @@
     for i in [1..9]
       if reply.indexOf("<input#{i}>") > -1
         reply = reply.replace(new RegExp("<input#{i}>", "ig"),
-          @master._users[user].__history__.input[i])
+          @master._users[user].__history__.input[i-1])
       if reply.indexOf("<reply#{i}>") > -1
         reply = reply.replace(new RegExp("<reply#{i}>", "ig"),
-          @master._users[user].__history__.reply[i])
+          @master._users[user].__history__.reply[i-1])

     # <id> and escape codes
     reply = reply.replace(/<id>/ig, user)


Add a comment about equality.

Add a comment about equality.

--- src/notice.coffee
+++ src/notice.coffee
@@ -58,6 +58,8 @@

   _sanitize: (obj, seen = []) ->
     if obj instanceof Object
+      # Object equality is determined by reference which means this should pass
+      # on unique objects with the same (or empty) values. {} != {}.
       if obj in seen
         @log("Dropping circular data structure.", k, v, obj)
         return "[CIRCULAR DATA STRUCTURE]"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -32,10 +32,9 @@
       if bits[4]? then parseFloat(bits[4]) else 1.0
     ]

-  `function normalizeKey(key) {
+  normalizeKey = (key) ->
     key.toLowerCase().replace(/ /g, "")
-  }`
-
+
   window.Color = (color) ->
     color ||= "rgba(0, 0, 0, 0)"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -333,8 +333,14 @@

     desaturatedBlue = blue.desaturate(0.3)

-    desaturatedBlue.toString()
-    # => 'rgba(38, 38, 217, 1)'
+    # to see what they look like
+    for color, index in ['blue', 'desaturatedBlue']
+      canvas.drawRect
+        color: eval(color)
+        x: 20 + (60 * index)
+        y: 20 + (60 * index)
+        width: 60
+        height: 60
     </pre></code>

     @name desaturate


Only download the same tarball once per invocation

--- src/generate-package.coffee
+++ src/generate-package.coffee
@@ -4,6 +4,7 @@

 knownExprs = {}
 pkginfos = {}
+hashes = {}

 generatePackage = (name, range, callback) ->
   # Avoid dependency cycles
@@ -42,27 +43,35 @@
       unless knownExprs["#{nm}-#{rng}"]
         pkgCount += 1
         generatePackage nm, rng, finishedCallback
-    http.get "http://registry.npmjs.org/#{name}/-/#{name}-#{version}.tgz", (res) ->
-      unless res.statusCode is 200
-        return error "Failed to get tarball for #{name}-#{version}: #{http.STATUS_CODES[res.statusCode]}"
-      res.on 'error', (err) ->
-        error "Error getting tarball for #{name}-#{version}: #{err}"
-      hash = crypto.createHash 'sha256'
-      hash.on 'error', (err) ->
-        error "Error getting hash for #{name}-#{version}: #{err}"
-      readHash = ->
-        hashBuffer = hash.read 32
-        if hashBuffer?
-          knownExprs["#{name}-#{range}"] =
-            hash: hashBuffer
-            patchLatest: patchLatest
-            dependencies: deps
-            version: version
-            name: name
-          finishedCallback()
-      res.pipe hash
-      hash.on 'readable', readHash
-      readHash()
+
+    handleHash = ->
+      knownExprs["#{name}-#{range}"] =
+        hash: hashes["#{name}-#{version}"]
+        patchLatest: patchLatest
+        dependencies: deps
+        version: version
+        name: name
+      finishedCallback()
+
+    if "#{name}-#{version}" of hashes
+      handleHash()
+    else
+      http.get "http://registry.npmjs.org/#{name}/-/#{name}-#{version}.tgz", (res) ->
+        unless res.statusCode is 200
+          return error "Failed to get tarball for #{name}-#{version}: #{http.STATUS_CODES[res.statusCode]}"
+        res.on 'error', (err) ->
+          error "Error getting tarball for #{name}-#{version}: #{err}"
+        hash = crypto.createHash 'sha256'
+        hash.on 'error', (err) ->
+          error "Error getting hash for #{name}-#{version}: #{err}"
+        readHash = ->
+          hashBuffer = hash.read 32
+          if hashBuffer?
+            hashes["#{name}-#{version}"] = hashBuffer
+            handleHash()
+        res.pipe hash
+        hash.on 'readable', readHash
+        readHash()

   if name of pkginfos
     handleInfo()


default dirname on call to getStatus()

default dirname on call to getStatus()

--- src/git-status-utils.coffee
+++ src/git-status-utils.coffee
@@ -32,10 +32,10 @@
       ]
     }
     ```
-    Not that remote and commitsAheadBehind are not always given by git status, for example,
+    Note that remote and commitsAheadBehind are not always given by git status, for example,
     files a local branch with no tracking remote
   ###
-  @getStatus: (dirName) ->
+  @getStatus: (dirName='.') ->
     rawStatus = @_fetchStatus(dirName)
     return @_parseGitStatus(rawStatus)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -118,6 +118,6 @@
   ]

   4.circularPoints (p, i) ->
-    o p.x.toFixed(2) .equal(points[i]), "#{p} == #{points[i]}"
+    equal p.x.toFixed(2) .equal(points[i]), "#{p} == #{points[i]}"

 module undefined


test matching / as path

test matching / as path

--- test/path_matcher_test.coffee
+++ test/path_matcher_test.coffee
@@ -7,7 +7,18 @@

 PathMatcher = matchers.Path

-test "Path matching", ->
+test "Path matching for '/'", ->
+  matcher = new PathMatcher("/")
+  assert.deepEqual(
+    matcher.match("/"),
+    {}
+  )
+  assert.equal(
+    matcher.match("/foo"),
+    false
+  )
+
+test "Path matching, capturing last component", ->
   matcher = new PathMatcher("/accounts/:account_id")
   assert.deepEqual(
     matcher.pattern,
@@ -27,12 +38,14 @@
     false
   )

+test "Path matching, capturing middle component", ->
   matcher = new PathMatcher("/accounts/:account_id/channels")
   assert.deepEqual(
     matcher.pattern,
     ["accounts", {name: "account_id"}, "channels"]
   )

+test "Path matching, capturing multiple components", ->
   matcher = new PathMatcher("/accounts/:account_id/channels/:channel_id")
   assert.deepEqual(
     matcher.pattern,


improved testhelper

improved testhelper

--- servers/testhelper.coffee
+++ servers/testhelper.coffee
@@ -112,7 +112,7 @@
     return data

-  generateCreateTeamRequestBody = ->
+  @generateCreateTeamRequestBody = (opts = {}) ->

     companyName = "testcompany#{generateRandomString(10)}"
     username    = generateRandomUsername()
@@ -124,24 +124,27 @@
       slug           :  companyName
       allow          :  'true'
       domains        :  'koding.com, kd.io'
-      invitees       :  'cihangir@koding.com,cihangir@koding.com,cihangir@savas.io,'
+      invitees       :  'test@koding.com,test@test.com,'
       newsletter     :  'true'
       username       :  username
       password       :  'testpass'
       agree          :  'on'
       passwordConfirm:  'testpass'
-      redirect       :  "#{@getUrl}?username=#{username}"
+      redirect       :  "#{generateUrl()}?username=#{username}"
+
+
+    deepObjectExtend defaultBodyObject, opts

     return defaultBodyObject

   # overwrites given options in the default params
-  @generateCreateRequestParams = (opts = {}) ->
+  @generateCreateTeamRequestParams = (opts = {}) ->

     url  = generateUrl
       route : '-/teams/create'

-    body = generateCreateTeamRequestBody()
+    body = TeamHandlerHelper.generateCreateTeamRequestBody()

     params               = { url, body }
     defaultRequestParams = generateDefaultParams params
@@ -154,7 +157,7 @@

 class RegisterHandlerHelper

-  generateRequestBody = ->
+  @generateRequestBody = (opts = {}) ->

     defaultBodyObject =
       email             : generateRandomEmail()
@@ -163,6 +166,8 @@
       username          : generateRandomUsername()
       passwordConfirm   : 'testpass'
       agree             : 'on'
+
+    deepObjectExtend defaultBodyObject, opts

     return defaultBodyObject

@@ -173,7 +178,7 @@
     url  = generateUrl
       route : 'Register'

-    body = generateRequestBody()
+    body = RegisterHandlerHelper.generateRequestBody()

     params                = { url, body }
     defaultRequestParams  = generateDefaultParams params


Add more documentation comments.

Add more documentation comments.

--- src/formatted_text_field.coffee
+++ src/formatted_text_field.coffee
@@ -775,30 +775,46 @@
   off: (args...) ->
     @element.off args...

+  # Gets the formatted text value. This is the same as the value of the
+  # underlying input element.
+  #
+  # Returns a String containing the input value.
   @::__defineGetter__ 'text', ->
     @element.val()

+  # Sets the formatted text value. This generally should not be used. Instead,
+  # use the value setter.
   @::__defineSetter__ 'text', (text) ->
     @element.val(text)

+  # Gets the unformatted text value. This is the value that should be
+  # considered the "real" value of the field.
+  #
+  # Returns a String containing the logical value of the field.
   @::__defineGetter__ 'value', ->
     value = @element.val()
     return value unless @_formatter
     @_formatter.parse value

+  # Sets the unformatted text value, or logical value, of the field.
   @::__defineSetter__ 'value', (value) ->
     value = @_formatter.format(value) if @_formatter
     @element.val value
     @element.trigger 'change'

+  # Gets the current formatter. Formatters are used to translate between #text
+  # and #value propertiees of the field.
   @::__defineGetter__ 'formatter', ->
     @_formatter

+  # Sets the current formatter.
   @::__defineSetter__ 'formatter', (formatter) ->
     value = @value
     @_formatter = formatter
     @value = value

+  # Internal: Returns an index map from indexes of the formatted #text to
+  # indexes of the #value.
   @::__defineGetter__ 'textToValueMapping', ->
     value = @value
     text = @text
@@ -818,11 +834,15 @@

     return mapping

+  # Gets the selection caret with start and end indexes relative to #value.
+  #
+  # Returns an Object with 'start' and 'end' keys.
   @::__defineGetter__ 'caret', ->
     mapping = @textToValueMapping
     textCaret = @element.caret()
     return start: mapping[textCaret.start], end: mapping[textCaret.end]

+  # Sets the current selection caret.
   @::__defineSetter__ 'caret', (valueCaret) ->
     min = 0
     max = @value.length


This should track loads...

This should track loads...

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -27,7 +27,9 @@
     Error: 'Operation unsuccessful.'

   @trackLoad: () ->
-    console.log("Loaded Carrot");
+    req = new XMLHttpRequest()
+    req.open 'GET', 'https://gocarrot.com/tracking?' + window?.document?.location?.search?.substr 1
+    req.send()

   constructor: (appId, udid, appSecret, hostname) ->
     try


one more try with exports

one more try with exports

--- src/parser.coffee
+++ src/parser.coffee
@@ -130,11 +130,11 @@
     """

 // #{@fullPath}
-var #{scriptName(@name)} = (function(module) {
-  var exports = module.exports;
+var #{scriptName(@name)} = (function(module, exports) {
+  module.exports = exports;
   #{output.join('')}
   return exports;
-})({exports: {}});
+})({}, {});

 """


Remove ES6 compat from Chrome-app source

--- src/top.coffee
+++ src/top.coffee
@@ -1,6 +1,6 @@
 require("./primitive")

-require("./es6compat")
+# require("./es6compat")  # Chrome supports ES6!
 require("./es7compat")
 require("./bbjscompat")
 require("./bootbox-promise")


add mitigation dir to express server

add mitigation dir to express server

--- src/server.coffee
+++ src/server.coffee
@@ -14,6 +14,7 @@
 app.use "/styles", express.static "#{__dirname}/styles"
 app.use "/vendor", express.static "#{__dirname}/vendor"
 app.use "/img", express.static "#{__dirname}/img"
+app.use "/mitigation", express.static "#{__dirname}/mitigation"

 # Provide a proxy for cross-origin information
 app.get "/proxy", (req, res) ->


MCAdmin.GalleryAssets coffee - Fix autoInit selector

--- app/assets/javascripts/martian_components/components_admin/gallery_assets.coffee
+++ app/assets/javascripts/martian_components/components_admin/gallery_assets.coffee
@@ -1,7 +1,7 @@
 class @MCAdmin.GalleryAssets
   @autoInit: ->
     init = =>
-      $('form[id^=form-gallery] .assets-inputs').each (i, el) => new @($(el))
+      $('form[id^=form_gallery] .assets-inputs').each (i, el) => new @($(el))

     $(document).on 'ajaxComplete', (ev, xhr) =>
       return false unless $(xhr.responseJSON?.component_form).length


remove method for children

remove method for children

--- src/xml.coffee
+++ src/xml.coffee
@@ -154,21 +154,31 @@
     up: () -> null # this node has no parent

     end: () =>
-        @closed = yes
-        if @headers
-            data = "#{indent this}#{@headers}/>"
-            @closed = 'self'
-        else
-            data = "#{indent this}</#{@name}>"
-        @emit 'end', data
-        this
-
-    toString: =>
+        if not @closed
+            if @headers
+                data = "#{indent this}#{@headers}/>"
+                @closed = 'self'
+            else
+                data = "#{indent this}</#{@name}>"
+                @closed = yes
+            @emit 'end', data
+        else if @closed is 'removed'
+            @emit 'end'
+        else
+            @closed = yes
+        this
+
+    toString: () =>
         "<#{@name}#{new_attrs @attrs}" +
             if @closed is 'self'
                 "/>"
             else if @closed
                 ">#{@content}</#{@name}>" # FIXME children ?
+
+    remove: () =>
+        @closed = 'removed' unless @closed
+        @emit 'remove', this
+        this

 class Builder extends EventEmitter


emit attribute changes

emit attribute changes

--- src/xml.coffee
+++ src/xml.coffee
@@ -106,8 +106,11 @@
         if typeof key is 'string'
             return @attrs[key] if @attrs[key] and not value
             @attrs[key] = value
+            @emit 'attr', this, key, value
         else
-            @attrs[k] = v for own k, v of key
+            for own k, v of key
+                @attrs[k] = v
+                @emit 'attr', this, k, v
         @headers = "<#{@name}#{new_attrs @attrs}" if @headers
         this


Add callbacks to manage errors.

Add callbacks to manage errors.

--- src/ector.coffee
+++ src/ector.coffee
@@ -25,7 +25,9 @@
 file_backup = "cn.json"
 ector = new Ector()
 ector.injectConceptNetwork FileConceptNetwork
-ector.cn.load file_backup
+ector.cn.load file_backup, (err) ->
+  if err
+    console.error 'Error while loading cn.json\n%s', err
 previousResponseNodes = null
 quiet = false
 speakReplies =
@@ -43,8 +45,11 @@
     msg.reply msg.random speakReplies

   robot.respond /save yourself/i, (msg) ->
-    ector.cn.save file_backup
-    msg.reply "Thanks, My mind is safe now!"
+    ector.cn.save file_backup, (err) ->
+      if err
+        msg.reply "An error occurred while saving cn.json:", err
+      else
+        msg.reply "Thanks, My mind is safe now!"

   robot.catchAll (msg) ->
     if not quiet


feat: add `matcher` method to the Router prototype

This function is most useful when you have a prefix string that should be ignored while matching. But it can be used for a quick conversion from Router instance to (req, res) pipe.

--- src/Router.coffee
+++ src/Router.coffee
@@ -40,6 +40,10 @@
       return -> match
     return null

+  matcher: (pathname) -> (req, res) ->
+    if route = router.match req, pathname
+      return route req, res
+
   extend: (fns) ->
     fn this for fn in fns
     return


Remove unused commented lines

Remove unused commented lines

--- src/ChildProcess.coffee
+++ src/ChildProcess.coffee
@@ -61,35 +61,4 @@
   kill: (signal)->
     @child.kill(signal)

-module.exports = ChildProcess
-###
-    exec: (command,options,cb)->
-    log.debug "ChildProcess.exec() ",command
-    expect(@child).to.be.null
-    expect(command).to.be.a 'string'
-
-    if options?
-      expect(options).to.be.an 'object'
-
-    if cb?
-      expect(cb).to.be.a 'function'
-
-    @child = _exec(command,options,cb)
-
-    @child.stdout.setEncoding('utf8');
-    @child.stderr.setEncoding('utf8');
-
-    @child.stdout.on "data", (data) =>
-      log.info data
-
-    @child.stderr.on "data", (data) =>
-      log.error data
-
-    @child.on "exit", (code,signal) =>
-      if code?
-        log.info "#{command} exited with code: " + code
-      else if signal?
-        log.info "#{command} killed with signal: " + signal
-      else
-        log.error "#{command} exited: " + args
-###+module.exports = ChildProcess


Remove HTML wrapper and other unused functions

--- src/index.coffee
+++ src/index.coffee
@@ -5,25 +5,6 @@
 ATTR_WHITELIST = ['href', 'src', 'target']

 $ = React.createElement
-defaultHTMLWrapper = React.createClass
-  _update: ->
-    current = @props.html
-    if @_lastHtml isnt current
-      @_lastHtml = current
-      node = @refs.htmlWrapper.getDOMNode()
-      node.contentDocument.body.innerHTML = @props.html
-      node.style.height = node.contentWindow.document.body.scrollHeight + 'px'
-      node.style.width  = node.contentWindow.document.body.scrollWidth  + 'px'
-
-  componentDidUpdate: -> @_update()
-  componentDidMount: -> @_update()
-
-  render: ->
-    $ 'iframe',
-      ref: 'htmlWrapper'
-      html: @props.html
-      style:
-        border: 'none'

 toChildren = (node, parentKey, tableAlign = []) ->
   return (for child, i in node.children
@@ -32,27 +13,6 @@
 isValidDocument = (doc) ->
   parsererrorNS = (new DOMParser()).parseFromString('INVALID', 'text/xml').getElementsByTagName("parsererror")[0].namespaceURI
   doc.getElementsByTagNameNS(parsererrorNS, 'parsererror').length == 0
-
-isInvalidXML = -> false
-if global.DOMParser?
-  parser = new DOMParser()
-  isInvalidXML = (xmlString) ->
-    parsererrorNS = parser.parseFromString('INVALID', 'text/xml').getElementsByTagName("parsererror")[0].namespaceURI
-    dom = parser.parseFromString(xmlString, 'text/xml')
-
-    if dom.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0
-      throw new Error('Error parsing XML')
-    return dom
-
-renderCompiledToStaticMarkup = (compiled) ->
-  if compiled.props?.html?
-    compiled.props.html
-  else if compiled.type?
-    React.renderToStaticMarkup(compiled)
-  else if typeof(compiled) is 'string'
-    compiled
-  else
-    compiled.toString()

 getPropsFromHTMLNode = (node, attrWhitelist) ->
   string =
@@ -162,9 +122,7 @@
     else
       throw node.type + ' is unsuppoted node type. report to https://github.com/mizchi/md2react/issues'

-htmlWrapperComponent = null
 module.exports = (raw, options = {}) ->
-  htmlWrapperComponent = options.htmlWrapperComponent ? defaultHTMLWrapper
   sanitize = options.sanitize ? true
   highlight = options.highlight ? (code, lang, key) ->
     $ 'pre', {key, className: 'code'}, [


testing

testing

--- src/index.coffee
+++ src/index.coffee
@@ -36,7 +36,7 @@
       meetingStartTime = _.get event, 'genisys.currentMeeting.startTime'
       noShowLimit = moment(meetingStartTime).utc().add(@limitMinutes, 'minute')
       if (moment().isBefore(noShowLimit) && moment(@getValidUntil()).isBefore(moment()))
-        @updateValidUntil
+        @updateValidUntil()
         console.log "Initializing valid until to no show limit as : #{@getValidUntil().toISOString()}"

       if (moment(@getValidUntil()).isBefore(moment().utc()) && moment().utc().isAfter(noShowLimit))


Simplify `use()`

Simplify `use()`

--- src/index.coffee
+++ src/index.coffee
@@ -69,8 +69,7 @@
   request.plugins = plugins or []
   request.defaults = defaults

-  request.use = (plugins...) ->
-    factory request.defaults, request.plugins.concat plugins
+  request.use = (plugins...) -> factory @defaults, @plugins.concat plugins

   request.bare = -> factory()


+ [bug] missing to import functions

+ [bug] missing to import functions

--- src/index.coffee
+++ src/index.coffee
@@ -6,7 +6,8 @@
 isFunction      = require 'util-ex/lib/is/type/function'
 isString        = require 'util-ex/lib/is/type/string'
 isArray         = require 'util-ex/lib/is/type/array'
-defineProperty  = require('util-ex/lib/defineProperty')
+isUndefined     = require 'util-ex/lib/is/type/undefined'
+defineProperty  = require 'util-ex/lib/defineProperty'
 Attributes      = require './attributes/'

 attributes      = new Attributes()


test $add / $remove / $eval

test $add / $remove / $eval

--- src/index.coffee
+++ src/index.coffee
@@ -85,3 +85,8 @@
 app.$set 'newCount', 5
 console.log app.$get 'newCount'
 console.log app.newCount
+
+# TODO: $add / $delete
+
+# $eval
+console.log app.$eval 'title | uppercase'


Expose version

Expose version

--- src/index.coffee
+++ src/index.coffee
@@ -1,4 +1,4 @@
 import * as sake from './sake'
-export * from './sake'
-
+export *         from './sake'
+export {version} from '../package.json'
 export default sake


add debug logging

add debug logging

--- src/routes.coffee
+++ src/routes.coffee
@@ -66,6 +66,7 @@
                     request.body.platform_id = platform.id
                     @postDataByUid request, response
                 else
+                    console.error err
                     response.status(500).send status: 'rejected', error: 'could not create record for egg'
         else
             @postDataByUid request, response


Fix markdown styling

Fix markdown styling

--- docs/components/docs/DocsBody.cjsx
+++ docs/components/docs/DocsBody.cjsx
@@ -42,19 +42,8 @@
       file:
         paddingBottom: '60px'

-      title:
-        fontSize: '32px'
-        fontWeight: '200'
-        color: 'rgba(0,0,0,.67)'
-        paddingBottom: '20px'
-
-      subtitle:
-        fontSize: '24px'
-        fontWeight: '400'
-        color: 'rgba(0,0,0,.67)'
-
   render: ->
-

+

       <Container>
         <Grid uneven>

@@ -76,17 +65,39 @@

             </Animate>

+
+
             { for fileName, file of docs
                 regex = /---[\s\S]*?title: (.+)[\s\S]*?---([\s\S]*)/.exec(file)
                 title = regex[1]
                 body = regex[2]
-                if body.trim()
-

-
{ title }

-                    <Markdown>{ body }</Markdown>
-

-                else
-
{ title }
 }
+
+
+
+

+                  { if fileName.split('-')[0].indexOf('.') is -1
+
{ title }

+                    else
+
{ title }
 }
+
+                  { if body.trim()
+

+                        <Markdown>{ body }</Markdown>
+
 }
+
 }
+

         </Grid>


begin to decouple bindings from dot-delimited keypath logic

--- src/batman.coffee
+++ src/batman.coffee
@@ -4695,15 +4695,18 @@
       # If we're working with an `@key` and not an `@value`, find the context the key belongs to so we can
       # hold a reference to it for passing to the `dataChange` and `nodeChange` observers.
       if k = @get('key')
-        Batman.RenderContext.deProxy(@get("keyContext.#{k}"))
+        if keyContext = @get('keyContext')
+          prop = Batman.Property.forBaseAndKey(keyContext, k)
+          Batman.RenderContext.deProxy(prop.getValue())
       else
         @get('value')
     set: (_, value) ->
       if k = @get('key')
         keyContext = @get('keyContext')
         # Supress sets on the window
-        if keyContext != Batman.container
-          @set("keyContext.#{k}", value)
+        if keyContext and keyContext != Batman.container
+          prop = Batman.Property.forBaseAndKey(keyContext, k)
+          prop.setValue(value)
       else
         @set('value', value)


Make jscheme.add() chainable

Make jscheme.add() chainable

--- src/scheme.coffee
+++ src/scheme.coffee
@@ -17,6 +17,8 @@
     else
       @addSchema(name, @parseConfigObj(schema, undefined, name))

+    this
+

   addSchema: (name, schema) ->
     if @validators[name]?
@@ -26,6 +28,8 @@
     @validators[name] = (value) =>
       errors = @recursiveValidate(schema, value)
       return if errors.hasErrors() then errors else true
+
+    this

   # @returns { Boolean } returns if the object is valid or not.


handle 404 event

handle 404 event

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -227,7 +227,7 @@
   #
   # @param routes [Object] object with routes
   # @param fn [Function] callback, which will be called, after routes will be defined
-  # @event application:hashchange - generate, when url change
+  # @event application:urlchange - generate, when url change
   # @event application:404 - generate, if given url not matched with given routes
   # @event application:run - generate, after application running
   create: (routes, fn = ->) ->
@@ -257,18 +257,17 @@
       [url, action]

     dispatcher = (e) ->
-      c 111111
       prev = current
       result = false
-      arr = null
+      route_array = null

       if event.type == "hashchange"
         # hashchange
-        arr = array_of_routes
+        route_array = array_of_routes
         current = window.location.hash
       else
         #plain
-        arr = plain_routes
+        route_array = plain_routes
         href = e.target.href # TODO the same for hashchange
         history.pushState({href: href}, "#{href}", href)
         pathname = window.location.pathname
@@ -279,7 +278,7 @@
       Sirius.Application.logger("Url change to: #{current}")
       Sirius.Application.adapter.fire(document, "application:urlchange", current, prev)

-      for part in arr
+      for part in route_array
         f = part[0]
         r = f.match(current)
         if r && !result
@@ -294,11 +293,12 @@

       if !result
         Sirius.Application.adapter.fire(document, "application:404", current, prev)
-        #FIXME
         r404 = routes['404'] || routes[404]
         if r404
-          z = new Sirius.ControlFlow(r404)
-          z.handle_event(null, current)
+          if Sirius.Utils.is_function(r404)
+            r404(current)
+          else
+            (new Sirius.ControlFlow(r404)).handle_event(null, current)

       false


import round trips the export

import round trips the export

--- src/canvas.coffee
+++ src/canvas.coffee
@@ -623,89 +623,96 @@
 		# * data - Object housing the data for application
 		#

-		# **FIXME:** May want to tease this out as a configurable option or as a global
-		#
-		# For now, putting namespaces of Annotations, bodies, targets, contraints here in order to be used
-		# in import/export
-		#
-		OAC_NS =
-			FragSelector: 'http:#www.w3.org/ns/openannotation/core/FragmentSelector'
-			SVGConstraint: 'http:#www.w3.org/ns/openannotation/extensions/SvgSelector'
-
 		NS =
 			OA: "http://www.w3.org/ns/openannotation/core"
 			OAX: "http://www.w3.org/ns/openannotation/extensions"
 			RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 			CNT: "http://www.w3.org/2008/content#"
 			DC: "http://purl.org/dc/elements/1.1/"
+
+		parseNPT = (npt) ->
+			if npt.indexOf(':') == -1
+				seconds = parseFloat npt
+				minutes = 0
+				hours = 0
+			else
+				bits = ((parseFloat b) for b in npt.split(':'))
+				seconds = bits.pop()
+				if bits.length > 0
+					minutes = bits.pop()
+				else
+					minutes = 0
+				if bits.length > 0
+					hours = bits.pop()
+				else
+					hours = 0
+			(hours * 60 + minutes) * 60 + seconds

 		app.importData = (data) ->
 			# ingest data and put it into dataStore
 			tempstore = []
-			for i, o in data
+			for i, o of data
 				# Singling out the Annotations from the rest of the RDF data so
 				# we can work down from just the Annotation object and its pointers
-				if "#{NS.OA}Annotation" in o["#{NS.RDF}type"]
-
+				if "#{NS.OA}Annotation" in (t.value for t in o["#{NS.RDF}type"])
+					temp =
+						id: i
+						type: "Annotation"
+						bodyContent: ''
+						bodyType: 'Text'
+						targetURI: app.options.url
 					# Logic chain to determine what kind of incoming annotation we're dealing with
 					#
 					# Only interested in annotations that match our Video URI: are 'about' the video OR
 					# that do not yet have targets
+					if o["#{NS.OA}hasBody"]? and o["#{NS.OA}hasBody"][0]? and data[o["#{NS.OA}hasBody"][0].value]?
+						temp.bodyContent = data[o["#{NS.OA}hasBody"][0].value]["#{NS.CNT}chars"][0].value
 					if o["#{NS.OA}hasTarget"]?
-						for hasTarget in o["#{NS.OA}hasTarget"]
-							if data[hasTarget.value]? and data[hasTarget.value]["#{NS.OA}hasSource"]?
-								if app.options.url in (s.value for s in data[hasTarget.value]["#{NS.OA}hasSource"])
+						for hasTarget in (v.value for v in o["#{NS.OA}hasTarget"])
+							if data[hasTarget]? and data[hasTarget]["#{NS.OA}hasSource"]?
+								refd = (app.options.url in (s.value for s in data[hasTarget]["#{NS.OA}hasSource"]))
+								if refd
 									# Target source matches the URI of our video; generate an OAC dataStore model
 									# to insert into canvas for this annotation series in the JSON:RDF data

 									# Unique ID comes from the URI value of type
-									temp =
-										id: i
-										type: "Annotation"
-										bodyContent: ''
-										bodyType: 'Text'
-										targetURI: app.options.url
-										shapeType: ''
-										npt_start: 0
-										npt_end: 0

 									#
-									# Check to see if target is a CompoundSelection Resource
+									# Check to see if target is a CompositeSelector Resource
 									# Right now, we don't care about things that are not Compound Resources made
 									# up of a time fragment and an SVG Constraint
-									#
-									tuid = data[hasTarget.value]
-
-									for hasSelector in tuid["#{NS.OA}hasSelector"]
-										if data[hasSelector.value]? and "#{NS.OAX}CompoundSelector" in (t.value for t in data[hasSelector.value]["#{NS.RDF}type"])
-
-											suid = data[hasSelector.value]
-											svgid = data[suid.hasSelector[0].value]
-
-											# Fill in blanks for SVG
-											# **FIXME:** This needs to parse the chars using the XML parser, not the DOM node creator
-											temp.shapeType = $(svgid.chars[0].value)[0].nodeName
-											# correct shape-type nodeName to full name for DataStore
-											if temp.shapeType == 'RECT'
-												temp.shapeType = 'Rectangle'
-											else if temp.shapeType == 'ELLI'
-												temp.shapeType = 'Ellipse'
-
-											temp.x = parseInt $(svgid.chars[0].value).attr('x'),10
-											temp.y = parseInt $(svgid.chars[0].value).attr('y'),10
-											temp.w = parseInt $(svgid.chars[0].value).attr('width'),10
-											temp.h = parseInt $(svgid.chars[0].value).attr('height'),10
-
-											# Fill in blanks for the NPT constraint
-											nptid = data[suid.hasSelector[1].value]
-											npt = nptid.value[0].value.replace(/^t=npt:/g, '')
-											temp.npt_start = parseInt npt.replace(/\,[0-9]+/g, ''),10
-											temp.npt_end = parseInt npt.replace(/^[0-9]+\,/g, ''),10
-
-											# Fill in blanks for body
-											temp.bodyContent = data[o.hasBody[0].value].chars[0].value or ''
-											tempstore.push temp
-
+									#
+									for hasSelector in (v.value for v in data[hasTarget]["#{NS.OA}hasSelector"])
+										refd = ("#{NS.OAX}CompositeSelector" in (t.value for t in data[hasSelector]["#{NS.RDF}type"]))
+										if data[hasSelector]? and refd
+											for hasSubSelector in (v.value for v in data[hasSelector]["#{NS.OA}hasSelector"])
+												if data[hasSubSelector]?
+													types = (t.value for t in data[hasSubSelector]["#{NS.RDF}type"])
+													if "#{NS.OAX}SvgSelector" in types
+														# extract SVG stuff
+														if data[hasSubSelector]["#{NS.CNT}chars"]? and data[hasSubSelector]["#{NS.CNT}chars"][0]?
+															svg = data[hasSubSelector]["#{NS.CNT}chars"][0].value
+															dom = $.parseXML svg
+															# based on the root element, we interogate the shape info to see
+															# which one wants to handle extracting the extents/etc. from the svg
+															if dom?
+																doc = dom.documentElement
+																rootName = doc.nodeName
+																for t, info of shapeTypes
+																	if info.extractFromSVG? and rootName in info.rootSVGElement
+																		shapeInfo = info.extractFromSVG doc
+																		if shapeInfo?
+																			$.extend(temp, shapeInfo)
+																			temp.shapeType = t
+
+													if "#{NS.OA}FragSelector" in types
+														# extract media fragment stuff
+														if data[hasSubSelector]["#{NS.RDF}value"]? and data[hasSubSelector]["#{NS.RDF}value"][0]?
+															fragment = data[hasSubSelector]["#{NS.RDF}value"][0].value
+															fragment = fragment.replace(/^t=npt:/, '')
+															bits = fragment.split(',')
+															temp.npt_start = parseNPT bits[0]
+															temp.npt_end   = parseNPT bits[1]
 					else
 						#  No Target is created yet - create blank item to insert into dataStore
 						# Unique ID comes from the URI value of type
@@ -718,10 +725,8 @@
 							shapeType: ''
 							npt_start: 0
 							npt_end: 0
-
-						if o["#{NS.OA}hasBody"]?
-							temp.bodyContent = data[o["#{NS.OA}hasBody"][0].value]["#{NS.OA}chars"][0]

+					if temp.npt_start? or temp.npt_end? or temp.shapeType?
 						tempstore.push temp
 			# insert into dataStore
 			app.dataStore.canvas.loadItems tempstore
@@ -1012,9 +1017,15 @@
 					item = model.getItem itemId
 					"<rect x='#{item.x[0]}' y='#{item.y[0]}' width='#{item.w[0]}' height='#{item.h[0]}' />"

-				rootSVGElement: "rect"
+				rootSVGElement: ["rect"]

 				extractFromSVG: (svg) ->
+					info = {}
+					info.w = parseFloat svg.getAttribute('width')
+					info.h = parseFloat svg.getAttribute('height')
+					info.x = parseFloat svg.getAttribute('x')
+					info.y = parseFloat svg.getAttribute('y')
+					info

 				#
 				# Renders the rectangular constraint on the video target.
@@ -1106,10 +1117,15 @@
 					item = model.getItem itemId
 					"<elli x='#{item.x[0]}' y='#{item.y[0]}' width='#{item.w[0]}' height='#{item.h[0]}' />"

-				rootSVGElement: "elli"
+				rootSVGElement: ["elli"]

 				extractFromSVG: (svg) ->
-
+					info = {}
+					info.w = parseFloat svg.getAttribute('width')
+					info.h = parseFloat svg.getAttribute('height')
+					info.x = parseFloat svg.getAttribute('x')
+					info.y = parseFloat svg.getAttribute('y')
+					info
 				#
 				# Rendering Lens for the Ellipse SVG shape
 				#


Fix outdated documentation for Client#copy.

--- src/client.coffee
+++ src/client.coffee
@@ -1025,12 +1025,11 @@
   # Copies a file or folder in the user's Dropbox.
   #
   # This method's "from" parameter can be either a path or a copy reference
-  # obtained by a previous call to makeCopyRef. The method uses a crude
-  # heuristic to interpret the "from" string -- if it doesn't contain any
-  # slash (/) or dot (.) character, it is assumed to be a copy reference. The
-  # easiest way to work with it is to prepend "/" to every path passed to the
-  # method. The method will process paths that start with multiple /s
-  # correctly.
+  # obtained by a previous call to makeCopyRef.
+  #
+  # The method treats String arguments as paths and CopyReference instances as
+  # copy references. The CopyReference constructor can be used to get instances
+  # out of copy reference strings, or out of their JSON representations.
   #
   # @param {String, Dropbox.CopyReference} from the path of the file or folder
   #   that will be copied, or a Dropbox.CopyReference instance obtained by


Use existsSync instead of rolling our own

--- src/config.coffee
+++ src/config.coffee
@@ -1,12 +1,5 @@
 path = require 'path'
 fs = require 'fs'
-
-fileExistsAndIsAccessible = (file) ->
-  try
-    fs.statSync file
-    true
-  catch e
-    false

 module.exports =
   getHomeDirectory: ->
@@ -34,7 +27,7 @@
     !!process.platform.match(/^win/)

   isWindows64Bit: ->
-    fileExistsAndIsAccessible "C:\\Windows\\SysWow64\\Notepad.exe"
+    fs.existsSync "C:\\Windows\\SysWow64\\Notepad.exe"

   x86ProgramFilesDirectory: ->
     process.env["ProgramFiles(x86)"] || process.env["ProgramFiles"]
@@ -43,10 +36,10 @@
     return false unless isWin32()

     vsPath = path.join x86ProgramFilesDirectory(), "Microsoft Visual Studio 10.0", "Common7", "IDE", "devenv.exe"
-    fileExistsAndIsAccessible vsPath
+    fs.existsSync vsPath

   isVs2012Installed: ->
     return false unless isWin32()

     vsPath = path.join x86ProgramFilesDirectory(), "Microsoft Visual Studio 11.0", "Common7", "IDE", "devenv.exe"
-    fileExistsAndIsAccessible vsPath
+    fs.existsSync vsPath


Move layout after turtles

Move layout after turtles

--- src/config.coffee
+++ src/config.coffee
@@ -31,8 +31,8 @@
   }
   _CHILDREN: [
     game
+    turtles
     layout
-    turtles
     inspector
   ]
 }