abort pinger when it fails

abort pinger when it fails

--- lib/spawner.coffee
+++ lib/spawner.coffee
@@ -52,10 +52,17 @@
           rendezvous.write url.pathname.substring(1) + "\n"
         else
           console.log "invalid socket"
+
+      ping = setInterval (->
+        try
+          rendezvous.write " "
+        catch error
+          console.log "error writing to rendezvous"
+          clearInterval ping
+      ), 1000
+
       rendezvous.on "data", (data) -> emitter.emit("data", data) unless data.toString() is "rendezvous\r\n"
-      rendezvous.on "end",         -> emitter.emit "end"
-
-      setInterval (-> rendezvous.write " "), 1000
+      rendezvous.on "end",         -> emitter.emit "end"; clearInterval ping

     request.on "error", (error) ->
       emitter.emit "error", error


minor tweaks to the unreg implementation

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -366,10 +366,9 @@
         assert f = "disconnecting %s socket handle".toString()
         l = (socket) -> logger.warn f.magenta, socket.id.bold
         p = (c) -> l(c); c.emit "shutdown", -> c.disconnect()
-        remove = (c) -> c.removeAllListeners "connection"
-        contexts = try _.map [sserver, ssecure], resolve
-        _.each contexts, (context) => try remove context
+        assert contexts = _.map [sserver, ssecure], resolve
         _.each contexts, (context, vector, addition) =>
+            try context.removeAllListeners "connection"
             intern = "missing a client listing function"
             assert _.isFunction(context.clients), intern
             assert _.isArray clients = context.clients()


Check for the right ctx

Check for the right ctx

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -93,6 +93,7 @@
         sources = -> context.sources.push compile()
         compile = -> subject.remote?.compile? symbol
         invalid = "not a remote object and not a link"
+        assert _.isObject(context), "got invalid context"
         compilable = _.isFunction subject.remote?.compile
         return scripts() if _.isString subject
         return sources() if compilable


fix misnomer

fix misnomer

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -154,8 +154,8 @@
             target = _.head outstanding; h.unshift this
             assert left = h[_.indexOf(h, target) - 1]
             assert right = h[_.indexOf(h, target) + 1]
-            follows = (not left.watermark) or (left is @)
-            assert follows, "original: #{left.identify()}"
+            shadow = (not left.watermark) or (left is @)
+            assert shadow, "original: #{left.identify()}"
             left.rebased right; @refactoring trigger

     # Scan the supplied class and return an entire inheritance hierarchy


Failsafe for duplication

Failsafe for duplication

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -79,6 +79,10 @@
     enumerable: no, value: (compound) ->
         current = this.hierarchy()
         foreign = compound.hierarchy()
+        copied = compound in current
+        identity = compound.name or compound.nick
+        duplicate = "Duplicate #{identity} compound"
+        throw new Error duplicate if copied
         orphan = (shape) -> shape not in commons
         commons = _.intersection current, foreign
         orphans = "No common base classes in hierarchy"


Fixed cut deep links again

Fixed cut deep links again

--- coffee/state.coffee
+++ coffee/state.coffee
@@ -7,8 +7,8 @@
 rnd = -> Date.now().toString(16) + pad (Math.random()*65536|0).toString(16), 4

 deserialize = ->
-  [type, id] = window.location.hash.substr(1).split '/'
-  { type, id }
+  [type, id...] = window.location.hash.substr(1).split '/'
+  { type, id:id.join '/' }
 serialize = (data) ->
   window.location.hash = '#'+data.type+(if data.id then '/'+data.id else '')


Adjust example js to not eat code examples

--- source/javascripts/examples.js.coffee
+++ source/javascripts/examples.js.coffee
@@ -1,3 +1,9 @@
 $ ->

-  $(document.body).stagehand()+  # this is a complicated bit to get Stagehand to ignore the example code blocks.
+  # normally you'd just say $(document.body).stagehand()
+  # not sure this needs to be supported by the actual plugin, so ...
+
+  $body = $(document.body)
+  $code = $body.find('pre code').remove()
+  $body.stagehand().find('pre').append($code)


doc: remove command specific options from root optimist parser

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -36,10 +36,6 @@
   options.usage(usage)
   options.alias('v', 'version').describe('version', 'Print the apm version')
   options.alias('h', 'help').describe('help', 'Print this usage message')
-  options.alias('a', 'all').boolean('all')
-  options.boolean('hard')
-  options.boolean('force')
-  options.string('tag')
   options.command = options.argv._[0]
   for arg, index in args when arg is options.command
     options.commandArgs = args[index+1..]


Improve console message for popup notification

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -54,7 +54,7 @@
   @log("Application: %o", App)

   @_popupMessage: (ntype, etype, icon, message, title) ->
-    this[etype]("Popup message: #{JSON.stringify({message: message, title: title})}")
+    this[etype]("Popup {message: %o, title: %o}", message, title)
     return new Notifier({
       icon: if icon? then "glyphicon glyphicon-#{icon}-sign" else undefined
       title: title


using 1980/05/22 as default start date - it is Pac-Man's release date in Japan :)

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -21,7 +21,10 @@
 .constant 'HOAY.CONSTANTS',
   LANG_DE_DE: 'de-DE'
   LANG_EN_US: 'en-US'
-  START_DATE_DEFAULT: new Date 1971, 9, 10
+  # Do you know what 1980/05/22 is about?
+  # It is the release date of PAC-MAN in Japan :)
+  # @see: http://history1900s.about.com/od/1980s/qt/Pac-Man.htm
+  START_DATE_DEFAULT: new Date 1980, 4, 22
   END_DATE_DEFAULT: new Date()

 # configurations


Fix spelling of "arguments" in Ruby page.

--- src/parser/ruby-parser.coffee
+++ src/parser/ruby-parser.coffee
@@ -247,7 +247,7 @@
   defNoArgs: (line, convention, commitUrl) ->
     convention = {lang: this.lang} unless convention
     (convention.defNoArgs =
-      title: "Omit parentheses when there aren't any arguemnts"
+      title: "Omit parentheses when there aren't any arguments"
       column: [
         {
           key: "omit", display: "Omit",
@@ -284,7 +284,7 @@
   defArgs: (line, convention, commitUrl) ->
     convention = {lang: this.lang} unless convention
     (convention.defArgs =
-      title: "Parentheses around arguemnts in def"
+      title: "Parentheses around arguments in def"
       column: [
         {
           key: "omit", display: "Omit",


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -180,10 +180,9 @@
   states = StateCollection()
   callbacks = CallbackCollection()

-  this.object       = object;
-  this.machine_name = name;
-
-  this.internal_state = options && options.initial ? options.initial : '';
+  machine_name = name
+
+  internal_state = options && (if options.initial then options.initial else '')
   this._add_methods_to_object(name, object);

   if(block){ block(this); }


Syncified some augmentation methods

Syncified some augmentation methods

--- src/augment.coffee
+++ src/augment.coffee
@@ -5,8 +5,8 @@
   di2 = util.clone(di)

   di2.registerVar = (id, value) ->
-    di.registerModule id, [], ({}, callback) ->
-      callback(null, value)
+    di.registerModule id, [], ->
+      value

   di2.registerVars = (objs) ->
     Object.keys(objs).forEach (name) ->
@@ -14,12 +14,12 @@

   di2.registerRequire = (id, value) ->
     value = id if !value?
-    di.registerModule id, [], ({}, callback) ->
-      callback(null, require(value))
+    di.registerModule id, [], ->
+      require(value)

   di2.registerAlias = (id, aliased) ->
-    di.registerModule id, [aliased], (obj, callback) ->
-      callback(null, obj[aliased])
+    di.registerModule id, [aliased], (obj) ->
+      obj[aliased]

   di2.registerFile = (id, filename) ->
     file = require(filename)


Don't bundle .idea folders

Don't bundle .idea folders

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -93,6 +93,7 @@
       '.pairs'
       '.travis.yml'
       'appveyor.yml'
+      '.idea'
     ]

     packageNames.forEach (packageName) -> ignoredPaths.push(path.join(packageName, 'spec'))


Fix context-menu specs

Fix context-menu specs

--- spec/context-menu-manager-spec.coffee
+++ spec/context-menu-manager-spec.coffee
@@ -15,7 +15,7 @@
           'label': 'command'

       expect(contextMenu.definitions['.selector'][0].label).toEqual 'label'
-      expect(contextMenu.definitions['.selector'][0].commandOrSubmenu).toEqual 'command'
+      expect(contextMenu.definitions['.selector'][0].command).toEqual 'command'

     it "loads submenus", ->
       contextMenu.add 'file-path',
@@ -33,7 +33,7 @@
         , devMode: true

         expect(contextMenu.devModeDefinitions['.selector'][0].label).toEqual 'label'
-        expect(contextMenu.devModeDefinitions['.selector'][0].commandOrSubmenu).toEqual 'command'
+        expect(contextMenu.devModeDefinitions['.selector'][0].command).toEqual 'command'

   describe "building a menu template", ->
     beforeEach ->


cross tabulate

cross tabulate

--- coffee/operations/reverse_cross_tabulate.coffee
+++ coffee/operations/reverse_cross_tabulate.coffee
@@ -6,6 +6,7 @@

    @initialize ->
     @elems.inputs = {}
+    @columns = {}

    operationName: 'Reverse Cross Tabulate'
    @operationName: 'Reverse Cross Tabulate'
@@ -50,7 +51,7 @@
    refresh: ->
     n = 0
     n++ for c of @columns
-    if n > 0
+    if n > 1
      @elems.btn.style.display = 'block'
     else
      @elems.btn.style.display = 'none'
@@ -62,14 +63,35 @@
     @table.refresh()

    apply: ->
+    n = 0
+    for col, i in @table.columns when @columns[i]
+     id = col.id
+     n++
+    id = "#{id}_0"
+
+    data = {}
+    for col, i in @table.columns when not @columns[i]
+     data[col.id] = []
+     for d in @table.data[col.id]
+      for j in [0...n]
+       data[col.id].push d
+
+    @table.size *= n
+
+    data[id] = new Array @table.size
+    m = 0
+    for col, i in @table.columns when @columns[i]
+     for d, j in @table.data[col.id]
+      data[id][m + j * n] = d
+     m++
+
     columns = []
     for col, i in @table.columns
-     if @columns[i]
-      delete @table.data[col.id]
-     else
+     if not @columns[i]
       columns.push col

     @table.columns = columns
+    @table.data = data

   OPERATIONS.set ReverseCrossTabulate.type, ReverseCrossTabulate


* [bug] typo cleanInterval.

* [bug] typo cleanInterval.

--- src/cache.coffee
+++ src/cache.coffee
@@ -33,7 +33,7 @@
     hasLRU: hasLRU= (id)->hasOwnProperty.call(_cacheLRU, id) and not isExpired(id)
     has: (id) ->hasOwnProperty.call(_cache, id) or hasLRU(id)
     deleteLRU: (id)->
-      setImmediate clearExpires if cleanInterval > 0 and Date.now() - lastCleanTime >= clearExpires
+      setImmediate clearExpires if cleanInterval > 0 and Date.now() - lastCleanTime >= cleanInterval
       if hasOwnProperty.call(_cacheLRU, id)
         result = _cacheLRU[id]
         delete _cacheLRU[id]
@@ -66,7 +66,7 @@
       if hasOwnProperty.call(_cache, id)
         _cache[id]
     isExpired: isExpired = (id)->
-      if cleanInterval > 0 and Date.now() - lastCleanTime >= clearExpires
+      if cleanInterval > 0 and Date.now() - lastCleanTime >= cleanInterval
         setImmediate clearExpires
       else
         expires = _cacheExpired[id]
@@ -108,7 +108,7 @@
       _cache[id] = value
       cache.emit(event, id, value, oldValue)
     setLRU: setLRU = (id, value, expires)->
-      setImmediate clearExpires if cleanInterval > 0 and Date.now() - lastCleanTime >= clearExpires
+      setImmediate clearExpires if cleanInterval > 0 and Date.now() - lastCleanTime >= cleanInterval
       if hasOwnProperty.call(_cacheLRU, id)
         oldValue = _cacheLRU[id]
         if expires <= 0
@@ -197,7 +197,7 @@
         callback.call thisArg, v, k, cache
       return
     forEachLRU: forEachLRU=(callback, thisArg)->
-      setImmediate clearExpires if cleanInterval > 0 and Date.now() - lastCleanTime >= clearExpires
+      setImmediate clearExpires if cleanInterval > 0 and Date.now() - lastCleanTime >= cleanInterval
       for k,v of _cacheLRU
         callback.call thisArg, v, k, cache if not isExpired k
       return


session is readonly until we receive AtInputPrompt

--- client/session.coffee
+++ client/session.coffee
@@ -19,11 +19,7 @@
     return

   cm = CodeMirror ($e = $ e)[0],
-    autofocus: true
-    mode: ''
-    matchBrackets: true
-    autoCloseBrackets: true
-    keyMap: 'dyalog'
+    autofocus: true, mode: '', matchBrackets: true, autoCloseBrackets: true, keyMap: 'dyalog', readOnly: true
     extraKeys:
       Tab: -> c = cm.getCursor(); opts.autocomplete? cm.getLine(c.line), c.ch
       'Shift-Enter': -> c = cm.getCursor(); opts.edit?(cm.getLine(c.line), c.ch)


Make start and stop time observable. Correctly use stop_time rather than end_time, so the isRunning calculation works.

--- js/inner.coffee
+++ js/inner.coffee
@@ -108,8 +108,6 @@

     @project_path = @komp =>
       "/gh/#{@project_name()}"
-
-

 class ActionLog extends Obj
   observables: =>
@@ -218,7 +216,10 @@
       committer_date: null
       author_name: null
       author_email: null
-      author_date: null,
+      author_date: null
+      start_time: null
+      stop_time: null,
+
       ["build_num"
        "status"
        "committer_name"
@@ -228,7 +229,6 @@
        "job_name"
        "branch"
        "vcs_revision"
-       "start_time"
        "build_time_millis"]

     @url = @komp =>
@@ -297,11 +297,11 @@
             "unknown"

     @pretty_start_time = @komp =>
-      if @start_time
-        Circle.time.as_time_since(@start_time)
+      if @start_time()
+        Circle.time.as_time_since(@start_time())

     @duration = @komp () =>
-      if @start_time
+      if @start_time()
         Circle.time.as_duration(@build_time_millis)

     @branch_in_words = @komp =>
@@ -332,7 +332,7 @@
     @maybeSubscribe()

   isRunning: () =>
-    @start_time and not @end_time()
+    @start_time() and not @stop_time()

   maybeSubscribe: () =>
     if @isRunning()


Fix broken URL and structure for leaderboards

Looks like the Hubot package may have become out-of-date with the latest API release.

- Fixes the path to the leaderboard
- Steps down into the `result` property of the returned JSON
- Adds an extra feature to allow company to optionally hide the amounts

--- src/bonusly.coffee
+++ src/bonusly.coffee
@@ -5,7 +5,8 @@
 #   None
 #
 # Configuration:
-#   HUBOT_BONUSLY_ADMIN_API_TOKEN
+#   HUBOT_BONUSLY_ADMIN_API_TOKEN - Obtain via https://bonus.ly/api
+#   HUBOT_BONUSLY_HIDE_AMOUNTS - (optional) If set, hide amounts
 #
 # Commands:
 #   hubot bonusly give <amount> to <name|email> for <reason> <#hashtag> - gives a micro-bonus to the specified user
@@ -36,7 +37,10 @@
           when 200
             data = JSON.parse body
             bonuses = data.result
-            bonuses_text = ("From #{bonus.giver.short_name} to #{bonus.receiver.short_name}: #{bonus.amount_with_currency} #{bonus.reason}" for bonus in bonuses).join('\n')
+            if process.env.HUBOT_BONUSLY_HIDE_AMOUNTS? && process.env.HUBOT_BONUSLY_HIDE_AMOUNTS == '1'
+              bonuses_text = ("From #{bonus.giver.short_name} to #{bonus.receiver.short_name} #{bonus.reason}" for bonus in bonuses).join('\n')
+            else
+              bonuses_text = ("From #{bonus.giver.short_name} to #{bonus.receiver.short_name}: #{bonus.amount_with_currency} #{bonus.reason}" for bonus in bonuses).join('\n')
             msg.send bonuses_text
           when 400
             data = JSON.parse body
@@ -48,14 +52,14 @@
     return msg.send bad_token_message unless token
     type_str = msg.match[2]
     type = if (type_str? && type_str == 'giver') then 'giver' else 'receiver'
-    path="/api/v1/leaderboards/count-#{type}?access_token=#{token}&limit=10"
+    path="/api/v1/analytics/standouts?access_token=#{token}&role=#{type}&limit=10"
     msg.send "o.k. I'll pull up the top #{type}s for you ..."
     msg.http(service)
       .path(path)
       .get() (err, res, body) ->
         switch res.statusCode
           when 200
-            leaders = JSON.parse body
+            leaders = JSON.parse(body).result
             leaders_text = ("##{index+1} with #{leader.count} bonuses: #{leader.user.first_name} #{leader.user.last_name}" for leader, index in leaders).join('\n')
             msg.send leaders_text
           when 400


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -104,7 +104,7 @@
     parsedColor =
       switch args.length
         when 0
-          [0, 0, 0, 1]
+          [0, 0, 0, 0]
         when 1
           channelize(args.first())
         when 2


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -441,6 +441,15 @@
     ###*
     A getter / setter for the hue value of the color. Passing no argument returns the
     current hue value. Passing a value will set the hue to that value and return the color.
+
+

+    magenta = Color(255, 0, 255)
+
+    yellow = color.hue(60)
+
+    yellow.toString()
+    # => 'rgba(255, 255, 0, 1)'
+

     @name hue
     @methodOf Color#


provide default port when connecting

provide default port when connecting

--- client/welcome.coffee
+++ client/welcome.coffee
@@ -23,7 +23,7 @@
       if $('.connect-add').is(':checked') && getFavs().map(fmtFav).indexOf(fmtFav x) < 0
         localStorage.favs = JSON.stringify getFavs().concat([x]); renderFavs()
       $('.connect-name').val ''
-      Dyalog.socket.emit 'connectToInterpreter', host: x.host, port: x.port
+      Dyalog.socket.emit 'connectToInterpreter', host: x.host, port: x.port or DEFAULT_PORT
       renderFavs()
       $f = $('.fav-addr').filter(-> $(@).text() == fmtFav x).closest '.fav'
       w = $f.width()


add app_id saving in variable

add app_id saving in variable

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -9,6 +9,7 @@
   active_thumb = 0
   timer = null
   full_width = 0
+  app_id = ''

   stopTimer = ->
     console.log 'get in Stop timer()'
@@ -106,10 +107,8 @@
         $('#photo').show()
         id = curr_photo.res_url.match(/\d+$/)[0]

-        if $('#vk_like').children().length != 0
-          console.log $('#vk_like').children()
-          $('#vk_like').children().remove()
-        VK.Widgets.Like("vk_like", {type: "mini", height: 20}, id);
+        $('#vk_like').empty()
+        VK.Widgets.Like("vk_like", {type: "mini", height: 20, pageTitle: "Угадай страну: " + curr_photo.country, pageUrl: 'https://vk.com/app' + app_id}, id);

         $('#photo_url').prop 'href', curr_photo.res_url
         #center the image
@@ -171,6 +170,8 @@
     get_new_photos()

   VK.init (data) ->
+    app_id = data.api_id
+    console.log app_id
     VK.api 'storage.get', {key: 'score'}, (data) ->
       if data.response
         if data.response == ''


link indicator only shown on links opening in new windows

--- client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemStatusUpdate.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ListItems/ActivityListItemStatusUpdate.coffee
@@ -29,7 +29,8 @@
       unless $(element).text() is href
         $(element).twipsy twOptions(href)

-      $(element).prepend """"""
+      if $(element).attr("target") is "_blank"
+       $(element).prepend """"""
       element

     # @$("a").hover (event)=>


Fixed next()

Fixed next()

--- src/cheerio.coffee
+++ src/cheerio.coffee
@@ -100,8 +100,8 @@
       return selector null

   selector::next = () ->
-    if elem.next
-      return selector elem.next
+    if @context[0].next
+      return selector @context[0].next
     else
       return selector null


Use the same tooltipRemove method

Use the same tooltipRemove method

--- core/app/backbone/factlink/tooltip_mixin.coffee
+++ core/app/backbone/factlink/tooltip_mixin.coffee
@@ -29,10 +29,14 @@
     @on 'render', @tooltipBindAll
     @on 'close', @tooltipResetAll

-  tooltipRemove: (selector) ->
+  tooltipRemove: (selector, fade=true) ->
     tooltip = @_tooltips[selector]
     if tooltip?
-      tooltip.positionedRegion.resetFade()
+      if fade
+        tooltip.positionedRegion.resetFade()
+      else
+        tooltip.positionedRegion.reset()
+
       delete @_tooltips[selector]

   tooltipBindAll: ->
@@ -48,6 +52,6 @@

   tooltipResetAll: ->
     for selector, tooltip of @_tooltips
-      tooltip.positionedRegion.reset()
+      @tooltipRemove(selector, false)

   tooltip: (selector) -> @_tooltips[selector]


fixing coffee -s ... (jannehietamaki)

fixing coffee -s ... (jannehietamaki)

--- src/command.coffee
+++ src/command.coffee
@@ -126,7 +126,7 @@
   stdin.on 'data', (buffer) ->
     code += buffer.toString() if buffer
   stdin.on 'end', ->
-    compileScript 'stdio', code
+    compileScript null, code

 # Watch a source CoffeeScript file using `fs.watchFile`, recompiling it every
 # time the file is updated. May be used in combination with other options,


Edited src/command.coffee via GitHub

Edited src/command.coffee via GitHub

--- src/command.coffee
+++ src/command.coffee
@@ -30,7 +30,7 @@
   ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
   ['-o', '--output [DIR]',    'set the directory for compiled JavaScript']
   ['-j', '--join [FILE]',     'concatenate the scripts before compiling']
-  ['-w', '--watch',           'watch scripts and repeat enabled tasks for any that change']
+  ['-w', '--watch',           'watch scripts for changes and rerun commands']
   ['-p', '--print',           'print the compiled JavaScript to stdout']
   ['-l', '--lint',            'pipe the compiled JavaScript through JavaScript Lint']
   ['-s', '--stdio',           'listen for and compile scripts over stdio']


Show plugin description in grey when using --plugins

--- src/command.coffee
+++ src/command.coffee
@@ -142,7 +142,7 @@

     if argv.plugins
         for plugin in nesh.plugins
-            nesh.log.info "#{plugin.name}: #{plugin.description}"
+            nesh.log.info "#{plugin.name}: " + "#{plugin.description}".grey
         return

     nesh.start opts, (err) ->


Add some filters to search endpoint.

Add some filters to search endpoint.

--- src/control.coffee
+++ src/control.coffee
@@ -256,7 +256,7 @@
             @services.set []
             @addService service

-        _search: (query) ->
+        _search: (query, filters) ->
             @_clearRadius()
             @selectedPosition.clear()
             @clearUnits all: true
@@ -284,13 +284,15 @@
                             @units.trigger 'finished'
                             @services.set []
                             deferred.resolve()
+                if filters? and _.size(filters) > 0
+                    opts.data = filters
                 opts = @searchResults.search query, opts

-        search: (query) ->
+        search: (query, filters) ->
             unless query?
                 query = @searchResults.query
             if query? and query.length > 0
-                @_search query
+                @_search query, filters
             else
                 sm.resolveImmediately()

@@ -423,7 +425,12 @@
         renderSearch: (path, opts) ->
             unless opts.query?.q?
                 return
-            @search opts.query.q
+            filters = {}
+            for filter in ['municipality', 'service']
+                value = opts.query?[filter]
+                if value?
+                    filters[filter] = value
+            @search opts.query.q, filters

         _matchResourceUrl: (path) ->
             match = path.match /^([0-9]+)/


Actually fix emit warning.

Actually fix emit warning.

--- compiler/index.coffee
+++ compiler/index.coffee
@@ -117,7 +117,7 @@
                 SDKError.warn('files', 'Projects SHOULD have a /index.html')

             num_indexed = Object.keys(_emitFile.files_emitted_indexed).length
-            num_emitted = _emitFile.files_emitted_indexed.length
+            num_emitted = _emitFile.files_emitted.length
             if num_indexed isnt num_emitted
                 SDKError.warn('files', "#{ num_emitted - num_indexed } too many emits. Check for multiple emits of the same file.")
             onCompile?(_emitFile.files_emitted, compileAssets.files_emitted, project_package, project_config)


change to use visibility to hide until mounted

--- src/element.coffee
+++ src/element.coffee
@@ -32,7 +32,7 @@
       @childRoot = @shadowRoot
     else
       @childRoot = document.createElement('_root_')
-      @childRoot.style.display = 'none'
+      @childRoot.style.visibility = 'hidden'
       #disconnect childnodes
       fragment = document.createDocumentFragment()
       fragment.appendChild(node) while node = @firstChild
@@ -45,7 +45,7 @@
     @propertyChange = @__component.onPropertyChange
     observer = new MutationObserver(Utils.debounce 10, =>
       return if @__released
-      @childRoot.style.display = '' unless Utils.useShadowDOM
+      @childRoot.style.visibility = '' unless Utils.useShadowDOM
       observer.disconnect()
       @__component.onMounted?(@)
     )


better implementation of operations

better implementation of operations

--- v1meta.coffee
+++ v1meta.coffee
@@ -44,6 +44,13 @@
             throw "Properties may only be set on assets having a _v1_transaction"
         @_v1_new_data[attr] = value
         @_v1_transaction.add_to_dirty(@)
+
+    _v1_execute_operation: (opname, callback) =>
+        @_v1_v1meta.server.execute_operation
+            asset_type_name: @_v1_asset_type_name
+            opname: opname
+            id: @_v1_id
+            callback: callback

     toString: () ->
         current = asset_dict_filter(@_v1_current_data)
@@ -103,8 +110,8 @@
             xml.iter 'Operation', (operation) =>
                 opname = operation.get('name')
                 modelClass::_v1_ops.push(opname)
-                modelClass.prototype[opname] = () =>
-                    @_v1_execute_operation(opname)
+                modelClass.prototype[opname] = (callback) =>
+                    @_v1_execute_operation(opname, callback)

             xml.iter 'AttributeDefinition', (attribute) =>
                 attr = attribute.get('name')


Handle edge case in debugger.

Handle edge case in debugger.

--- src/debug.coffee
+++ src/debug.coffee
@@ -30,7 +30,7 @@
                     data = new STATEFUL_EVENT
                     data.variable = variableName
                     data.event = eventName
-                    data.target = target?.toJSON()
+                    data.target = target?.toJSON?() or target
                     for param, i in rest
                         data["param_#{i+1}"] = param
                     console.log data


human error

human error

--- src/error.coffee
+++ src/error.coffee
@@ -1,4 +1,4 @@
-module.exports = class KiteError extends KiteError
+module.exports = class KiteError extends Error
   constructor: (message) ->
     Error.call this, message
     @name = "KiteError"


Add tags to the user model

Add tags to the user model

--- models/user.coffee
+++ models/user.coffee
@@ -35,7 +35,8 @@
   created_at: Date
   updated_at: Date
   organization: Schema.ObjectId
-  badges: [Badge.schema]
+  badges: [EarnedBadgeSchema]
+  tags: [String]

 UserSchema.plugin(timestamps)

@@ -83,12 +84,17 @@

 User = db.model 'User', UserSchema

-User.findOrCreate = (username, issuer_id, callback)->
-  User.find(username: username, organization: issuer_id).limit(1).exec (err, user)->
-    if user.length > 0
-      callback(null, user[0])
+User.findOrCreate = (username, options, callback)->
+  issuer_id = options.issuer_id
+  tags = options.tags
+  User.findOne(username: username, organization: issuer_id).limit(1).exec (err, user)->
+    if user?
+      user.tags.merge tags
+      user.save()
+      callback(null, user)
     else
       user = new User username: username, organization: issuer_id
+      user.tags.merge tags if tags?
       user.save (err)->
         if err
           callback(err)


atom: update config

atom: update config

--- .atom/config.cson
+++ .atom/config.cson
@@ -41,6 +41,7 @@
     useAlternateScoring: true
   "git-blame": {}
   "git-blame-plus": {}
+  hex: {}
   jumpy: {}
   linter:
     clearOnChange: true


[atom] show linter panel at bottom

[atom] show linter panel at bottom

--- .atom/config.cson
+++ .atom/config.cson
@@ -12,7 +12,8 @@
     userId: "424d8951-d72d-4084-ad4e-00c911987c60"
   "file-icons":
     coloured: false
-  "linter-ui-default": {}
+  "linter-ui-default":
+    showPanel: true
   "tree-view":
     hideIgnoredNames: true
     squashDirectoryNames: true


Added fetchQuery method to ActivityAppController.

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -169,6 +169,25 @@
         cache.overview.reverse()  if cache?.overview
         callback null, cache

+  fetchQuery: (options, callback) ->
+    selector    =
+      type      : { $in : [options.type or= 'CNewMemberBucketActivity'] }
+      createdAt :
+        $gt     : options.from
+        $lt     : options.to
+
+    options     =
+      limit     : options.limit
+
+    KD.remote.api.CActivity.some selector, options, (err, activities)=>
+      return if err or activities.length is 0
+
+      activities = clearQuotes activities
+      KD.remote.reviveFromSnapshots activities, (err, teasers)=>
+        return warn err if err
+
+        callback? teasers, activities
+
   continueLoadingTeasers:->

     unless isLoading


Removed WIP code

Removed WIP code

--- client/landing/app/Applications/Environments.kdapplication/views/VMs.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/VMs.coffee
@@ -16,7 +16,7 @@
     @vmListView = @vmListController.getView()

     @on "Clicked", (item)=>
-      @graphView.update item
+      @graphView.update? item
       @vmListController.deselectAllItems()
       @vmListController.selectSingleItem item

@@ -27,8 +27,6 @@
         kiteName  : 'os',
         vmName    : item.data.name,
         method    : cmd
-      , ->
-        console.log arguments

     @graphView = new VMDetailView
     @splitView = new KDSplitView
@@ -88,21 +86,6 @@
 class VMDetailView extends KDView
   constructor: (options, data) ->
     super options, data
-    @addSubView @netView = new KDCustomHTMLView
-      tagName: 'img'
-      attributes:
-        src: "http://chart.googleapis.com/chart?chxl=0:|%2B0.50%25|%2B0.25%25|0.00%25|-0.25%25&chxt=r&chs=400x200&cht=ls&chco=DA3B15,F7A10A,4582E7&chd=s:abdegedbageba,somedabelprmnr,PRTSPNQYWROQTXYRQ_&chls=1|1|1"
-
-    @addSubView new KDCustomHTMLView
-      tagName: 'br'
-
-    @addSubView @cpuView = new KDCustomHTMLView
-      tagName: 'img'
-      attributes:
-        src: "http://chart.googleapis.com/chart?chs=150x100&cht=gm&chco=000000,00FF00|FFCC33|FF0000&chd=t:70&chma=|0,4&chtt=CPU+Utilization&chts=676767,14"
-
-  update:(item)->
-    console.log item

 class VMListItemView extends KDListItemView
   constructor: (options, data) ->


kodingAppsController requires manifest instead of name, so we need to fetch manifest before request to run

--- client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -394,8 +394,11 @@

     folder = nodeView.getData()
     folder.emit "fs.run.started"
-    name = FSHelper.trimExtension folder.path
-    @getSingleton('kodingAppsController').runApp name, =>
+    kodingAppsController = @getSingleton('kodingAppsController')
+
+    manifest = KodingAppsController.getManifestFromPath folder.path
+
+    kodingAppsController.runApp manifest, =>
       folder.emit "fs.run.finished"
       callback?()


Update regex to match multiple variations of the stack.

https://regex101.com/r/yT2dT4/1


fix: constructor (no `new` keyword)

fix: constructor (no `new` keyword)

--- src/index.coffee
+++ src/index.coffee
@@ -110,8 +110,11 @@
   _send: (req, res) ->
     onRequest.call this, req, res

-App.Layer = Layer
-module.exports = App
+slush = (opts) ->
+  new App opts
+
+slush.Layer = Layer
+module.exports = slush

 #
 # Helpers


add strict:true to restify options

add strict:true to restify options

--- src/index.coffee
+++ src/index.coffee
@@ -39,8 +39,12 @@
 app.use express.json()

 app.configure () ->
-  restify.serve app, models.User, lowercase: true
-  restify.serve app, models.City, lowercase: true
+  restify.serve app, models.User, restifyOptions
+  restify.serve app, models.City, restifyOptions
+
+restifyOptions =
+  lowercase: true,
+  strict: true

 routes = require './routes'
 routes(app)


All parameters are required

All parameters are required

--- src/index.coffee
+++ src/index.coffee
@@ -137,59 +137,27 @@
         callback(false)

   following: (id, options, scope, callback) ->
-    if !callback?
-      callback = options
-      options = {}
-
     @members("#{@namespace}:#{@followingKey}:#{scope}:#{id}", options, callback)

   followers: (id, options, scope, callback) ->
-    if !callback?
-      callback = options
-      options = {}
-
     @members("#{@namespace}:#{@followersKey}:#{scope}:#{id}", options, callback)

   blocked: (id, options, scope, callback) ->
-    if !callback?
-      callback = options
-      options = {}
-
     @members("#{@namespace}:#{@blockedKey}:#{scope}:#{id}", options, callback)

   reciprocated: (id, options, scope, callback) ->
-    if !callback?
-      callback = options
-      options = {}
-
     @members("#{@namespace}:#{@reciprocatedKey}:#{scope}:#{id}", options, callback)

   followingPageCount: (id, pageSize, scope, callback) ->
-    if !callback?
-      callback = pageSize
-      pageSize = @pageSize
-
     @totalPages("#{@namespace}:#{@followingKey}:#{scope}:#{id}", pageSize, callback)

   followersPageCount: (id, pageSize, scope, callback) ->
-    if !callback?
-      callback = pageSize
-      pageSize = @pageSize
-
     @totalPages("#{@namespace}:#{@followersKey}:#{scope}:#{id}", pageSize, callback)

   blockedPageCount: (id, pageSize, scope, callback) ->
-    if !callback?
-      callback = pageSize
-      pageSize = @pageSize
-
     @totalPages("#{@namespace}:#{@blockedKey}:#{scope}:#{id}", pageSize, callback)

   reciprocatedPageCount: (id, pageSize, scope, callback) ->
-    if !callback?
-      callback = pageSize
-      pageSize = @pageSize
-
     @totalPages("#{@namespace}:#{@reciprocatedKey}:#{scope}:#{id}", pageSize, callback)

   defaultOptions: ->


extract port of first node if not in configuration

--- src/index.coffee
+++ src/index.coffee
@@ -87,7 +87,7 @@
     cassandraConfig = config.cassandra
     if cassandraConfig.datacenterName?
       defaultPolicy = new cassandra.policies.loadBalancing.DCAwareRoundRobinPolicy(cassandraConfig.datacenterName)
-      nodeWhiteList = [ cassandraConfig.contactPoints[0] ]
+      nodeWhiteList = [ firstNodeWithPort(cassandraConfig) ]
       whiteListPolicy = new cassandra.policies.loadBalancing.WhiteListPolicy(defaultPolicy, nodeWhiteList)
       cassandraConfig.policies = {
         loadBalancing : whiteListPolicy
@@ -103,6 +103,13 @@
   catch error
     d.reject new Error("Error creating Cassandra client: #{error}", error)
   d.promise
+
+firstNodeWithPort = (cassandraConfig) ->
+  firstNode = cassandraConfig.contactPoints[0]
+  if firstNode.match(/[^\:]+:[0-9]+/)
+    firstNode
+  else
+    "#{firstNode}:#{cassandraConfig.protocolOptions.port}"

 # Create a the schema_version table in the keyspace if it does not yet exist
 createVersionTable = (config, client, keyspace) ->


Only call `measure` in __DEV__ mode

Only call `measure` in __DEV__ mode

--- src/index.coffee
+++ src/index.coffee
@@ -166,17 +166,20 @@
   @emit "response", req
   measure req, res

-measure = (req, res) ->
-  return unless __DEV__
-  log.moat 0
-  status = res.statusCode
-  if status is 200
-  then log.green status + " "
-  else log.red status + " "
-  log.white req.method + " " + req.path + " "
-  log.gray (now() - req.timestamp).toFixed(3) + "ms"
-  log.moat 0
-  return
+# Only measure response times during development.
+measure = Function.prototype
+if __DEV__
+  measure = (req, res) ->
+    {elapsedTime} = req
+    log.moat 0
+    status = res.statusCode
+    if status is 200
+    then log.green status + " "
+    else log.red status + " "
+    log.white req.method + " " + req.path + " "
+    log.gray elapsedTime + "ms"
+    log.moat 0
+    return

 # Attached to the request object.
 handle404 = ->


getting to work

getting to work

--- src/index.coffee
+++ src/index.coffee
@@ -64,33 +64,34 @@
 ##
 # Generate a random avaiable path on the S3 bucket
 ##
-Upload.prototype._getDestPath = (prefix, callback) ->
+Upload.prototype._getDestPath = (prefix, filename, callback) ->
   retry 5, (cb) =>
     path = prefix + @_getRandomPath() if @opts.generateRandomPath is true
-    path = prefix + @opts.absolutePath if @opts.generateRandomPath is false
+    path = prefix + @opts.absolutePath + filename if @opts.generateRandomPath is false
     @s3.listObjects Prefix: path, (err, data) ->
       return cb err if err
-      return cb null, path if data.Contents.length is 0
-      return cb new Error "Path #{path} not avaiable"
+      return cb null, path
+      #return cb null, path if data.Contents.length is 0
+      #return cb new Error "Path #{path} not avaiable"
   , callback

 ##
 # Upload a new image to the S3 bucket
 ##
-Upload.prototype.upload = (src, opts, cb) ->
-  image = new Image src, opts, @
+Upload.prototype.upload = (src, filename, opts, cb) ->
+  image = new Image src, filename, opts, @
   image.start(cb)

 ##
 # Image upload
 ##
-Image = module.exports.Image = (@src, @opts, @upload) ->
+Image = module.exports.Image = (@src, @filename, @opts, @upload) ->
   @

 Image.prototype.start = (cb) ->
   auto
     metadata: @getMetadata.bind(@, @src)
-    dest: @getDest.bind(@)
+    dest: @getDest.bind(@, @filename)
     versions: ['metadata', @resizeVersions.bind(@)]
     uploads: ['versions', 'dest', @uploadVersions.bind(@)]
     cleanup: ['uploads', @removeVersions.bind(@)]
@@ -106,9 +107,9 @@
 ##
 # Get image destination
 ##
-Image.prototype.getDest = (cb) ->
+Image.prototype.getDest = (filename, cb) ->
   prefix = @opts?.awsPath or @upload.opts.aws.path
-  @upload._getDestPath prefix, cb
+  @upload._getDestPath prefix, filename, cb

 ##
 # Resize image


Make injecting styles idempotent

Make injecting styles idempotent

--- src/index.coffee
+++ src/index.coffee
@@ -64,9 +64,15 @@
       newFontSize = ms(scaler, options.modularScales[0]) * baseFont + "px"
       vr.adjustFontSizeTo(newFontSize)
     injectStyles: ->
-      node = document.createElement('style')
-      node.innerHTML = createStylesString()
-      document.head.appendChild(node)
+      # Replace existing
+      if document.getElementByID('typography.js')?
+        styleNode = document.getElementById('typography.js')
+        styleNode.innerHTML = createStylesString(options)
+      else
+        node = document.createElement('style')
+        node.id = "typography.js"
+        node.innerHTML = createStylesString()
+        document.head.appendChild(node)
   }

 #console.log test({


Added Cutpurse and Treasury

Added Cutpurse and Treasury

--- cards.coffee
+++ cards.coffee
@@ -711,6 +711,20 @@
       state.drawCards(opp, 1)
 }

+makeCard 'Cutpurse', action, {
+  cost: 4
+  coins: +2
+  isAttack: true
+
+  playEffect: (state) ->
+      state.attackOpponents (opp) ->
+        if c.Copper in opp.hand
+          opp.doDiscard(c.Copper)
+        else
+          state.log("#{opp.ai} has no Copper in hand.")
+          state.revealHand(opp)
+}
+
 makeCard 'Diadem', c.Silver, {
   cost: 0
   isPrize: true
@@ -1312,6 +1326,27 @@
           state.gainCard(state.current, c.Gold, 'draw')
           numGolds += 1
       state.log("…gaining #{numGolds} Golds, putting them on top of the deck.")
+}
+
+makeCard 'Treasury', c.Market, {
+  buys: 0
+
+  canTopDeck: yes
+
+  buyInPlayEffect: (state, card) ->
+    if card.isVictory
+      c.Treasury.canTopDeck = no
+
+  cleanupEffect: (state) ->
+    if c.Treasury.canTopDeck
+      transferCardToTop(c.Treasury, state.current.discard, state.current.draw)
+      state.log("#{state.current.ai} returns a Treasury to the top of the deck.")
+
+
+    # canTopDeck must be reset with the last Treasury cleaned up so that the
+    # effect of buying a Victory card does not carry on to future turns
+    if c.Treasury not in state.current.inPlay
+      c.Treasury.canTopDeck = yes
 }

 makeCard 'Tribute', action, {


Removed dateTime specific options and added options handling.

--- client/formFieldTemplates.coffee
+++ client/formFieldTemplates.coffee
@@ -24,7 +24,7 @@
       if this.value is 'checked'
         this.checked = 'checked'

-  if this.inputType is 'staticDate' #or this.inputType is 'dateTime'
+  if this.inputType is 'staticDate'
     this.value = moment.unix(this.value).format("DD MMM YYYY HH:mm")

   if this.inputType is 'dateTime'
@@ -101,10 +101,10 @@

 Template.dateTimeFieldTemplate.rendered = ->
   datepicker = $("##{this.data.name}-picker")
-  switch this.data.only
-    when 'time' then datepicker.datetimepicker({pickDate: false})
-    when 'date' then datepicker.datetimepicker({pickTime: false})
-    else datepicker.datetimepicker()

-  datepicker.data('DateTimePicker').setDate(moment(this.data.value));
-  return
+  datepicker.datetimepicker(this.data.options);
+
+  if this.data.value isnt undefined
+    datepicker.data('DateTimePicker').setDate(moment(this.data.value));
+
+  return


Adds protobuf lang, and go lang.

Adds protobuf lang, and go lang.

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -10,6 +10,7 @@
   "emmet"
   "file-icons"
   "file-watcher"
+  "go-plus"
   "highlight-selected"
   "hyperclick"
   "hyperclick-robot-framework"
@@ -25,6 +26,7 @@
   "language-gitignore"
   "language-gradle"
   "language-ini"
+  "language-protobuf"
   "language-restructuredtext"
   "language-robot-framework"
   "language-twig"


Fixed cat and fetch helpers

Fixed cat and fetch helpers

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -1,9 +1,10 @@
-fs     = require 'fs'
-glob   = require 'glob'
-path   = require 'path'
-rimraf = require 'rimraf'
-which  = require 'which'
-{exec} = require 'child_process'
+fs      = require 'fs'
+glob    = require 'glob'
+path    = require 'path'
+request = require 'request'
+rimraf  = require 'rimraf'
+which   = require 'which'
+{exec}  = require 'child_process'

 # Prepare export object.
 module.exports = exports = {}
@@ -42,7 +43,7 @@
     fs.mkdirSync dir, 0755

 # Concatenates one or more strings to target file.
-exports.cat = (target, strings...) ->
+exports.cat = (target, strings) ->
   fd = fs.openSync target, 'w'
   for s in strings
     fs.writeSync fd, s, null
@@ -50,21 +51,23 @@
   fs.closeSync fd

 # Fetches the contents of local or remote paths.
-expots.fetch = (fn, paths...) ->
+exports.fetch = (fn, paths) ->
   contents = []
   current = 0
   total = paths.length
   step = ->
     fn contents if ++current is total
   for path, i in paths
-    if /^[a-z]+:\/\//.test path
-      request path, (err, response, body) ->
-        throw err if err
-        contents[i] = body
+    do (path, i) ->
+      if /^[a-z]+:\/\//.test path
+        console.log "fetch: #{path}"
+        request path, (err, response, body) ->
+          throw err if err
+          contents[i] = body
+          step()
+      else
+        contents[i] = fs.readFileSync path, 'utf8'
         step()
-    else
-      fs.readFileSync path, 'utf8'
-      step()

 # Find files based on a global pattern.
 # Call the provided function with the result, if any files are found.


Make sure `hash` function won't break after eons of usage

Previously, increasing the prefix indefinitely would eventually make the function incorrect since `Math.pow(10, 16) === (Math.pow(10, 16) + 1)` in JavaScript, due to all numbers being floating point numbers. This wasn't likely to happen in real life, 10^16 is quite a lot, but now I can sleep better at night ;)

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -55,16 +55,20 @@
 capitalize = (word) ->
   word.slice(0,1).toUpperCase() + word.slice(1)

+hash_current = 0
+hash_prefix = ""
+hash_max = Math.pow(10, 12)
+
 hash = (value) ->
   key = if value instanceof Object
     unless "_s_hash" of value
-      def value, "_s_hash", value: ++arguments.callee.current
+      hash_prefix = Math.random().toString(36) if hash_current >= hash_max
+      def value, "_s_hash", value: hash_prefix + (++hash_current)
     value._s_hash
   else
     value
   return (typeof value) + ' ' + key

-hash.current = 0

 # Pushes item to a collection on object, interacts in a sane way with prototypes.
 safePush = (object, collection, item) ->


lexer: optimized regexes

lexer: optimized regexes

--- src/lexer.coffee
+++ src/lexer.coffee
@@ -33,7 +33,7 @@
   # Before returning the token stream, run it through the [Rewriter](rewriter.html)
   # unless explicitly asked not to.
   tokenize: (code, options) ->
-    code     = code.replace /(\r|\s+$)/g, ''
+    code     = code.replace(/\r/g, '').replace /\s+$/, ''
     o        = options or {}
     @code    = code         # The remainder of the source code.
     @i       = 0            # Current character position we're parsing.
@@ -75,7 +75,7 @@
   # referenced as property names here, so you can still do `jQuery.is()` even
   # though `is` means `===` otherwise.
   identifierToken: ->
-    return false unless id = @match IDENTIFIER, 1
+    return false unless id = @match IDENTIFIER
     @i += id.length
     forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1
     tag = 'IDENTIFIER'
@@ -104,7 +104,7 @@
   # Matches numbers, including decimals, hex, and exponential notation.
   # Be careful not to interfere with ranges-in-progress.
   numberToken: ->
-    return false unless number = @match NUMBER, 1
+    return false unless number = @match NUMBER
     return false if @tag() is '.' and starts number, '.'
     @i += number.length
     @token 'NUMBER', number
@@ -117,7 +117,7 @@
     return false unless string =
       @balancedToken(['"', '"'], ['#{', '}']) or
       @balancedToken ["'", "'"]
-    @interpolateString string.replace /\n/g, '\\\n'
+    @interpolateString string.replace MULTILINER, '\\\n'
     @line += count string, "\n"
     @i += string.length
     true
@@ -126,20 +126,22 @@
   # preserve whitespace, but ignore indentation to the left.
   heredocToken: ->
     return false unless match = @chunk.match HEREDOC
-    quote = match[1].substr 0, 1
-    doc = @sanitizeHeredoc match[2] or match[4] or '', {quote}
+    heredoc = match[0]
+    quote = heredoc.charAt 0
+    doc = @sanitizeHeredoc match[2], {quote}
     @interpolateString quote + doc + quote, heredoc: yes
-    @line += count match[1], "\n"
-    @i += match[1].length
+    @line += count heredoc, '\n'
+    @i += heredoc.length
     true

   # Matches and consumes comments.
   commentToken: ->
-    return false unless match = @chunk.match(COMMENT)
-    @line += count match[1], "\n"
-    @i += match[1].length
-    if match[2]
-      @token 'HERECOMMENT', @sanitizeHeredoc match[2],
+    return false unless match = @chunk.match COMMENT
+    [comment, here] = match
+    @line += count comment, '\n'
+    @i += comment.length
+    if here
+      @token 'HERECOMMENT', @sanitizeHeredoc here,
         herecomment: true, indent: Array(@indent + 1).join(' ')
       @token 'TERMINATOR', '\n'
     true
@@ -148,7 +150,7 @@
   jsToken: ->
     return false unless starts @chunk, '`'
     return false unless script = @balancedToken ['`', '`']
-    @token 'JS', script.replace JS_CLEANER, ''
+    @token 'JS', script.slice 1, -1
     @i += script.length
     true

@@ -161,18 +163,18 @@
     return false if first[1] is ' ' and @tag() not in ['CALL_START', '=']
     return false if     include NOT_REGEX, @tag()
     return false unless regex = @balancedToken ['/', '/']
-    return false unless end = @chunk.substr(regex.length).match REGEX_END
-    regex += flags = end[2] if end[2]
-    if regex.match REGEX_INTERPOLATION
-      str = regex.substring(1).split('/')[0]
-      str = str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
-      @tokens = @tokens.concat [['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]
+    return false unless end = @chunk[regex.length..].match REGEX_END
+    flags = end[0]
+    if REGEX_INTERPOLATION.test regex
+      str = regex.slice 1, -1
+      str = str.replace REGEX_ESCAPE, '\\$&'
+      @tokens.push ['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']
       @interpolateString "\"#{str}\"", escapeQuotes: yes
-      @tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"#{flags}\""] if flags
-      @tokens.splice @tokens.length, 0, [')', ')'], [')', ')']
+      @tokens.push [',', ','], ['STRING', "\"#{flags}\""] if flags
+      @tokens.push [')', ')'], [')', ')']
     else
-      @token 'REGEX', regex
-    @i += regex.length
+      @token 'REGEX', regex + flags
+    @i += regex.length + flags.length
     true

   # Matches a token in which which the passed delimiter pairs must be correctly
@@ -191,11 +193,11 @@
   # Keeps track of the level of indentation, because a single outdent token
   # can close multiple indents, so we need to know how far in we happen to be.
   lineToken: ->
-    return false unless indent = @match MULTI_DENT, 1
-    @line += count indent, "\n"
+    return false unless indent = @match MULTI_DENT
+    @line += count indent, '\n'
     @i    += indent.length
-    prev = @prev(2)
-    size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
+    prev = @prev 2
+    size = indent.length - 1 - indent.lastIndexOf '\n'
     nextCharacter = @match NEXT_CHARACTER, 1
     noNewlines = nextCharacter is '.' or nextCharacter is ',' or @unfinished()
     if size - @indebt is @indent
@@ -235,13 +237,13 @@
         @outdebt = 0
         @token 'OUTDENT', dent
     @outdebt -= moveOut if dent
-    @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or noNewlines
+    @token 'TERMINATOR', '\n' unless @tag() is 'TERMINATOR' or noNewlines
     true

   # Matches and consumes non-meaningful whitespace. Tag the previous token
   # as being "spaced", because there are some cases where it makes a difference.
   whitespaceToken: ->
-    return false unless space = @match WHITESPACE, 1
+    return false unless space = @match WHITESPACE
     prev = @prev()
     prev.spaced = true if prev
     @i += space.length
@@ -264,11 +266,11 @@
   # here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
   # parentheses that indicate a method call from regular parentheses, and so on.
   literalToken: ->
-    match = @chunk.match OPERATOR
-    value = match and match[1]
-    space = match and match[2]
-    @tagParameters() if value and value.match CODE
-    value or= @chunk.substr 0, 1
+    if match = @chunk.match OPERATOR
+      [value, space] = match
+      @tagParameters() if CODE.test value
+    else
+      value = @chunk.charAt 0
     @i += value.length
     spaced = (prev = @prev()) and prev.spaced
     tag = value
@@ -321,8 +323,8 @@
     indent = options.indent
     return doc if options.herecomment and not include doc, '\n'
     unless options.herecomment
-      while (match = HEREDOC_INDENT.exec(doc)) isnt null
-        attempt = if match[2]? then match[2] else match[3]
+      while (match = HEREDOC_INDENT.exec doc)
+        attempt = if match[1]? then match[1] else match[2]
         indent = attempt if not indent? or 0 < attempt.length < indent.length
     indent or= ''
     doc = doc.replace(new RegExp("^" + indent, 'gm'), '')
@@ -519,29 +521,26 @@
 JS_FORBIDDEN = JS_KEYWORDS.concat RESERVED

 # Token matching regexes.
-IDENTIFIER    = /^([a-zA-Z\$_](\w|\$)*)/
-NUMBER        = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b/i
-HEREDOC       = /^("{6}|'{6}|"{3}([\s\S]*?)\n?([ \t]*)"{3}|'{3}([\s\S]*?)\n?([ \t]*)'{3})/
-OPERATOR      = /^(-[\-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)([ \t]*)/
-WHITESPACE    = /^([ \t]+)/
-COMMENT       = /^(###([^#][\s\S]*?)(###[ \t]*\n|(###)?$)|(\s*#(?!##[^#])[^\n]*)+)/
-CODE          = /^((-|=)>)/
-MULTI_DENT    = /^((\n([ \t]*))+)(\.)?/
-LAST_DENTS    = /\n([ \t]*)/g
-LAST_DENT     = /\n([ \t]*)/
+IDENTIFIER = /^[a-zA-Z_$][\w$]*/
+NUMBER     = /^(?:0x[\da-f]+)|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?/i
+HEREDOC    = /^("""|''')([\s\S]*?)\n?[ \t]*\1/
+OPERATOR   = /^(?:-[-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)(?=([ \t]*))/
+WHITESPACE = /^[ \t]+/
+COMMENT    = /^###([^#][\s\S]*?)(?:###[ \t]*\n|(?:###)?$)|^(?:\s*#(?!##[^#])[^\n]*)+/
+CODE       = /^[-=]>/
+MULTI_DENT = /^(?:\n[ \t]*)+/

 # Regex-matching-regexes.
 REGEX_START         = /^\/([^\/])/
-REGEX_INTERPOLATION = /([^\\]#\{.*[^\\]\})/
-REGEX_END           = /^(([imgy]{1,4})\b|\W|$)/
-REGEX_ESCAPE        = /\\[^\$]/g
+REGEX_INTERPOLATION = /[^\\]#\{.*[^\\]\}/
+REGEX_END           = /^[imgy]{0,4}(?![a-zA-Z])/
+REGEX_ESCAPE        = /\\[^#]/g

 # Token cleaning regexes.
-JS_CLEANER      = /(^`|`$)/g
 MULTILINER      = /\n/g
-NO_NEWLINE      = /^([+\*&|\/\-%=<>!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
-HEREDOC_INDENT  = /(\n+([ \t]*)|^([ \t]+))/g
-ASSIGNED        = /^\s*(([a-zA-Z\$_@]\w*|["'][^\r\n]+?["']|\d+)[ \t]*?[:=][^:=])/
+NO_NEWLINE      = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|not|delete|typeof|instanceof)$/
+HEREDOC_INDENT  = /\n+([ \t]*)|^([ \t]+)/g
+ASSIGNED        = /^\s*((?:[a-zA-Z$_@]\w*|["'][^\n]+?["']|\d+)[ \t]*?[:=][^:=])/
 NEXT_CHARACTER  = /^\s*(\S)/

 # Compound assignment tokens.


Don't show tip when list is empty

Don't show tip when list is empty

--- views/services/doyouknow.cjsx
+++ views/services/doyouknow.cjsx
@@ -6,9 +6,9 @@
 PREFIX = __ "doyouknow-prefix"

 update = (list) ->
-  return if !config.get('poi.doyouknow.enabled', true)
+  return if !config.get('poi.doyouknow.enabled', true) && list
   # Post a random tip
-  window.log PREFIX+list[Math.floor(Math.random() * list.length)],
+  window.log PREFIX + list[Math.floor(Math.random() * list.length)],
     priority: 1
     stickyFor: STICKY_TIME*1000
   # Reinvoked after certain interval


Fix errors on context menu creation

Fix errors on context menu creation

--- src/links.coffee
+++ src/links.coffee
@@ -37,7 +37,7 @@
 	chrome.contextMenus.create({contexts:["all"], id:"parent", title:chrome.i18n.getMessage("menu_main")})
 	chrome.contextMenus.create({contexts:["all"], parentId:"parent", id:"all", title:chrome.i18n.getMessage("menu_sub_all")})
 	chrome.contextMenus.create({contexts:["all"], parentId:"parent", type:"separator"})
-	for own key, value of FILTERS
+	for own key, value of FILTERS when key != "all"
 		chrome.contextMenus.create({contexts:["all"], parentId:"parent", id:key, title:chrome.i18n.getMessage("menu_sub_#{key}")})

 onMessageHandler = (request, sender, sendResponse) ->
@@ -56,14 +56,15 @@
 	@previewAmount = Math.min(@links.length, LINK_PREVIEW_THRESHOLD)
 	if @previewAmount > 0
 		chrome.contextMenus.create({contexts:["all"], parentId:"parent", id:"preview_separator", type:"separator"})
-		for i in [0..@previewAmount]
+		for i in [0...@previewAmount]
 			@previewLinks["preview#{i}"] = @links[i]
 			chrome.contextMenus.create({contexts:["all"], parentId:"parent", id:"preview#{i}", title:@links[i]})

 clearPreviewContextMenus = ->
-	chrome.contextMenus.remove("preview_separator")
-	for i in [0..@previewAmount]
-		chrome.contextMenus.remove("preview#{i}")
+	if @previewAmount > 0
+		chrome.contextMenus.remove("preview_separator")
+		for i in [0...@previewAmount]
+			chrome.contextMenus.remove("preview#{i}")
 	@previewAmount = 0
 	@previewLinks = {}


Simpify 'file:.' prefix check

Simpify 'file:.' prefix check

--- src/install.coffee
+++ src/install.coffee
@@ -346,7 +346,7 @@
     for name, version of @getPackageDependencies()
       do (name, version) =>
         commands.push (next) =>
-          if version.indexOf('file:.') is 0
+          if version.startsWith('file:.')
             @installLocalPackage(name, version, options, next)
           else
             @installRegisteredPackage({name, version}, options, next)


remove old package

remove old package

--- src/install.coffee
+++ src/install.coffee
@@ -355,6 +355,10 @@
             @installNode (error) -> next(error, packagePath)
         commands.push (packagePath, next) =>
           @installModule(options, pack, packagePath, next)
+        if installGlobally and packageName isnt pack.name
+          commands.push (newPack, next) => # remove package with old name
+            fs.removeSync(path.join(@atomPackagesDirectory, packageName))
+            next(null, newPack)
         commands.push ({installPath}, next) ->
           if installPath?
             metadata = JSON.parse(fs.readFileSync(path.join(installPath, 'package.json'), 'utf8'))


fix: remove class when call destroy()

fix: remove class when call destroy()

--- src/loading.coffee
+++ src/loading.coffee
@@ -81,11 +81,11 @@
       $( ".simple-loading-mask, .simple-global-loading" ).remove()
     else if @opts.type instanceof jQuery
       $btn = @opts.type
+      $btn.removeClass "simple-loading"
       if $btn.is "button"
         $btn.text $btn.data("origin-text")
           .attr "style", ""
           .prop "disabled", false
-          .removeClass "simple-loading"
           .removeData "simple origin-text"
       else if $btn.is "a"
         $btn.show().next(".simple-tiny-loading").remove()


manager: Avoid trying to start msgflo/RoundRobin

Not a participant, merely a specific binding config.
Probably this logic should go somewhere more resuable

--- src/manager.coffee
+++ src/manager.coffee
@@ -17,8 +17,13 @@
     participant.startParticipant library, client, component, processId, (err, part) ->
       return cb err, part

-  debug 'starting participants', processes
-  async.map Object.keys(processes), start, (err, parts) ->
+  isParticipant = (name) ->
+    component = processes[name].component
+    return component != 'msgflo/RoundRobin'
+
+  participants = Object.keys(processes).filter isParticipant
+  debug 'starting participants', participants, processes
+  async.map participants, start, (err, parts) ->
     debug 'participants started', err, parts.length
     return callback err, parts


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -132,7 +132,7 @@
   meter: (options={}) ->
     Object.reverseMerge options,
       borderColor: 'white'
-      borderWi
+      borderWidth: 1.5
       color: 'green'
       nameColor: 'white'
       font: '14px Helvetica'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -18,7 +18,7 @@
         width
       } = meterData

-      if not I[name]
+      if not I[name] ||
       ratio = I[name] / I["max#{name.capitalize()}"]

       if showName || text


javac now attempts a compile, but gets (wrong) compile errors.

--- src/methods.coffee
+++ src/methods.coffee
@@ -516,7 +516,7 @@
             js_str = rs.jvm2js_str jvm_path_str
             rs.init_string path.resolve path.normalize js_str
         o 'list(Ljava/io/File;)[Ljava/lang/String;', (rs, _this, file) ->
-            fname = rs.jvm2js_str file.fields.path
+            path = rs.jvm2js_str rs.get_obj file.fields.path
             files = fs.readdirSync(path)
             rs.init_object('[Ljava/lang/String;',(rs.init_string(f) for f in files))
       ]
@@ -554,6 +554,7 @@
                 throw "Tried to open #{fname}: ZipFile is not actually implemented."
               rs.set_zip_descriptor
                 name: 'special', path: 'third_party/classes/'
+          o 'close(J)V', (rs, jzfile) -> rs.free_zip_descriptor jzfile
           o 'getTotal(J)I', (rs, zd_long) ->
               zipfile = rs.get_zip_descriptor zd_long
               unless zipfile.size?


comments

comments

--- src/motions.coffee
+++ src/motions.coffee
@@ -4,9 +4,12 @@

 {Command, repeatCountTimes} = require './helpers'

-# accumulate the default mappings
+
+# The default key mappings are specified alongside the definitions of each
+# motion.  Accumulate the mappings so they can be exported.
 defaultMappings = {}
 map = (keys, motionClass) -> defaultMappings[keys] = motionClass
+

 # base class for all motions
 class Motion extends Command
@@ -18,12 +21,15 @@
   # motions do the same thing in visual mode
   visualExec: (jim) -> @exec jim

-# used for double operators `cc`, `2yy`, `3d4d`
+
+# Define an unmapped `Motion` that will be used for double operators (e.g. `cc`,
+# `2yy`, `3d4d`).
 class LinewiseCommandMotion extends Motion
   linewise: yes
   exec: (jim) ->
     if additionalLines = @count - 1
       new MoveDown(additionalLines).exec jim
+

 # Basic directional motions
 # -------------------------
@@ -44,15 +50,17 @@
 # Word motions
 # ------------

-# these return a new regex each time so that we always get a fresh lastIndex
-# a string of non-whitespace characters
+# Return a new regex with a fresh lastIndex each time for use in word motions.
+# There are two different kinds of words:
+#
+# * A **WORD** is a string of non-whitespace characters.
+# * A **word** is a string of regex word characters (i.e. `[A-Za-z0-9_]`) *or* a
+#   string of non-whitespace non-word characters (i.e. special chars)
 WORDRegex = -> /\S+/g
-# a string of word characters (i.e. [A-Za-z0-9_]) OR a string of non-whitespace non-word characters (i.e. special chars)
 wordRegex = -> /(\w+)|([^\w\s]+)/g

-# move to the end of the current word or the end of the next word if on the end of a
-# word
+# Move to the next end of a **word**.
 map 'e', class MoveToWordEnd extends Motion
   exec: repeatCountTimes (jim) ->
     regex = if @bigWord then WORDRegex() else wordRegex()
@@ -61,16 +69,20 @@
     rightOfCursor = line.substring column

     matchOnLine = regex.exec rightOfCursor
+
+    # If we're on top of the last char of a word we want to match the next one.
     if matchOnLine?[0].length <= 1
-      # if we're on top of the last char of a word we want to go to the next one
       matchOnLine = regex.exec rightOfCursor

+    # If there's a match on the current line, go to the end of the word that's
+    # been matched.
     if matchOnLine
-      # go to the end of the word that's been matched
       column += matchOnLine[0].length + matchOnLine.index - 1
+
+    # If there's no match on the current line go end of the next word, whatever
+    # line that may be on.  If there are no more non-blank characters, don't
+    # move the cursor.
     else
-      # if there's no match on the current line go end of the next word, whatever line
-      # that may be
       loop
         line = jim.adaptor.lineText ++row
         firstMatchOnSubsequentLine = regex.exec line
@@ -78,16 +90,17 @@
           column = firstMatchOnSubsequentLine[0].length + firstMatchOnSubsequentLine.index - 1
           break
         else if row is jim.adaptor.lastRow()
-          # there are no more non-blank characters, don't move the cursor
           return

+    # Move to the `row` and `column` that have been determined.
     jim.adaptor.moveTo row, column

+# Move to the next end of a **WORD**.
 map 'E', class MoveToBigWordEnd extends MoveToWordEnd
   bigWord: yes

-# move to the beginning of the next word
+# Move to the next beginning of a **word**.
 map 'w', class MoveToNextWord extends Motion
   exclusive: yes
   exec: (jim) ->
@@ -99,33 +112,41 @@
       rightOfCursor = line.substring column

       thisMatch = regex.exec rightOfCursor
+
+      # If the next match isn't on this line, find it on the next.
       if not thisMatch or not nextMatch = regex.exec rightOfCursor
-        # the next match isn't on this line, find it on the next
-
+
+        # If the user typed `dw` on the last word of a line, for instance, just
+        # delete the rest of the word instead of deleting to the start of the
+        # word on the next line.
         if timesLeft is 0 and @operation
-          # one exception: e.g. `dw` on the last word of a line just deletes the rest
-          # of the word (instead of deleting to the start of the word on the next line)
           column = line.length
+
         else
           line = jim.adaptor.lineText ++row
           nextLineMatch = regex.exec line
           column = nextLineMatch?.index or 0
+
+      # `cw` actually behaves like `ce` instead of `dwi`. So if this motion is
+      # part of a `Change` operation, ensure that the last time the loop is
+      # executed we execute a `MoveToWordEnd` instead.
       else if timesLeft is 0 and @operation?.switchToMode is 'insert'
-        # this motion is part of a Change operation, this accounts for the exception
-        # that `cw` behaves like `ce` instead of `dwi`
         lastMotion = new MoveToWordEnd()
         lastMotion.bigWord = @bigWord
         lastMotion.exec jim
         @exclusive = no
         return
+
+      # If we've found the beginning of the next match and it's not already
+      # under the cursor, go to it.
       else if thisMatch?.index > 0
-        # We've found the beginning of the next match and it's not already
-        # under the cursor. Go to it
         column += thisMatch.index
+
+      # If we're on top of part of a word, go to the next one.
       else
-        # we're on top of part of a WORD, go to the next one
         column += nextMatch.index

+      # Move to the `row` and `column` that have been determined.
       jim.adaptor.moveTo row, column

 map 'W', class MoveToNextBigWord extends MoveToNextWord
@@ -158,6 +179,7 @@
       match = regex.exec line
       column = match?.index or 0

+    # Move to the `row` and `column` that have been determined.
     jim.adaptor.moveTo row, column

 map 'B', class MoveBackBigWord extends MoveBackWord


Remove stray trace

Remove stray trace

--- src/package.coffee
+++ src/package.coffee
@@ -547,5 +547,4 @@
       detail = error.message
       stack = error.stack ? error

-    console.trace()
     atom.notifications.addFatalError(message, {stack, detail, dismissable: true})


Emit event after request callback

Emit event after request callback

--- src/request.coffee
+++ src/request.coffee
@@ -13,6 +13,7 @@
         @preparing = false
       else
         @userCallback.apply(@, arguments)
+        @emit('requestCompleted')

   addParameter: (name, type, value, options) ->
     if arguments.length < 4


routing/MQTT: Also subscribe when srcQueue==tgtQueue

--- src/routing.coffee
+++ src/routing.coffee
@@ -38,7 +38,7 @@
     # TODO: handle non-pubsub types
     id = bindingId from, to
     debug 'Binder.addBinding', binding.type, id
-    return callback null if @bindings[id] or from == to
+    return callback null if @bindings[id] # already exists, avoid duplicate

     handler = (msg) =>
       binding = @bindings[id]
@@ -50,8 +50,11 @@
         for subCallback in subscription.handlers
           subCallback(subscription.binding, msg.data)

-      @transport.sendTo 'outqueue', to, msg.data, (err) ->
-        throw err if err
+      if from != to
+        @transport.sendTo 'outqueue', to, msg.data, (err) ->
+          throw err if err
+      else
+        # same topic/queue, data should appear without our forwarding

     @transport.subscribeToQueue from, handler, (err) =>
       return callback err if err


Fixed for coffeelint

Fixed for coffeelint

--- server/api/url/url.model.coffee
+++ server/api/url/url.model.coffee
@@ -18,7 +18,7 @@
 UrlSchema.index { 'shortenId': 1 }, { unique: true }

 Hashids = require 'hashids'
-hashids = new Hashids(config.secrets.session, 6);
+hashids = new Hashids(config.secrets.session, 6)

 UrlSchema.pre 'save', (next) ->
   _this = this


improve user messages

improve user messages

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -339,15 +339,14 @@

   writeData()

-  usermsg = "#{who} updated expiration for `#{entry.val}` #{list_name}ing. " +
-    "New expiration `#{entry.expires}`."
+  usermsg = "#{who} updated expiration for `#{entry.val}` #{list_name}ing."
+  usermsg += "  New expiration `#{entry.expires}`."
   msg.reply usermsg

   logmsg = "#{modulename}: robot responded to #{who}: " +
     "updated entry in #{list_name}"
   robot.logger.info logmsg

-  #usermsg = usermsg.replace(/  Change will be applied in \< 5 minutes\./, '')
   notifySubscribers usermsg, who

@@ -393,9 +392,9 @@

   deltaN = fwdata[list_name].length - new_entry.length
   if deltaN > 0
-    usermsg = "#{who} removed `#{deltaN}` fw #{list_name} entries.  " +
-      "Change will be applied in < 5 minutes. Removed: " +
-      "```"+ deleted.join("\n") + "```"
+    usermsg = "#{who} removed `#{deltaN}` fw #{list_name} entries."
+    usermsg += "  Change will be applied in < 5 minutes." unless isTerse who
+    usermsg += "  Removed: ```"+ deleted.join("\n") + "```"
     fwdata[list_name] = new_entry
     writeData()
   else
@@ -539,10 +538,10 @@
   arr = []
   for obj in fwdata.firewalls
     obj.checkin = moment(obj.checkin) if typeof(obj.checkin) is 'string'
-    arr.push sprintf '%-18s %-10s %-8s %-15s', obj.name, obj.list, obj.type,
+    arr.push sprintf '%-16s %-10s %-10s %-15s', obj.name, obj.list, obj.type,
       obj.checkin.fromNow()
-  usermsg = "Expect firewall check times: ```"+ arr.join("\n") + "```"
-  msg.reply usermsg
+  usermsg = "Expected firewall check times: ```"+ arr.join("\n") + "```"
+  msg.send usermsg

   logmsg = "#{modulename}: robot responded to #{who}: checkins"
   robot.logger.info logmsg


remove syslog listener

remove syslog listener

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -20,7 +20,6 @@
 moment = require 'moment'
 #ipaddr = require 'ipaddr.js'
 sprintf = require("sprintf-js").sprintf
-syslogd = require('syslogd')

 modulename = 'fw'
 data_file = modulename + ".json"


Simplified udefine.inject.add calls

Simplified udefine.inject.add calls

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -74,6 +74,11 @@
   udefine.inject.modules = {}

   udefine.inject.add = (name, value) ->
+    return unless name?
+    value = {} unless value?
+    value.root = root unless value.root?
+    value.name = name unless value.name?
+
     udefine.inject.modules[name] = value
     @


comment out unreliable distributed messaging test

--- tests/infrastructure/messaging.tests.coffee
+++ tests/infrastructure/messaging.tests.coffee
@@ -53,16 +53,19 @@
     )
     .expect(generatedEventsForCustomerRegistration())

+  '''
   it 'supports distributed messaging via a shared commits collection', (test, done) ->

-    secondApp = Space.Application.create RequiredModules: ['Space.eventSourcing']
-    secondApp.configure {
-      appId: 'SecondApp'
+    SecondApp = Space.Application.extend {
+      requiredModules: ['Space.eventSourcing']
+      configuration: { appId: 'SecondApp' }
+      onStart: ->
+        # Aggregate all published events on the second app
+        @publishedEvents = []
+        @eventBus.onPublish (event) => @publishedEvents.push event
     }
+    secondApp = new SecondApp()
     secondApp.start()
-    # Aggregate all published events on the second app
-    publishedEvents = []
-    secondApp.eventBus.onPublish (event) -> publishedEvents.push event
     expectedEvents = null

     CustomerApp.test(CustomerApp.Customer)
@@ -78,10 +81,9 @@
       return expectedEvents
     )

-    Meteor.setTimeout (done =>
-      # Remove the event that is only visible to the other app
-      # because it is directly published on its event bus!
-      expectedEvents.splice(3,1)
-      expect(publishedEvents).toMatch expectedEvents
-      secondApp.stop()
-    ), 100
+    # Remove the event that is only visible to the other app
+    # because it is directly published on its event bus!
+    expectedEvents.splice(3,2)
+    expect(secondApp.publishedEvents).toMatch expectedEvents
+    secondApp.stop()
+  '''


Check to see if entry really exists before populating tracks in Top100 playlist. Fix Issue 47

--- coffee/_TrackSource.class.coffee
+++ coffee/_TrackSource.class.coffee
@@ -120,11 +120,12 @@
         tracks = []
         if not error and response.statusCode is 200
           $.each(data.feed.entry, (i, track) ->
-            tracks.push
-              title: track['media$group']['media$title']['$t']
-              artist: track['author'][0]['name']['$t']
-              cover_url_medium: track['media$group']['media$thumbnail'][1].url
-              cover_url_large: track['media$group']['media$thumbnail'][0].url
+            if track['media$group']['media$thumbnail']
+              tracks.push
+                title: track['media$group']['media$title']['$t']
+                artist: track['author'][0]['name']['$t']
+                cover_url_medium: track['media$group']['media$thumbnail'][1].url
+                cover_url_large: track['media$group']['media$thumbnail'][0].url
           )
         @_cachedFeaturedMusic[playlistId] = tracks
         success? @_cachedFeaturedMusic[playlistId]


Fix hash parameters so that #diagramOnly=true works again

--- src/code/utils/hash-parameters.coffee
+++ src/code/utils/hash-parameters.coffee
@@ -50,7 +50,7 @@
   readHash: ->
     if window and window.location
       # remove the leading slash
-      hash = window.location.hash.substring(1)
+      hash = window.top.location.hash.substring(1)
     else
       ""


Composite UID for schedule records

Composite UID for schedule records

--- schedule_importer/main.coffee
+++ schedule_importer/main.coffee
@@ -1,3 +1,5 @@
+#!/bin/env node
+
 async = require('async')
 program = require('commander')
 config = require('config')
@@ -7,14 +9,33 @@
 moment = require('moment')
 mongo = require('mongodb')
 OSPoint = require('ospoint')
+util = require('util')
 zlib = require('zlib')
+
+PATH_TEMPLATE = '/ntrod/CifFileAuthenticate?type=CIF_%s_TOC_FULL_DAILY&day=toc-full'
+
+################################################################################
+#
+# Command line parsing
+#
+################################################################################
+
+list = (val) ->
+  return val.split(',')

 program
   .version('0.0.1')
-  .option('-s, --schedule [path_to_schedule]', 'A gunzipped schedule file')
+  .option('-s, --schedules <path_to_schedules>', 'schedule files', list)
   .option('-r, --railreference [path_to_rail_reference]',
           'NaPTAN rail reference')
   .parse(process.argv)
+
+
+################################################################################
+#
+# Database
+#
+################################################################################

 getMongoDb = (callback) ->
   onMongoConnect = (error, db) ->
@@ -26,13 +47,19 @@
     "mongodb://#{config.mongo.host}:#{config.mongo.port}/#{config.mongo.name}",
     onMongoConnect)

-options =
-  hostname: 'datafeeds.networkrail.co.uk'
-  path: '/ntrod/CifFileAuthenticate?type=CIF_HF_TOC_FULL_DAILY&day=toc-full'
-  auth: "#{config.datafeed.username}:#{config.datafeed.password}"
-
-getSchedule = (callback) ->
+
+################################################################################
+#
+# Network Rail datafeed SCHEDULE importing
+#
+################################################################################
+
+getSchedule = (callback, toc_code) ->
   buffer = []
+  options =
+    hostname: 'datafeeds.networkrail.co.uk'
+    path: util.format(PATH_TEMPLATE, toc_code)
+    auth: "#{config.datafeed.username}:#{config.datafeed.password}"
   req = https.request options, (res) ->
     console.log res.statusCode
     if res.statusCode == 302
@@ -59,6 +86,13 @@
   for char in daysRunString
     char == '1'

+getScheduleCompositeUID = (jsonScheduleV1) ->
+  cifTrainUid = jsonScheduleV1['CIF_train_uid']
+  scheduleStartDate = jsonScheduleV1['schedule_start_date']
+  cifStopIndicator = jsonScheduleV1['CIF_stp_indicator']
+  return "#{cifTrainUid}#{scheduleStartDate}#{cifStopIndicator}"
+
+
 importSchedule = (scheduleData) ->
   entries = []
   scheduleEntries = []
@@ -74,35 +108,44 @@
       entry['_id'] = entry['TiplocV1']['tiploc_code']
       entries.push entry
     else if entry['JsonScheduleV1']
-      id = entry['JsonScheduleV1']['CIF_train_uid']
-      startDate = entry['JsonScheduleV1']['schedule_start_date']
-      entry['JsonScheduleV1']['schedule_start_date'] \
+      jsonScheduleV1 = entry['JsonScheduleV1']
+      entry['_id'] = getScheduleCompositeUID(jsonScheduleV1)
+      startDate = jsonScheduleV1['schedule_start_date']
+      jsonScheduleV1['schedule_start_date'] \
         = moment(startDate, 'YYYY-MM-DD').toDate()
-      endDate = entry['JsonScheduleV1']['schedule_end_date']
-      entry['JsonScheduleV1']['schedule_end_date'] \
+      endDate = jsonScheduleV1['schedule_end_date']
+      jsonScheduleV1['schedule_end_date'] \
         = moment(endDate, 'YYYY-MM-DD').toDate()
-      daysRun = entry['JsonScheduleV1']['schedule_days_runs']
+      daysRun = jsonScheduleV1['schedule_days_runs']
       daysRun = daysRunToWeekArray(daysRun)
-      entry['JsonScheduleV1']['schedule_days_runs'] = daysRun
+      jsonScheduleV1['schedule_days_runs'] = daysRun
       scheduleEntries.push entry

   console.log "#{entries.length} tiplocs to insert"
   console.log "#{scheduleEntries.length} schedules to insert"
+  insertDbEntries(entries, scheduleEntries)
+
+insertDbEntries = (tiplocEntries, scheduleEntries) ->
   console.log 'inserting into db'
   getMongoDb (db) ->
     tiplocs = db.collection('tiplocs')
-    tiplocs.insert entries, (error, docs) ->
+    tiplocs.insert tiplocEntries, (error, docs) ->
       console.log 'inserted the entries'
       schedules = db.collection('schedules')
       async.each scheduleEntries, schedules.save.bind(schedules), (error) ->
         if error?
           console.log error
-        console.log 'ALL DONE closing db'
-        db.close ->
-          console.log 'goodbye'
+        console.log 'closing db'
+        db.close()
+
+
+################################################################################
+#
+# NaPTAN rail reference importing
+#
+################################################################################

 readRailReferenceCsv = (railReferenceCsv) ->
-  console.log 'safa'
   rows = []
   csv(railReferenceCsv, headers: true)
     .on('data', (data) ->
@@ -111,7 +154,6 @@
       data.latitude = latLon.latitude
       data.longitude = latLon.longitude
       data._id = data.TiplocCode
-      console.log(data)
       rows.push(data)
     ).on('end', ->
       getMongoDb (db) ->
@@ -121,25 +163,37 @@
           db.close()
   ).parse()

+
+################################################################################
+#
+# main
+#
+################################################################################
+
 main = ->
   if program.railreference
     console.log "loading rail reference form #{program.railreference}"
     readRailReferenceCsv(program.railreference)
-  else if program.schedule
-    console.log "loading schedule from #{program.schedule}"
-    fs.readFile program.schedule, (error, data) ->
-      if error
-        throw error
-      importSchedule(data)
+  else if program.schedules
+    for schedule in program.schedules
+      console.log "loading schedule from #{schedule}"
+      fs.readFile schedule, (error, data) ->
+        if error
+          throw error
+        importSchedule(data)
   else
-    getSchedule (error, data) ->
-      if error
-        console.log error
-      fs.writeFile '/tmp/schedule.txt', data, (error) ->
-        if error
-          console.log error
-        else
-          console.log 'Saved'
+    for toc_code in config.datafeed.schedule_toc_codes
+      do (toc_code) ->
+        saveSchedule = (error, data) ->
+          if error
+            console.log error
+          path = "/tmp/schedule-#{toc_code}.txt"
+          fs.writeFile path, data, (error) ->
+            if error
+              console.log error
+            else
+              console.log "Saved to #{path}"
+        getSchedule(saveSchedule, toc_code)

 if require.main == module
   main()


removing a trace for IE in avatar

removing a trace for IE in avatar

--- app/coffee/avatar.coffee
+++ app/coffee/avatar.coffee
@@ -47,8 +47,8 @@
       onComplete: (id, fileName, response) =>
         avatar_url = '/images/uploads/' + response.image.id + '-avatar_main.' + response.image.format
         avatar_thumb = '/images/uploads/' + response.image.id + '-32x32.' + response.image.format
-        alert("RESPONSE :: " + response)
-        alert("RESPONSE.image :: " + response.image)
+        #alert("RESPONSE :: " + response)
+        #alert("RESPONSE.image :: " + response.image)

         $.ajax {
           type: 'PUT'


fixed code style on servers.lib.server.index.coffee

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -1,8 +1,8 @@
 process.title = 'koding-webserver'
-{argv}        = require 'optimist'
+{ argv }      = require 'optimist'

 Object.defineProperty global, \
-  'KONFIG', value : require('koding-config-manager').load "main.#{argv.c}"
+  'KONFIG', { value : require('koding-config-manager').load "main.#{argv.c}" }

 { webserver, projectRoot, basicAuth } = KONFIG

@@ -14,7 +14,7 @@
 app                   = express()
 webPort               = argv.p ? webserver.port
 { error_500 }         = require './helpers'
-{ generateHumanstxt } = require "./humanstxt"
+{ generateHumanstxt } = require './humanstxt'

 do ->
   cookieParser = require 'cookie-parser'
@@ -33,7 +33,7 @@
     secret            : 'foo'
     resave            : yes
     saveUninitialized : true
-  app.use bodyParser.urlencoded extended : yes
+  app.use bodyParser.urlencoded { extended : yes }
   app.use compression()
   # helmet:
   app.use helmet.xframe('sameorigin')
@@ -97,7 +97,7 @@
 app.get  '/activity/p/?*'                        , (req, res) -> res.redirect 301, '/Activity'
 app.get  '/-/healthCheck'                        , require './handlers/healthcheck'
 app.get  '/-/versionCheck'                       , require './handlers/versioncheck'
-app.get  '/-/version'                            , (req, res) -> res.jsonp version: KONFIG.version
+app.get  '/-/version'                            , (req, res) -> res.jsonp { version: KONFIG.version }
 app.get  '/-/jobs'                               , require './handlers/jobs'
 app.post '/recaptcha'                            , require './handlers/recaptcha'
 app.get  '/-/presence/:service'                  , (req, res) -> res.status(200).end()
@@ -143,7 +143,7 @@
 # NOTE: in the event of errors, send 500 to the client rather
 #       than the stack trace.
 app.use (err, req, res, next) ->
-  console.error "request error"
+  console.error 'request error'
   console.error err
   console.error err.stack
   res.status(500).send error_500()


rename install to setup

rename install to setup

--- lib/dobi.coffee
+++ lib/dobi.coffee
@@ -19,16 +19,16 @@
 Usage: dobi <command> [command-specific-options]

 where <command> [command-specific-options] is one of:
-  backup <site-slug>                create a backup of a site
+  backup <site-slug>                backup your site data
   create <my-package> <type=app>    create a new package
-  deploy <my-app>                   deploy an app (COMING SOON)
+  deploy <my-app>                   deploy an app
   docs                              open the dobi docs
   help                              show usage
   init                              initialize a workspace
-  install <my-app> <site-slug>      create a site using your app
   login                             authenticate your user
   open <site-slug>                  open a site
   run                               run a development server
+  setup <my-app> <site-slug>        setup a site using your app
   start                             daemonize a development server
   stop                              stop a daemonized development server
   usage                             show usage
@@ -181,7 +181,7 @@
           # done
           exit "backup complete: #{name}"

-  # create a new app
+  # create a new package
   when 'create'
     [id, version] = args[0].split '@'
     type = args[1] or 'app'
@@ -197,7 +197,7 @@
       user = config?.user
       exit "please login first: 'dobi login'" unless user

-      # bootstrap the project
+      # bootstrap the package
       source = path.join __dirname, '..', 'bootstrap', type
       workspace = getWorkspacePathSync()
       exit 'must be in a workspace to create a package' unless workspace
@@ -365,8 +365,49 @@
         exit 'failed to create pkg directory' if err
         exit "workspace successfully created at: #{CWD}"

-  # create a site using your app
-  when 'install'
+  # authenticate your user
+  when 'login'
+    login true, (config) ->
+      exit JSON.stringify user, null, 2 if config.user
+      exit 'not logged in. try "dobi login"'
+
+  # open a site
+  when 'open'
+    url = 'http://www.lessthan3.com'
+    url += "/#{arg}" for arg in args
+    open url
+    exit()
+
+  # run a development server
+  when 'run'
+    workspace = getWorkspacePathSync()
+    exit 'must be in a workspace to run the server' unless workspace
+
+    # dependencies
+    connect = require 'connect'
+    express = require 'express'
+    dobi = require './server'
+    pkg = require path.join '..', 'package'
+
+    # configuration
+    app = express()
+    app.use express.logger '[:date] :status :method :url'
+    app.use connect.urlencoded()
+    app.use connect.json()
+    app.use express.methodOverride()
+    app.use express.cookieParser()
+    app.use dobi {
+      pkg_dir: path.join workspace, 'pkg'
+    }
+    app.use app.router
+    app.use express.errorHandler {dumpExceptions: true, showStack: true}
+
+    # listen
+    app.listen pkg.config.port
+    log "listening: #{pkg.config.port}"
+
+  # setup a site using your app
+  when 'setup'
     [id, version] = args[0].split '@'
     slug = args[1]

@@ -458,46 +499,6 @@
               open "http://www.lessthan3.com/#{slug}?dev=1"
             ), 3000

-  # authenticate your user
-  when 'login'
-    login true, (config) ->
-      exit JSON.stringify user, null, 2 if config.user
-      exit 'not logged in. try "dobi login"'
-
-  # open a site
-  when 'open'
-    url = 'http://www.lessthan3.com'
-    url += "/#{arg}" for arg in args
-    open url
-    exit()
-
-  # run a development server
-  when 'run'
-    workspace = getWorkspacePathSync()
-    exit 'must be in a workspace to run the server' unless workspace
-
-    # dependencies
-    connect = require 'connect'
-    express = require 'express'
-    dobi = require './server'
-    pkg = require path.join '..', 'package'
-
-    # configuration
-    app = express()
-    app.use express.logger '[:date] :status :method :url'
-    app.use connect.urlencoded()
-    app.use connect.json()
-    app.use express.methodOverride()
-    app.use express.cookieParser()
-    app.use dobi {
-      pkg_dir: path.join workspace, 'pkg'
-    }
-    app.use app.router
-    app.use express.errorHandler {dumpExceptions: true, showStack: true}
-
-    # listen
-    app.listen pkg.config.port
-    log "listening: #{pkg.config.port}"

   # daemonize a development server
   when 'start'


getting rid of state and a bit of refactoring

--- src/components/task-plan/reading.cjsx
+++ src/components/task-plan/reading.cjsx
@@ -40,11 +40,12 @@
     return

   render: ->
-    selectedReadingList =
-

-
Currently selected sections in this reading

-        {_.map(@props.selected, @renderTopics)}
-
 if @props.selected.length && TocStore.isLoaded()
+    if (@props.selected.length and TocStore.isLoaded())
+      selectedReadingList =
+

+
Currently selected sections in this reading

+          {_.map(@props.selected, @renderTopics)}
+

       Select Readings
@@ -122,40 +123,50 @@
       id = TaskPlanStore.freshLocalId()
       plan = TaskPlanActions.create(id, due_at: new Date())

-    {id, plan}
+    {id}

   componentWillMount: -> TaskPlanStore.addChangeListener(@update)
   componentWillUnmount: -> TaskPlanStore.removeChangeListener(@update)

+  getPlanId: () ->
+    @getParams().id or @state.id
+
   update: () ->
-    plan = TaskPlanStore.get(@state.id)
-    @setState {plan}
+    id = @getPlanId()
+    plan = TaskPlanStore.get(id)
+    if (id isnt plan.id)
+      @setState({id: plan.id})
+    else
+      @setState {}

   setDueAt: (value) ->
-    TaskPlanActions.updateDueAt(@state.id, value)
+    id = @getPlanId()
+    TaskPlanActions.updateDueAt(id, value)

   setTitle: ->
+    id = @getPlanId()
     value = @refs.title.getDOMNode().value
-    TaskPlanActions.updateTitle(@state.id, value)
+    TaskPlanActions.updateTitle(id, value)

   publishPlan: ->
-    {id} = TaskPlanStore.get(@state.id)
+    id = @getPlanId()
     TaskPlanActions.publish(id)
     @transitionTo('editReading', {id})

   deletePlan: () ->
-    id = @getParams().id
+    id = @getPlanId()
     @transitionTo('dashboard')
     TaskPlanActions.delete(id)
     {}

   render: ->
-    id = @getParams().id
+    id = @getPlanId()
+    plan = TaskPlanStore.get(id)

-    isEnabled = @state.plan?.title and @state.plan?.due_at and @state.plan?.settings.page_ids.length > 0
+    isEnabled = plan?.title and plan?.due_at and plan?.settings.page_ids.length > 0

     headerText = if id then 'Edit Reading' else 'Add Reading'
-    topics = TaskPlanStore.getTopics(@state.id)
+    topics = TaskPlanStore.getTopics(id)

     footer= <ReadingFooter enabled={isEnabled} onPublish={@publishPlan} onDelete={@deletePlan}/>

@@ -163,7 +174,7 @@

{headerText}

         Name
-        <input ref="title" id="title" type="text" onChange={@setTitle} value={@state.plan?.title}/>
+        <input ref="title" id="title" type="text" onChange={@setTitle} value={plan?.title}/>

         Due Date
@@ -174,9 +185,9 @@
           calendar={true}
           readOnly={false}
           onChange={@setDueAt}
-          value={new Date(@state.plan?.due_at)}/>
+          value={new Date(plan?.due_at)}/>

-      <SelectTopics planId={@state.id} selected={topics}/>
+      <SelectTopics planId={id} selected={topics}/>
     </BS.Panel>

 module.exports = ReadingPlan