Remove legacy popout terminal helper

Remove legacy popout terminal helper

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,33 +10,30 @@
   treeView: null

   activate: (@state) ->
-    treeViewisDisabled = localStorage.disableTreeView is 'true'
+    @nsyncDisposables = nsyncInitializer(@state)

-    if not treeViewisDisabled
-      @nsyncDisposables = nsyncInitializer(@state)
+    @warnIfAtomsTreeViewIsActive()

-      @warnIfAtomsTreeViewIsActive()
+    document.body.classList.add('learn-ide')

-      document.body.classList.add('learn-ide')
+    @disposables = new CompositeDisposable
+    @state.attached ?= true if @shouldAttach()

-      @disposables = new CompositeDisposable
-      @state.attached ?= true if @shouldAttach()
+    @createView() if @state.attached

-      @createView() if @state.attached
-
-      @disposables.add atom.commands.add('atom-workspace', {
-        'tree-view:show': => @createView().show()
-        'tree-view:toggle': => @createView().toggle()
-        'tree-view:toggle-focus': => @createView().toggleFocus()
-        'tree-view:reveal-active-file': => @createView().revealActiveFile()
-        'tree-view:toggle-side': => @createView().toggleSide()
-        'tree-view:add-file': => @createView().add(true)
-        'tree-view:add-folder': => @createView().add(false)
-        'tree-view:duplicate': => @createView().copySelectedEntry()
-        'tree-view:remove': => @createView().removeSelectedEntries()
-        'tree-view:rename': => @createView().moveSelectedEntry()
-        'tree-view:show-current-file-in-file-manager': => @createView().showCurrentFileInFileManager()
-      })
+    @disposables.add atom.commands.add('atom-workspace', {
+      'tree-view:show': => @createView().show()
+      'tree-view:toggle': => @createView().toggle()
+      'tree-view:toggle-focus': => @createView().toggleFocus()
+      'tree-view:reveal-active-file': => @createView().revealActiveFile()
+      'tree-view:toggle-side': => @createView().toggleSide()
+      'tree-view:add-file': => @createView().add(true)
+      'tree-view:add-folder': => @createView().add(false)
+      'tree-view:duplicate': => @createView().copySelectedEntry()
+      'tree-view:remove': => @createView().removeSelectedEntries()
+      'tree-view:rename': => @createView().moveSelectedEntry()
+      'tree-view:show-current-file-in-file-manager': => @createView().showCurrentFileInFileManager()
+    })

   deactivate: ->
     nsync.cache() unless @preventCache


MAP object remove bug in 'removeObject' is fix

--- lib/main.coffee
+++ lib/main.coffee
@@ -872,8 +872,10 @@

     if (motionObj.rigid)
         object.motionObj.sprite.destroy()
-    else if (motionObj._type == LABEL || motionObj._type == SPRITE || motionObj._type == PRIMITIVE || motionObj._type == COLLADA)
-        _scenes[object.motionObj._scene].removeChild(object.motionObj.sprite)
+    else
+        switch (motionObj._type)
+            when SPRITE, LABEL, MAP, EXMAP, PRIMITIVE, COLLADA
+                _scenes[object.motionObj._scene].removeChild(object.motionObj.sprite)

     object.motionObj.sprite = undefined
     object.motionObj = undefined


correct return value

correct return value

--- lib/misc.coffee
+++ lib/misc.coffee
@@ -26,4 +26,7 @@
   exclusive: (f) ->
     lock = module.exports.mutex()
     (args...) ->
-      lock (release) => release f.call @, args...
+      lock (release) =>
+        result = f.call @, args...
+        release result
+        result


add comment

add comment

--- lib/puml.coffee
+++ lib/puml.coffee
@@ -50,7 +50,6 @@
                             '-charset', 'UTF-8']

   @task.stdin.write(content)
-  # console.log("write content:" + content)
   data = ""
   @task.stdout.on 'data', (chunk)->
     data = data + chunk.toString()
@@ -63,7 +62,7 @@

         diag = diag + "--></g></svg>"
         #same diagram svg data will be trigger times while the diagram initialize
-        #use hash to prevent callbacks error
+        #use hash to prevent callbacks error, but it still not work very well
         hash = md5(diag)
         if plantumlAPI.oldHash != hash
           plantumlAPI.oldHash = hash


Register a context menu command

Register a context menu command

--- lib/tree.coffee
+++ lib/tree.coffee
@@ -7,6 +7,7 @@
     @createView() if @state.attached
     rootView.command 'tree-view:toggle', => @createView().toggle()
     rootView.command 'tree-view:reveal-active-file', => @createView().revealActiveFile()
+    atom.contextMenuMap.add '.tree-view', 'Add file', 'tree-view:add'

   deactivate: ->
     @treeView?.deactivate()


Fixes (Possibly?) a Memory leak.

Fixes (Possibly?) a Memory leak.

--- src/middleware/rewriteHTML.coffee
+++ src/middleware/rewriteHTML.coffee
@@ -72,6 +72,10 @@
             else
                 buffer.pipe(newRes)

+
+            buffer.on "end", () ->
+                buffer = null
+
             buffer.resume()

         next()


Fixes too much .coffee and not enough .js

--- lib/util.coffee
+++ lib/util.coffee
@@ -6,8 +6,8 @@
     obj = {}
     for file in fs.readdirSync dir
       continue if file is 'index.coffee' or file[0] is '.'
-      name = path
-        .basename(file, '.coffee')
+      name = file
+        .replace(/\.[^.]*$/, '')
         .replace /^([a-z])|-([a-z])/g, (a, b, c) -> (b or c).toUpperCase()
       obj[name] = require "#{dir}/#{file}"
     obj


Specify a content length for batched requests

Makes sure the full body gets sent

--- src/kissmetrics-batch.coffee
+++ src/kissmetrics-batch.coffee
@@ -90,6 +90,8 @@
     urlToSign = "http://#{@HOST}/#{urlPath}"
     signature = @_generateSignature urlToSign, apiSecret

+    requestBody = JSON.stringify {data: queue.get()}
+
     request = http.request
       method: @HTTP_METHOD
       host: @HOST
@@ -97,8 +99,9 @@
       headers:
         'X-KM-ApiKey': apiKey
         'Connection': 'close'
+        'Content-Length': requestBody.length

-    request.end JSON.stringify({data: queue.get()})
+    request.end requestBody
     request


hide text input buttons for java files

hide text input buttons for java files

--- src/smc-webapp/editor-html-md/editor-html-md.coffee
+++ src/smc-webapp/editor-html-md/editor-html-md.coffee
@@ -49,7 +49,7 @@
         #     * source editor -- a CodeMirror editor
         #     * preview/contenteditable -- rendered view
         # console.log("HTML_MD_editor", @)
-
+        $('.salvus-editor-textedit-buttonbar').show()
         if @ext == 'html'
             @opts.mode = 'htmlmixed'
         else if @ext == 'md'
@@ -62,7 +62,9 @@
             # canonicalize .wiki and .mediawiki (as used on github!) to "mediawiki"
             @ext = "mediawiki"
             @opts.mode = 'mediawiki'
-        else if @ext != 'java'
+        else if @ext == 'java'
+            $('.salvus-editor-textedit-buttonbar').hide()
+        else
             throw Error('file must have extension md, html, rmd, rst, tex, java, or wiki')

         @disable_preview = @local_storage("disable_preview")


Add missing welcome page translation for Korean

--- def/ko/welcome.cson
+++ def/ko/welcome.cson
@@ -1,29 +1,29 @@
 Welcome:
-  tabTitle: "Welcome"
+  tabTitle: "환영합니다"
   subtitle: {
-    text1: "A hackable text editor for the 21"
-    superscript: "st"
-    text2: " Century"
+    text1: "해킹할 수 있는 21세기 텍스트 편집기"
+    superscript: ""
+    text2: " "
   }
   help: {
-    forHelpVisit: "For help, please visit"
+    forHelpVisit: "도움이 필요하시다면, 다음을 참고해 주세요."
     atomDocs: {
-      text1: "The "
+      text1: "설명서 또는 API 참고 문서는 "
       link: "Atom docs"
-      text2: " for Guides and the API reference."
+      text2: " 에서 확인해주세요."
       _template: "${text1}${link}${text2}"
     }
     atomForum: {
-      text1: "The Atom forum at "
+      text1: "Atom 포험은 "
       link: "discuss.atom.io"
-      text2: ""   # optional
+      text2: " 에서 확인해주세요."   # optional
       _template: "${text1}${link}${text2}"
     }
     atomOrg: {
-      text1: "The "
+      text1: "GitHub에 생성된 모든 Atom의 패키지를 찾으시려면 "
       link: "Atom org"
-      text2: ". This is where all GitHub-created Atom packages can be found."
+      text2: " 에서 확인해주세요."
       _template: "${text1}${link}${text2}"
     }
   }
-  showWelcomeGuide: "Show Welcome Guide when opening Atom"
+  showWelcomeGuide: "Atom 시작 시, 환영 가이드 보기"


ContentSearch: Pass workspace and machine to content search view.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1059,7 +1059,8 @@

     return @contentSearch.findInput.setFocus()  if @contentSearch

-    @contentSearch = new IDEContentSearch
+    data = machine: @mountedMachine, workspace: @workspaceData
+    @contentSearch = new IDEContentSearch {}, data
     @contentSearch.once 'KDObjectWillBeDestroyed', => @contentSearch = null
     @contentSearch.once 'ViewNeedsToBeShown', (view) =>
       @activeTabView.emit 'ViewNeedsToBeShown', view


IDE: Minor refactor.

IDE: Minor refactor.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1648,18 +1648,24 @@

   makeReadOnly: ->

+    appView = @getView()
+
     ideView.isReadOnly = yes  for ideView in @ideViews
     @forEachSubViewInIDEViews_ (pane) -> pane.makeReadOnly()
     @finderPane.makeReadOnly()
-    @getView().setClass 'read-only'
+
+    appView.setClass 'read-only'

   makeEditable: ->
+
+    appView = @getView()

     ideView.isReadOnly = no  for ideView in @ideViews
     @forEachSubViewInIDEViews_ (pane) -> pane.makeEditable()
     @finderPane.makeEditable()
-    @getView().unsetClass 'read-only'
+
+    appView.unsetClass 'read-only'

   deleteWorkspaceRootFolder: (machineUId, rootPath) ->


Fixed missing custom type sha256

Fixed missing custom type sha256

--- dl/src/chain/serializer_operation_types.coffee
+++ dl/src/chain/serializer_operation_types.coffee
@@ -33,6 +33,9 @@
 When updating generated code
 Replace:  operation = static_variant [
 with:     operation.st_operations = [
+
+replace: sha256
+with: bytes 32

 Delete:
 operation  = new Serializer(
@@ -920,7 +923,7 @@
     fee: asset
     amount: asset
     from: protocol_id_type "account"
-    blinding_factor: sha256
+    blinding_factor: bytes 32
     outputs: array blind_output
 )

@@ -942,7 +945,7 @@
     fee: asset
     amount: asset
     to: protocol_id_type "account"
-    blinding_factor: sha256
+    blinding_factor: bytes 32
     inputs: array blind_input
 )


cli/generate: fix typo.

cli/generate: fix typo.

--- src/cli/generate.coffee
+++ src/cli/generate.coffee
@@ -148,7 +148,7 @@

     # Generate package.json
-    pacakgejs = """
+    packagejs = """
 {
   "name": "#{name}",
   "description": "An awesome real time application",
@@ -159,15 +159,15 @@
   "dependencies": {

 """
-    pacakgejs += "    \"socketstream\": \"0.3.x\""
+    packagejs += "    \"socketstream\": \"0.3.x\""
     mods = selectedFormatters.concat(['hogan'])
     mods.push('console') if program.repl
     mods.forEach (name, i) ->
-      pacakgejs += ",\n    \"ss-#{name}\": \"0.1.x\""
-
-    pacakgejs += "\n  }\n}"
-
-    write('/package.json', pacakgejs)
+      packagejs += ",\n    \"ss-#{name}\": \"0.1.x\""
+
+    packagejs += "\n  }\n}"
+
+    write('/package.json', packagejs)

     # Show finish text
     log "Success! Created app '#{name}' with:".yellow


util-node: add a sales_tax test for a ZIP code

--- src/smc-util-node/test/misc_node-test.coffee
+++ src/smc-util-node/test/misc_node-test.coffee
@@ -48,4 +48,13 @@
             expect(ret).toBe exp
             done()

+describe 'check sales tax work', ->
+    sales_tax = misc_node.sales_tax

+    it 'knows about 98122', ->
+        expect(sales_tax('98122')).toBe 0.10100
+    it 'knows all expected zip codes', ->
+        for zip in [98001..99403]
+            v = sales_tax(zip)
+            expect(v).toBeGreaterThan(0)
+            expect(v).toBeLessThan(0.2000)


Compute more accurate idle time from local information

--- client/timers_client.coffee
+++ client/timers_client.coffee
@@ -28,7 +28,12 @@
   @idleTime: UI.emboxValue ->
     return if TurkServer.isAdmin()
     return unless (instance = Assignments.findOne()?.instances?[0])?
-    return instance.idleTime || 0
+    # If we're idle, add the local idle time (as it's not updated from the server)
+    # TODO add a test for this part - difficult because user-status is a different package
+    idleMillis = (instance.idleTime || 0)
+    if UserStatus.isIdle()
+      idleMillis += TimeSync.serverTime() - UserStatus.lastActivity()
+    return idleMillis

   # Milliseconds this user has been disconnected in the experiment
   @disconnectedTime: UI.emboxValue ->


less sensitive .pager note

less sensitive .pager note

--- scripts/pagerv2_commands.coffee
+++ scripts/pagerv2_commands.coffee
@@ -202,8 +202,9 @@
         data.incident.status,
         "[#{data.incident.service.summary}] "
         )
-      res.send "#{origin}#{data.incident.id} #{data.incident.summary} - #{data.incident.status}" +
-               "#{assigned}"
+      res.send pagerv2.printIncident(data.incident,data.incident.status,robot.adapterName)
+      #"#{origin}#{data.incident.id} #{data.incident.summary} - #{data.incident.status}" +
+      #         "#{assigned}"
     .catch (e) ->
       res.send e
     res.finish()
@@ -234,7 +235,8 @@
             inc.status,
             "[#{inc.service.summary}] "
             )
-          res.send "#{origin}#{inc.id} #{inc.summary} - #{inc.status}#{assigned}"
+          res.send pagerv2.printIncident(inc,inc.status,robot.adapterName)
+          #"#{origin}#{inc.id} #{inc.summary} - #{inc.status}#{assigned}"
       else
         res.send 'There are no open incidents for now.'
     .catch (e) ->
@@ -374,7 +376,7 @@
     res.finish()

 #   hubot pager note <#,#,#> <note> - create a note for incidents <#,#,#>
-  robot.respond /pager note #?([^\s]+) (.*)$/, 'pager_note', (res) ->
+  robot.respond /pager note (?:\s*)?#?([^\s]+) (.*)$/, 'pager_note', (res) ->
     [ _, incident, note ] = res.match
     pagerv2.getPermission(res.envelope.user, 'pageruser')
     .then ->


Core#send

Core#send

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -77,7 +77,7 @@
   o = Core()
     test: true

-  o.send("attrReader", "test")
+  o.send("attrAccessor", "test")

   ok(o.test())


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -50,7 +50,9 @@

     test: true

-  o = Core
+  o = Core()
+
+  o.include()

 test "#include multiple", ->
   o = Core


Added keyword to root

Added keyword to root

--- client/components/Root.coffee
+++ client/components/Root.coffee
@@ -64,7 +64,7 @@
                         <Tab.Pane eventKey="school">
                             <p>
                                 В этом курсе вы научитесь программировать, писать сложные алгоритмы и решать олимпиадные задачи. Вы
-                                подготовитесь к олимпиадам, да и задачи части C ЕГЭ вам покажутся проще. В дальнейшем
+                                подготовитесь к олимпиадам по информатике, да и задачи части C ЕГЭ вам покажутся проще. В дальнейшем
                                 полученные тут знания и умения вам помогут как в вузе, так и в любой деятельности,
                                 хоть как-то связанной с программированием.
                             </p>


Call @handleInput in the #attachFiles promise body

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -86,9 +86,9 @@
   # File verification

   attachFiles: (files) ->
-    @handleInput ->
-      operations = (new Trix.FileVerificationOperation(file) for file in files)
-      Promise.all(operations).then (files) =>
+    operations = (new Trix.FileVerificationOperation(file) for file in files)
+    Promise.all(operations).then (files) =>
+      @handleInput ->
         @delegate?.inputControllerWillAttachFiles()
         @responder?.insertFile(file) for file in files
         @requestRender()


Client: Poison Heal doesn't activate if at full HP

--- server/bw/data/abilities.coffee
+++ server/bw/data/abilities.coffee
@@ -692,6 +692,10 @@
 makeAbility 'Poison Heal', ->
   # Poison damage neutralization is hardcoded in Attachment.Poison and Toxic.
   this::endTurn = ->
+    # Return early so that:
+    # 1. We don't trigger ability activation if the pokemon won't be healed.
+    # 2. Ability activation must happen before HP animation.
+    return  if @pokemon.currentHP == @pokemon.stat('hp')
     if @pokemon.has(Status.Poison) || @pokemon.has(Status.Toxic)
       @pokemon.activateAbility()
       amount = @pokemon.stat('hp') >> 3


File is not longer needed

File is not longer needed

--- coffeescript/average.coffee
+++ coffeescript/average.coffee
@@ -1,5 +0,0 @@
-average = (a) ->
-  (a.reduce ((x, y) -> x + y), 0) / a.length
-
-console.log average [7.5, 20, 6, -50, 88]
-console.log average []


put api/file/content/:path in higher precedence

--- server/mongoose/url/file.coffee
+++ server/mongoose/url/file.coffee
@@ -26,12 +26,12 @@
 	@post api, bearer, newHome(), ensurePermission('write'), ->
 		controller.File.create(@request, @response)

+	@get content, bearer, newHome(), ensurePermission('read'), ->
+		controller.File.open(@request, @response)
+
 	@get api, bearer, newHome(), ensurePermission('read'), ->
 		controller.File.read(@request, @response)

-	@get content, bearer, newHome(), ensurePermission('read'), ->
-		controller.File.open(@request, @response)
-
 	@put api, bearer, newHome(), ensurePermission('write'), ->
 		controller.File.update(@request, @response)


Support loading data from cnx-archive

Support loading data from cnx-archive

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -1,4 +1,5 @@
 define (require) ->
+  _ = require('underscore')
   Backbone = require('backbone')

   MEDIA_TYPES =
@@ -10,10 +11,7 @@

     defaults:
       title: 'Untitled'
-      author:
-        name: 'Unknown'
-        email: '#'
-      body: 'No content'
+      content: 'No content'

   return class Content extends Backbone.Model
     url: () -> "/content/#{@id}"
@@ -41,12 +39,27 @@
       @set('type', type)

       if type is 'book'
-        @set('pages', @get('contents').length)
+        @setupToc()
         @setPage(page or 1) # Default to page 1
       else
         currentPage = @get('currentPage')
         currentPage.id = @id
         currentPage.fetch()
+
+    # Create a flat collection to store all the pages
+    setupToc: () ->
+      toc = new Backbone.Collection()
+
+      _.each @get('tree').contents, (item, index, contents) ->
+        if item.contents
+          _.each item.contents, (module, ind, subcol) ->
+            module.parent = index # Module parents are numbered by their position in the tree
+            toc.add(module)
+        else
+          toc.add(item)
+
+      @set('toc', toc)
+      @set('pages', toc.models.length)

     setPage: (num) ->
       if num < 1 then num = 1
@@ -55,13 +68,14 @@

       @set('page', num)

+      num-- # Page numbers begin at 1, but arrays begin at 0
       currentPage = @get('currentPage')
       currentPage.clear({silent: true}).set(currentPage.defaults) # Reset the current page
-      contents = @get('contents')
-      currentPage.id = contents[num-1].id
+      contents = @get('toc').at(num).toJSON()
+      currentPage.id = contents.id
       currentPage.fetch
         success: (model, response, options) ->
-          currentPage.set('title', contents[num-1].title) if contents[num-1].title
+          #currentPage.set('title', contents[num].title) if contents[num].title

     nextPage: () ->
       currentPage = @get('currentPage')


Activity: minor code styling

Activity: minor code styling

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -17,12 +17,10 @@
       appStorageController
       windowController
       mainView
-    }             = KD.singletons
-    {entryPoint}  = KD.config
-
+    }            = KD.singletons
+    {entryPoint} = KD.config
     @sidebar     = mainView.activitySidebar
     @appStorage  = appStorageController.storage 'Activity', '2.0'
-
     @panePathMap = {}

     @tabs = new KDTabView
@@ -50,8 +48,6 @@

   viewAppended: ->

-    # @addSubView @groupHeader  unless isKoding()
-    # @addSubView @sidebar
     @addSubView @tabs

     @parent.on 'KDTabPaneActive', =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


reactview: add `updateOptions` to trigger rerender

--- client/app/lib/react/reactview.coffee
+++ client/app/lib/react/reactview.coffee
@@ -1,3 +1,4 @@
+{ merge } = require 'lodash'
 kd       = require 'kd'
 ReactDOM = require 'react-dom'

@@ -9,6 +10,11 @@

     @on 'KDObjectWillBeDestroyed', =>
       ReactDOM.unmountComponentAtNode @getElement()
+
+
+  updateOptions: (newOptions) ->
+    @options = merge {}, @options, newOptions
+    @viewAppended()

   renderReact: ->


Fix reviewResult for empty submit list

Fix reviewResult for empty submit list

--- client/components/ReviewResult.coffee
+++ client/components/ReviewResult.coffee
@@ -315,7 +315,7 @@
 optionsForSimilar =
     urls: (props) ->
         if props.submits and props.me?.admin
-            similar: "similarSubmits/#{props.submits[props.submits.length - 1]._id}"
+            similar: "similarSubmits/#{props.submits[props.submits.length - 1]?._id}"
         else
             {}
     propogateReload: true


share: sagews -- input render

share: sagews -- input render

--- src/smc-webapp/sagews/output.cjsx
+++ src/smc-webapp/sagews/output.cjsx
@@ -88,8 +88,16 @@

     render_raw_input: (value, key) ->
+        {prompt, value} = value

-            raw input render not yet implemented
+            {prompt}
+            <input
+                style       = {padding: '0em 0.25em', margin: '0em 0.25em'}
+                type        = 'text'
+                size        = {Math.max(47, value.length + 10)}
+                readOnly    = {true}
+                value       = {value}
+            />

     render_output_mesg: (elts, mesg) ->


invoke children scope directly when tag is syncronious

--- src/xml.coffee
+++ src/xml.coffee
@@ -12,7 +12,12 @@
     pipe = {}
     opts.level = @level+1
     opts = deep_merge @opts, opts # possibility to overwrite existing opts, like pretty
-    @pending.push tag = new @Tag name, attrs, children, opts
+
+    { direct } = opts
+    _children = children
+    _children = undefined if direct
+
+    @pending.push tag = new @Tag name, attrs, _children, opts

     tag.up = (opts = {}) => # set parent
         opts.end ?= true
@@ -70,6 +75,7 @@
         return

     @emit 'add', tag
+    tag.children children, opts if direct
     return tag


fixed emitting events (parent tags stop propagating events when their child tag is closed)

--- src/xml.coffee
+++ src/xml.coffee
@@ -103,6 +103,11 @@
         @$tag = sync_tag
         @tag = new_tag

+    emit: =>
+        if @builder.closed is yes and @parent.closed is yes
+            @builder.emit arguments...
+        else super
+
     attr: (key, value) =>
         if typeof key is 'string'
             return @attrs[key] if @attrs[key] and not value


adds textwrap support for multiple spaces

--- src/textwrap/helpers/tspan.coffee
+++ src/textwrap/helpers/tspan.coffee
@@ -96,12 +96,28 @@
     next_char = ""
     if reverse
       next_char = vars.text.current.charAt(vars.text.current.length - progress.length - 1)
-      joiner    = if next_char is " " then " " else ""
+      if next_char is " "
+        joiner = ""
+        i = 2
+        while next_char is " "
+          joiner += " "
+          next_char = vars.text.current.charAt(vars.text.current.length - progress.length - i)
+          i++
+      else
+        joiner = ""
       progress  = word + joiner + progress
       textBox.text word + joiner + current
     else
       next_char = vars.text.current.charAt(progress.length)
-      joiner    = if next_char is " " then " " else ""
+      if next_char is " "
+        joiner = ""
+        i = 1
+        while next_char is " "
+          joiner += " "
+          next_char = vars.text.current.charAt(progress.length + i)
+          i++
+      else
+        joiner = ""
       progress += joiner + word
       textBox.text current + joiner + word

@@ -135,8 +151,10 @@
       unsafe = true
       while unsafe
         next_char = vars.text.current.charAt(progress.length)
-        if next_char is " "
-          next_char = vars.text.current.charAt(progress.length + 1)
+        i = 1
+        while next_char is " "
+          next_char = vars.text.current.charAt(progress.length + i)
+          i++
         unsafe = vars.text.split.value.indexOf(next_char) >= 0
         placeWord next_char if unsafe


Always insert empty markers in the middle of existing children

We should only favor the next child if it has a leftmost empty leaf and
we’re inserting at the *end* of the current child.

Signed-off-by: Max Brunsfeld <78036c9b69b887700d5846f26a788d53b201ffbb@gmail.com>

--- src/marker-index.coffee
+++ src/marker-index.coffee
@@ -22,7 +22,7 @@
       child = @children[i]
       childEnd = childStart.traverse(child.extent)
       if rangeIsEmpty or child.hasEmptyRightmostLeaf()
-        childIntersectsRange = childEnd.compare(start) >= 0 and not @children[i + 1]?.hasEmptyLeftmostLeaf()
+        childIntersectsRange = childEnd.compare(start) > 0 or (childEnd.compare(start) is 0 and not @children[i + 1]?.hasEmptyLeftmostLeaf())
       else
         childIntersectsRange = childEnd.compare(start) > 0


Blank rawElapsed is correctly filled in again

--- src/models/punch.coffee
+++ src/models/punch.coffee
@@ -139,7 +139,7 @@
       if elapsed < 0
         Logger.error 'Invalid punch row: elapsed time is less than 0', new Error(datetimes)
         return
-      else if elapsed isnt rawElapsed and Math.abs(elapsed - rawElapsed) > 0.02
+      else if elapsed isnt rawElapsed and (rawElapsed is undefined or Math.abs(elapsed - rawElapsed) > 0.02)
         Logger.debug "#{row[headers.id]} - Updating totalTime because #{elapsed} is not #{rawElapsed} - #{Math.abs(elapsed - rawElapsed)}"
         hours = Math.floor elapsed
         minutes = Math.round((elapsed - hours) * 60)


Pump: fixed error log format in debug mode

--- src/Pump.coffee
+++ src/Pump.coffee
@@ -182,7 +182,7 @@
       @on 'error', -> reject 'Pumping failed. See .errorBuffer() contents for error messages'

   logErrorsToConsole: ->
-    @errorBuffer().on 'write', (errorRecord) ->
+    @errorBuffer().on 'write', (errorRecord) =>
       name = errorRecord.pump ? '(root)'
       if @_debug
         console.log "Error in pump #{name}:"


Use removeListener

Use removeListener

--- components/GetSource.coffee
+++ components/GetSource.coffee
@@ -44,7 +44,7 @@
   unsubscribe: (runtime) ->
     @sources = {}
     @pending = []
-    runtime.off 'component', @handleMessage
+    runtime.removeListener 'component', @handleMessage
     @runtime = null

   handleMessage: (message) =>


Make error handling less aggressive

Make error handling less aggressive

--- components/SCDDetect.coffee
+++ components/SCDDetect.coffee
@@ -17,17 +17,12 @@
   out = fs.createWriteStream tmpFile.path
   stream = canvas.pngStream()
   stream.on 'data', (chunk) ->
-    out.write chunk
-  stream.on 'error', (err) ->
-    console.log 'stream error', err
-    callback err
-    tmpFile.unlink()
+    out.write(chunk)
   stream.on 'end', () ->
     try
       onEnd tmpFile, cascade, callback
-    catch err
-      console.log 'stream end error', err
-      callback err
+    catch e
+      callback e
       tmpFile.unlink()

 onEnd = (tmpFile, cascade, callback) ->
@@ -38,9 +33,6 @@
     console.log 'stderr', stderr
     console.log 'err', err
     tmpFile.unlink()
-    if stderr
-      callback stderr
-      return
     if err
       callback err
       return


Improving error handlers.

Improving error handlers.

--- src/polvo/config.coffee
+++ src/polvo/config.coffee
@@ -76,16 +76,19 @@

   setup:( config )=>

-    return unless @validate_server config
-    return unless @validate_sources config
-    return unless @validate_excludes config
-    return unless @validate_includes config
-    return unless @validate_destination config
-    return unless @validate_wrappers config
-    return unless @validate_vendors config
+    passed = true
+    passed and= a = @validate_server config
+    passed and= b = @validate_sources config
+    passed and= c = @validate_excludes config
+    passed and= d = @validate_includes config
+    passed and= e = @validate_destination config
+    passed and= f = @validate_wrappers config
+    passed and= g = @validate_vendors config

-    @confs.push config
-
+    unless passed
+      process.exit()
+    else
+      @confs.push config

   validate_server:( config )->
@@ -101,7 +104,7 @@
     return yes

   validate_sources:( config )->
-    if config.sources is null or config.sources.length is 0
+    unless config.sources? and config.sources.length
       msg = 'You need to inform at least one source, check your config file.'
       return error msg

@@ -125,7 +128,7 @@
     return yes

   validate_destination:( config )->
-    if config.destination is null
+    unless config.destination?
       msg = 'You need to inform a destination folder, check your config file.'
       return error msg

@@ -151,7 +154,7 @@

   validate_wrappers:( config )->
-    if config.wrappers is null
+    unless config.wrappers?
       config.wrappers = javascript: 'amd', style: 'amd'
     else
       config.wrappers.javascript ?= 'amd'
@@ -161,7 +164,7 @@

    # vendors (optional)
   validate_vendors:( config )->
-    for vname, vpath of config.vendors.javascript
+    for vname, vpath of config?.vendors?.javascript
       # skip cdn vendors or incompatible vendors
       if (/^http/m.test vpath) or (vname is 'incompatible')
         continue


AMQP: Dont requeue on NACK

AMQP: Dont requeue on NACK

Require for deadlettering to work on roundrobin

--- src/amqp.coffee
+++ src/amqp.coffee
@@ -106,7 +106,7 @@
   nackMessage: (message) ->
     fields = message.amqp.fields
     debug 'NACK', fields.routingKey, fields.deliveryTag
-    @channel.nack message.amqp, false
+    @channel.nack message.amqp, false, false

   # Participant registration
   registerParticipant: (part, callback) ->


Making websocket authorization secure.

Making websocket authorization secure.

--- src/server/index.coffee
+++ src/server/index.coffee
@@ -122,38 +122,55 @@
                 io.set('browser client gzip', true)
             io.set('log level', 1)
             io.set 'authorization', (handshakeData, callback) =>
-                #Ashima - Browsers can turn off sending of the referer header and is this secure enough anyway?
-                referer = handshakeData.headers.referer.split('\/')
-                if referer
-                    mountPoint = "/" + referer[3]
-                    isAuthenticationReq = (referer[4] == "authenticate" or referer[4] == "password_reset")
-                    app = applicationManager.find(mountPoint)
-                    if app.authenticationInterface && not isAuthenticationReq
-                        handshakeData.cookie = ParseCookie(handshakeData.headers.cookie)
-                        handshakeData.sessionID = handshakeData.cookie['cb.id']
-                        @mongoStore.get handshakeData.sessionID, (err, session) ->
-                            if err
-                                callback(err.message, false)
-                            else
-                                if session.user
-                                    handshakeData.session = session
-                                    #Using anything other than null sends 500 instead of 403
-                                    callback(null, true)
-                                else
-                                    callback(null, false)
-                    else
-                        callback null, true
-                else
-                    callback null, false
+                handshakeData.cookie = ParseCookie(handshakeData.headers.cookie)
+                handshakeData.sessionID = handshakeData.cookie['cb.id']
+                @mongoStore.get handshakeData.sessionID, (err, session) ->
+                    if err then callback(null, false)
+                    else if session.user
+                        handshakeData.session = session
+                    callback(null, true)

         io.sockets.on 'connection', (socket) =>
             @addLatencyToClient(socket) if @config.simulateLatency
             socket.on 'auth', (app, browserID) =>
                 # app, browserID are provided by the client and cannot be trusted
                 decoded = decodeURIComponent(browserID)
-                bserver = browserManagers[app].find(decoded)
-                bserver?.addSocket(socket)
+                if browserManagers[app] and @isAuthorized(socket.handshake.session, app, browserID)
+                    bserver = browserManagers[app].find(decoded)
+                    bserver?.addSocket(socket)
+                else
+                    socket.disconnect()
         return io
+
+    isAuthorized : (session, mountPoint, browserID) ->
+
+        if /landing_page$/.test(mountPoint)
+            mountPoint  = mountPoint.split("/")
+            mountPoint.pop()
+            mountPoint = mountPoint.join('/')
+            appUser = session.user.filter (item) ->
+                item.app is mountPoint
+            if appUser[0] then return true
+            else return false
+
+        app = @applicationManager.find(mountPoint)
+
+        if not app
+            return false
+        else if not app.authenticationInterface
+            return true
+        else if not session.user
+            return false
+        else
+            appUser = session.user.filter (item) ->
+                item.app is mountPoint
+            if appUser[0]
+                @permissionManager.findBrowserPermRec appUser[0],
+                mountPoint, browserID, (browserRec) ->
+                    if browserRec and Object.keys(browserRec).length isnt 0
+                        return true
+                    else return false
+            else return false

     addLatencyToClient : (socket) ->
         if typeof @config.simulateLatency == 'number'


Remove commented heuristic

Remove commented heuristic

--- src/set/set_sort.coffee
+++ src/set/set_sort.coffee
@@ -39,9 +39,6 @@
     @fire('itemWasMoved', item, newIndex, oldIndex)

   _handleItemsAdded: (items) ->
-    # if items.length > Math.log(@_storage.length) * 5
-    #   @_reIndex()
-    # else
     newStorage = @_storage.slice()
     addedItems = []
     addedIndexes = []


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/function_extensions.coffee
+++ test/function_extensions.coffee
@@ -28,7 +28,7 @@
       score += 100
     ).once()

-
+  exp

 test "#returning", ->
   x = 0


return random armour from repo

return random armour from repo

--- coffee/armorRepository.coffee
+++ coffee/armorRepository.coffee
@@ -1,5 +1,7 @@
 define [], () ->
   class ArmorRepository
     constructor: (@armors) ->
-    getArmor: () -> @armors[0]
+    getArmor: () ->
+      index = Math.floor(Math.random() * @armors.length)
+      @armors[index]
   return ArmorRepository


Line was too long

Line was too long

--- coffeescript/wordcount.coffee
+++ coffeescript/wordcount.coffee
@@ -9,4 +9,5 @@
     counts[word] = (counts[word] or 0) + 1

 process.stdin.on 'end', ->
-  console.log "#{word} #{counts[word]}" for word in Object.keys(counts).sort()
+  for word in Object.keys(counts).sort()
+    console.log "#{word} #{counts[word]}"


Add maneuver dials to German card info.

Add maneuver dials to German card info.

* Must unify this.


Verify if enemy is in playing field before hit


Re-enabling test for setter-only property

--- test/tests/BaseClassTest.coffee
+++ test/tests/BaseClassTest.coffee
@@ -181,7 +181,7 @@

 		props.hasOwnProperty("testProp").should.be.false

-	it.skip "should throw on assignment of read-only prop", ->
+	it "should throw on assignment of read-only prop", ->
 		class TestClass extends Framer.BaseClass
 			@define "testProp",
 				get: -> "value"


Add change listener on source for updates

--- bokehjs/src/coffee/models/annotations/label.coffee
+++ bokehjs/src/coffee/models/annotations/label.coffee
@@ -20,8 +20,14 @@
     if @mget('render_mode') == 'css'
       # dispatch CSS update immediately
       @listenTo(@model, 'change', @render)
+      @listenTo(@mget('source'), 'change', () ->
+        @set_data()
+        @render())
     else
       @listenTo(@model, 'change', @plot_view.request_render)
+      @listenTo(@mget('source'), 'change', () ->
+        @set_data()
+        @plot_view.request_render())

   set_data: () ->
     super(@mget('source'))


Add padding to handle text height measurement error

--- bokehjs/src/coffee/models/annotations/title.coffee
+++ bokehjs/src/coffee/models/annotations/title.coffee
@@ -17,11 +17,11 @@
         vx = 0
         vy = @_get_text_location(@mget('title_alignment'), 'height') + @mget('title_padding')
       when 'right'
-        vx = @canvas.get('right')
+        vx = @canvas.get('right') - 1 #fudge factor due to error in text height measurement
         vy = @canvas.get('height') - @_get_text_location(@mget('title_alignment'), 'height') + @mget('title_padding')
       when 'above'
         vx = @_get_text_location(@mget('title_alignment'), 'width') + @mget('title_padding')
-        vy = @canvas.get('top')
+        vy = @canvas.get('top') - 1 #fudge factor due to error in text height measurement
       when 'below'
         vx = @_get_text_location(@mget('title_alignment'), 'width') + @mget('title_padding')
         vy = 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -72,7 +72,7 @@
   o = GameObject
   o.bind "destroy", ->
     ok true, "destroyed event is fired on destroy"
-  .destroy()
+  o.destroy()

   GameObject
     destroy: 'ok(true, "destroyed event is fired on destroy")'


improve sample test code

improve sample test code

--- test/scripts/xxx.coffee
+++ test/scripts/xxx.coffee
@@ -16,6 +16,6 @@
       @kakashi
         .receive sender, message
         .then =>
-          expect(@kakashi.send.callCount).to.equal(1)
+          expect(@kakashi.send).to.have.callCount(1)
           expect(@kakashi.send.firstCall.args[1]).to.equal('XXX!')
         .then (-> done()), done


Fix duplicated legends

Fix duplicated legends

--- static/coffee/locations_v2.coffee
+++ static/coffee/locations_v2.coffee
@@ -15,6 +15,8 @@
   overallResults = null
   countryResults = null

+  topBoundary = null
+
   topCategory = null
   otherCategory = null
   displayOthers = false


Use default branch in project page controller

--- static/scripts/controllers.coffee
+++ static/scripts/controllers.coffee
@@ -102,6 +102,7 @@
                 $scope.project = data
                 if not $scope.branches
                     $scope.branches = data.branches
+                $scope.branch = $scope.project.default_branch
                 ngProgress.complete()
         loadProject()


Updating browser's list

Updating browser's list

--- tests/www/utils/browsers.coffee
+++ tests/www/utils/browsers.coffee
@@ -24,21 +24,21 @@
   #   tags: ['theoricus', 'test', 'chrome', 'win7']
   #   'record-video': true

-  # chrome_win8:
-  #   local: false
-  #   browserName: 'chrome'
-  #   version: chrome_next_to_last
-  #   platform: 'Windows 8'
-  #   tags: ['theoricus', 'test', 'chrome', 'win8']
-  #   'record-video': true
-
-  # chrome_linux:
-  #   local: false
-  #   browserName: 'chrome'
-  #   version: chrome_last
-  #   platform: 'Linux'
-  #   tags: ['theoricus', 'test', 'chrome', 'linux']
-  #   'record-video': true
+  chrome_win8:
+    local: false
+    browserName: 'chrome'
+    version: chrome_next_to_last
+    platform: 'Windows 8'
+    tags: ['theoricus', 'test', 'chrome', 'win8']
+    'record-video': true
+
+  chrome_linux:
+    local: false
+    browserName: 'chrome'
+    version: chrome_last
+    platform: 'Linux'
+    tags: ['theoricus', 'test', 'chrome', 'linux']
+    'record-video': true

   # chrome_snow:
   #   local: false
@@ -51,7 +51,7 @@
   chrome_mountain:
     local: true
     browserName: 'chrome'
-    # version: chrome_last
+    version: chrome_next_to_last
     platform: 'OS X 10.8'
     tags: ['theoricus', 'test', 'chrome', 'mountain']
     'record-video': true
@@ -68,17 +68,17 @@
   #   tags: ['theoricus', 'test', 'safari5', 'winxp']
   #   'record-video': true

-  # # # safari5 on snow reports false `phishing's alert` when
-  # # # opening page through sauce connect
-  # # safari_snow:
-  # #   local: false
-  # #   version: '5'
-  # #   browserName: 'safari'
-  # #   platform: 'OS X 10.6'
-  # #   tags: ['theoricus', 'test', 'safari5', 'snow']
-  # #   'record-video': true
-
-  safari_mountain:
+  # safari5 on snow reports false `phishing's alert` when
+  # opening page through sauce connect
+  safari5_snow:
+    local: false
+    version: '5'
+    browserName: 'safari'
+    platform: 'OS X 10.6'
+    tags: ['theoricus', 'test', 'safari5', 'snow']
+    'record-video': true
+
+  safari6_mountain:
     local: true
     version: '6'
     browserName: 'safari'
@@ -106,21 +106,21 @@
   #   tags: ['theoricus', 'test', 'firefox', 'win7']
   #   'record-video': true

-  # firefox_win8:
-  #   local: false
-  #   browserName: 'firefox'
-  #   version: firefox_last
-  #   platform: 'Windows 8'
-  #   tags: ['theoricus', 'test', 'firefox', 'win8']
-  #   'record-video': true
-
-  # firefox_linux:
-  #   local: false
-  #   browserName: 'firefox'
-  #   version: firefox_last
-  #   platform: 'Linux'
-  #   tags: ['theoricus', 'test', 'firefox', 'linux']
-  #   'record-video': true
+  firefox_win8:
+    local: false
+    browserName: 'firefox'
+    version: firefox_last
+    platform: 'Windows 8'
+    tags: ['theoricus', 'test', 'firefox', 'win8']
+    'record-video': true
+
+  firefox_linux:
+    local: false
+    browserName: 'firefox'
+    version: firefox_last
+    platform: 'Linux'
+    tags: ['theoricus', 'test', 'firefox', 'linux']
+    'record-video': true

   # firefox_snow:
   #   local: false


SocialWorker: remove commented out line

SocialWorker: remove commented out line

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -874,7 +874,6 @@
     success: (client, callback) ->
       JApiToken = require '../apitoken'
       selector  = { group : @getAt 'slug' }
-      #options   = { createdAt : 1, code : 1, originId : 1 }

       JApiToken.some selector, {}, (err, apiTokens) ->
         return callback err, []  if err or not apiTokens


new fair descriptions

new fair descriptions

--- models/mixins/meta_overrides.coffee
+++ models/mixins/meta_overrides.coffee
@@ -22,6 +22,18 @@

 metaOverrides = (model) ->

+  'fog-design-plus-art-2016':
+    title: 'FOG Art + Design 2016 | Artsy'
+    description: 'Amid a burgeoning West Coast art scene, preview this fair of 43+ dealers—Marian Goodman Gallery, David Gill Gallery, and Salon 94 among them—from the Bay Area and beyond.'
+
+  'art-stage-singapore-2016':
+    title: 'Art Stage Singapore  2016 | Artsy'
+    description: 'Explore work from Asia and beyond as 140+ regional and international galleries—Tomio Koyama Gallery, Whitestone Gallery, and amanasalto among them—gather in Singapore.'
+
+  'art-los-angeles-contemporary-2016':
+    title: 'Art Los Angeles Contemporary 2016 | Artsy'
+    description: 'Find out which works The Hole, MOT International, and Galerie Christina Lethert are bringing to the West Coast.'
+
   'design-miami-2015':
     title: 'Design Miami/ 2015 | Artsy'
     description: 'From established modernists to emerging desginers, your first look at the preeminent design fair in Miami'


 Merge branch 'master.context' into 'master'

context: invoke change group if entry point is a group

--- client/Main/maincontroller/groupscontroller.coffee
+++ client/Main/maincontroller/groupscontroller.coffee
@@ -21,7 +21,9 @@
       then router.handleRoute "#{pageInfo.path}"
       else router.handleRoute "#{pageInfo.path}", {entryPoint}

-    mainController.ready => @changeGroup entryPoint?.slug
+    mainController.ready =>
+      {slug} = entryPoint  if entryPoint?.type is "group"
+      @changeGroup slug

   getCurrentGroup:->
     throw 'FIXME: array should never be passed'  if Array.isArray @currentGroupData.data


Client: Remove unnecessary throw.

Client: Remove unnecessary throw.

--- client/app/js/collections/battle_collection.coffee
+++ client/app/js/collections/battle_collection.coffee
@@ -212,7 +212,6 @@
       console.error(e)
       console.error(e.stack)
       done()
-      throw e  if document.domain == 'localhost'
     if wasAtBottom && !view.chatView.isAtBottom()
       view.chatView.scrollToBottom()


attempting new test case

attempting new test case


Add copyright blurb

Add copyright blurb

--- rembrant.coffee
+++ rembrant.coffee
@@ -1,3 +1,8 @@
+# Rembrant - photo gallery software
+# (c) 2011 - Honza Pokorny - All rights reserved
+# Freely available under the terms of the BSD license
+# https://github.com/honza/rembrant
+
 fs = require 'fs'
 path = require 'path'
 _ = require 'underscore'


Use KDSplitView in layout builder.

Use KDSplitView in layout builder.

It was using SplitViewWithOlderSiblings before but it has issues with resizing.

--- client/ide/workspace/workspacelayoutbuilder.coffee
+++ client/ide/workspace/workspacelayoutbuilder.coffee
@@ -37,7 +37,7 @@

         views.push wrapper

-    SplitViewClass = @getOptions().splitViewClass or SplitViewWithOlderSiblings
+    SplitViewClass = @getOptions().splitViewClass or KDSplitView
     splitView      = new SplitViewClass { type, sizes, views, cssClass }
     @splitViews[@baseSplitName] = splitView  if @baseSplitName


fix specs for updated launcher

fix specs for updated launcher

--- coach/specs/concept-coach/launcher/index.spec.cjsx
+++ coach/specs/concept-coach/launcher/index.spec.cjsx
@@ -28,7 +28,7 @@
     User.isLoggedIn.mockReturnValue(true)
     wrapper = shallow(<Launcher {...@props} />)
     expect(wrapper.find('LoginAction[isVisible=false]')).to.have.length(1)
-    expect(wrapper.find('.btn-openstax-primary').childAt(0).text() ).to.eq('Enroll in This Course')
+    expect(wrapper.find('Enroll')).to.have.length(1)
     expect(SnapShot.create(<Launcher {...@props} />).toJSON()).toMatchSnapshot()
     undefined

@@ -41,9 +41,9 @@
     undefined

   it 'calls callbacks when buttons clicked', ->
-    wrapper = shallow(<Launcher {...@props} />)
+    wrapper = mount(<Launcher {...@props} />)
     wrapper.find('LoginAction').prop('onLogin')()
     expect(@props.onLogin).toHaveBeenCalled()
-    wrapper.find('.btn-openstax-primary').simulate('click')
+    wrapper.find('Enroll').simulate('click')
     expect(@props.onEnroll).toHaveBeenCalled()
     undefined


Fixed HTML tab in bulk email feature on firefox. LMS-2275

--- common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -96,6 +96,10 @@
     $currentTarget = $(e.currentTarget)
     if not $currentTarget.hasClass('current')
       $currentTarget.addClass('current')
+
+      # Initializing $mceToolbar if undefined.
+      if not @$mceToolbar?
+        @$mceToolbar = $(@element).find('table.mceToolbar')
       @$mceToolbar.toggleClass(HTMLEditingDescriptor.isInactiveClass)
       @$advancedEditorWrapper.toggleClass(HTMLEditingDescriptor.isInactiveClass)


bugfix: jar-args may not be present

bugfix: jar-args may not be present

--- console/runner.coffee
+++ console/runner.coffee
@@ -149,7 +149,7 @@
     cname = find_main_class(jar_dir) unless cname?
     unless cname?
       console.error "No main class provided and no Main-Class found in #{argv.jar}"
-    main_args = (argv['jar-args'].toString().trim().split /\s+/) or []
+    main_args = (argv['jar-args']?.toString().trim().split /\s+/) or []
   else
     main_args = argv._[1..]


add better test coverage for stream classes

--- test/stream.test.coffee
+++ test/stream.test.coffee
@@ -1,33 +1,111 @@
 osc = require "../"
-should = require "should"
+expect = require "expect.js"

 describe "OSC UnpackStream", ->
-  it "should emit message", (done)->
-    stream = new osc.UnpackStream

-    stream.on "message", (msg)->
-      msg.address.should.be.equal "/a2r"
-      msg.arguments[0].should.be.equal 5
+  describe ".write", ->
+    it "should emit message if write is called with an OSC buffer", (done)->
+      stream = new osc.UnpackStream
+
+      stream.on "message", (msg)->
+        expect(msg.address).to.be "/a2r"
+        expect(msg.arguments[0]).to.be 5
+        done()
+
+      stream.write new osc.Message("/a2r", "i", 5).toBuffer()
+
+    it "should emit an error if write is called with invalid data", (done)->
+      stream = new osc.UnpackStream
+
+      stream.on "error", (err)->
+        expect(err).to.be.an Error
+        done()
+
+      stream.write "..."
+
+    it "should pass the optional dictionary to the parser", (done)->
+      dict = new osc.Dictionary(1: "/a2r")
+      stream = new osc.UnpackStream(dict)
+
+      stream.on "message", (msg)->
+        expect(msg.address).to.be "/a2r"
+        expect(msg.typeTag).to.be "f"
+        expect(msg.arguments).to.have.length 1
+        done()
+
+      stream.write new osc.Message("/")
+        .add("i", 1)
+        .add(12.5)
+        .toBuffer()
+
+  describe ".end", ->
+    stream = null
+
+    beforeEach -> stream = new osc.UnpackStream()
+
+    it "should emit `close`", (done)->
+      stream.on("close", done)
+      stream.end()
+
+    it "should write data before calling end if called with data", (done)->
+      called = false
+
+      stream.on "close", ->
+        expect(called).to.be true
+        done()
+
+      stream.on "message", -> called = true
+
+      stream.end new osc.Message("/test", osc.Impulse).toBuffer()
+
+describe "OSC PackStream", ->
+
+  describe ".send", ->
+
+    it "should emit `data` with buffer of packed message", (done)->
+      stream = new osc.PackStream
+
+      stream.on "data", (data)->
+        msg = osc.fromBuffer(data)
+        expect(msg.equal(message)).to.be true
+        done()
+
+      message = new osc.Message("/test", osc.Impulse)
+      stream.send message
+
+    it "should emit `error` with error if something went wrong", (done)->
+      stream = new osc.PackStream
+
+      stream.on "error", (error)->
+        expect(error).to.be.an Error
+        done()
+
+      stream.send "foo bar"
+
+    it "should pass the dictionary to the Message::toBuffer method", (done)->
+      dict = new osc.Dictionary(1: "/a2r")
+      stream = new osc.PackStream(dict)
+
+      stream.on "data", (data)->
+        msg = osc.fromBuffer(data)
+        expect(msg.address).to.be "/"
+        expect(msg.arguments).to.have.length 2
+        expect(msg.arguments[0]).to.be 1
+        done()
+
+      stream.send new osc.Message("/a2r", 56)
+
+describe "pipe messages from pack stream to unpack stream", ->
+
+  it "should pass the message through the pipe", (done)->
+    from = new osc.PackStream
+    to   = new osc.UnpackStream
+
+    to.on "message", (msg)->
+      expect(msg.equal(message)).to.be true
       done()

-    stream.write new osc.Message("/a2r", "i", 5).toBuffer()
+    from.pipe(to)

-  it "should emit error if write is called with invalid data", (done)->
-    stream = new osc.UnpackStream
-
-    stream.on "error", (err)->
-      done()
-
-    stream.write "..."
-
-  it "should pass the optional dictionary to the parser", (done)->
-    dict = new osc.Dictionary(1: "/a2r")
-    stream = new osc.UnpackStream(dict)
-
-    stream.on "message", (msg)->
-      msg.address.should.be.equal "/a2r"
-      msg.typeTag.should.be.equal "f"
-      msg.arguments.should.have.length 1
-      done()
-
-    stream.write new osc.Message("/").add("i", 1).add(12.5).toBuffer()
+    message = new osc.Message("/test", [54, "hello"])
+    from.send(message)


KernelManager: Add more tests for running kernels

--- spec/kernel-manager-spec.coffee
+++ spec/kernel-manager-spec.coffee
@@ -21,18 +21,24 @@

     describe 'handle running kernels', ->
         mockGrammar =
-            name: 'grammarLanguage'
+            name: 'Kernel1'

         mockKernel =
             kernelSpec:
-                language: 'kernelLanguage'
+                language: 'kernel1'
             destroy: ->
+
+        mockKernel2 =
+            kernelSpec:
+                language: 'kernel2'
+            destroy: ->
+
+        mockKernels =
+            kernel1: mockKernel
+            kernel2: mockKernel2

         describe 'destroy', ->
             it 'should destroy all running kernels', ->
-                mockKernels =
-                    kernel1: _.clone mockKernel
-                    kernel2: _.clone mockKernel
                 spyOn(mockKernels.kernel1, 'destroy')
                 spyOn(mockKernels.kernel2, 'destroy')
                 kernelManager._runningKernels = mockKernels
@@ -43,7 +49,14 @@

         describe 'setRunningKernelFor', ->
             it 'should set the running kernel for a grammar', ->
-                kernelManager.setRunningKernelFor mockGrammar, mockKernel
+                grammar =
+                    name: 'grammarLanguage'
+
+                kernel =
+                    kernelSpec:
+                        language: 'kernelLanguage'
+                    destroy: ->
+                kernelManager.setRunningKernelFor grammar, kernel
                 expect(kernelManager._runningKernels.grammarlanguage)
                     .not.toBeUndefined()
                 expect(kernelManager._runningKernels.grammarlanguage
@@ -51,24 +64,31 @@

         describe 'destroyRunningKernelFor', ->
             it 'should destroy a running kernel for a grammar', ->
-                mockKernels =
-                    grammarlanguage: _.clone mockKernel
-                    kernel2: _.clone mockKernel
-
-                spyOn(mockKernels.grammarlanguage, 'destroy')
+                spyOn(mockKernels.kernel1, 'destroy')
                 spyOn(mockKernels.kernel2, 'destroy')
                 kernelManager._runningKernels = _.clone mockKernels
                 kernelManager.destroyRunningKernelFor mockGrammar

-                expect(mockKernels.grammarlanguage.destroy).toHaveBeenCalled()
+                expect(mockKernels.kernel1.destroy).toHaveBeenCalled()
                 expect(mockKernels.kernel2.destroy).not.toHaveBeenCalled()
                 expect(kernelManager._runningKernels.kernel2).not.toBeUndefined()
-                expect(kernelManager._runningKernels.grammarlanguage).toBeUndefined()
-
-
-        it 'should read lower case name from grammar', ->
-            expect(kernelManager.getLanguageFor mockGrammar)
-                .toEqual('grammarlanguage')
+                expect(kernelManager._runningKernels.kernel1).toBeUndefined()
+
+        describe 'getAllRunningKernels', ->
+            it 'should get all running kernels', ->
+                kernelManager._runningKernels = mockKernels
+                expect(kernelManager.getAllRunningKernels()).toEqual(mockKernels)
+
+        describe 'getRunningKernelFor', ->
+            it 'should get the running kernel for a language', ->
+                kernelManager._runningKernels = mockKernels
+                expect(kernelManager.getRunningKernelFor('kernel1'))
+                    .toEqual(mockKernel)
+
+        describe 'getLanguageFor', ->
+            it 'should read lower case name from grammar', ->
+                expect(kernelManager.getLanguageFor mockGrammar)
+                    .toEqual('kernel1')

     describe 'handle kernelspecs', ->
         firstKernelSpecString = '''{


:lipstick: spec

:lipstick: spec

--- spec/workspace-view-spec.coffee
+++ spec/workspace-view-spec.coffee
@@ -127,15 +127,11 @@
       expect(activePane).toHaveFocus()

   describe "keymap wiring", ->
-    commandHandler = null
-    beforeEach ->
-      commandHandler = jasmine.createSpy('commandHandler')
-      atom.workspaceView.on('foo-command', commandHandler)
-
-      atom.keymaps.add('name', '*': {'x': 'foo-command'})
-
     describe "when a keydown event is triggered in the WorkspaceView", ->
       it "triggers matching keybindings for that event", ->
+        commandHandler = jasmine.createSpy('commandHandler')
+        atom.workspaceView.on('foo-command', commandHandler)
+        atom.keymaps.add('name', '*': {'x': 'foo-command'})
         event = keydownEvent 'x', target: atom.workspaceView[0]

         atom.workspaceView.trigger(event)


Allow cancelling the filter chain and action.

--- src/event_emitter/lifecycle_events.coffee
+++ src/event_emitter/lifecycle_events.coffee
@@ -31,13 +31,10 @@
     @[afterName] = addCallback(afterName)
     @::[afterName] = addCallback(afterName)

-fire = (eventName, arg) ->
+fire = (eventName, args...) ->
   return unless handlers = @_batman.get(eventName)
-  result = true

   for {options, callback} in handlers
-    continue if options?.if and !options.if.call(this, arg)
-    continue if options?.unless and options.unless.call(this, arg)
-    result = false if callback.call(this, arg) == false
-
-  return result
+    continue if options?.if and !options.if.apply(this, args)
+    continue if options?.unless and options.unless.apply(this, args)
+    return false if callback.apply(this, args) == false


Make this a drop-in replacement for xmlhttprequest.

--- src/001-xml_http_request.coffee
+++ src/001-xml_http_request.coffee
@@ -647,4 +647,10 @@
       length += buffer.length
     target

+# XMLHttpRequest is the result of require('node-xhr2').
 module.exports = XMLHttpRequest
+
+# Make node-xhr2 work as a drop-in replacement for libraries that promote the
+# following usage pattern:
+#     var XMLHttpRequest = require('xhr-library-name').XMLHttpRequest
+XMHttpRequest.XMLHttpRequest = XMLHttpRequest


add test for Tuple.isHash()

add test for Tuple.isHash()

--- tests/test_tuple.coffee
+++ tests/test_tuple.coffee
@@ -16,6 +16,9 @@

     it 'should return false if String', ->
       assert.equal Tuple.isHash("foo"), false
+
+    it 'should return false if Number', ->
+      assert.equal Tuple.isHash(401), false

     it 'should return false if Array', ->
       assert.equal Tuple.isHash([1,2,3]), false


Remove text annotation warning.

Remove text annotation warning.

Just noise, something that needs to be fixed Marquee-side or during imports.

--- entry/NOAT.coffee
+++ entry/NOAT.coffee
@@ -61,13 +61,10 @@

     _validateRange: (start, end) ->
         if start > end
-            console.error("start (#{ start }) must be <= end (#{ end })")
             return false
         if start < 0
-            console.error("start (#{ start }) must be >= 0")
             return false
         if end > @text.length
-            console.error("end (#{ end }) must be <= length of text (#{ @text.length })")
             return false
         return true


reloadInfo is fat-arrow function (should works)

--- app/assets/javascripts/uploadcare/widget/base-widget.js.coffee
+++ app/assets/javascripts/uploadcare/widget/base-widget.js.coffee
@@ -29,7 +29,7 @@
       @__setupWidget()
       @template.reset()

-      @element.on 'change.uploadcare', => @reloadInfo()
+      @element.on 'change.uploadcare', @reloadInfo
       @reloadInfo()

     __setupWidget: ->


Do not display “starting...” log when --help.

--- src/main.coffee
+++ src/main.coffee
@@ -47,12 +47,12 @@

 # Main.
 exports = module.exports = $coroutine (args) ->
-  $debug 'Starting…'
-
   return exports.help() unless (
     (args.indexOf '--help') is -1 and
     (args.indexOf '-h') is -1
   )
+
+  $debug 'Starting…'

   # Default config.
   opts = {


logic optimization

logic optimization

--- src/main.coffee
+++ src/main.coffee
@@ -128,11 +128,9 @@
                 return true

         [fooOuter, barOuter] = for item in [foo, bar]
-            getOuterRange item, @grid.width(), @grid.height()
-
-        for key, value of fooOuter
-            if value is barOuter[key] is true
-                return true
+            getOuterBits item, @grid.width(), @grid.height()
+
+        return true if fooOuter & barOuter

         [fooMarks, barMarks] = (getMarkBits item for item in [foo, bar])

@@ -183,11 +181,16 @@

         list

-    getOuterRange = (item, width, height) ->
-        top: item.point.y is item.range.top
-        bottom: item.point.y + item.range.bottom + 1 is height
-        left: item.point.x is item.range.left
-        right: item.point.x + item.range.right + 1 is width
+    getOuterBits = (item, width, height) ->
+        result   = 0
+        result  += item.point.y is item.range.top
+        result <<= 1
+        result  += item.point.y + item.range.bottom + 1 is height
+        result <<= 1
+        result  += item.point.x is item.range.left
+        result <<= 1
+        result  += item.point.x + item.range.right + 1 is width
+        result

     getMarkBits = (item) ->
         x: item.range.markX item.point


Use runtime-supplied UUID if available

Use runtime-supplied UUID if available

--- components/PopulateRuntimeData.coffee
+++ components/PopulateRuntimeData.coffee
@@ -1,4 +1,5 @@
 noflo = require 'noflo'
+uuid = require 'uuid'

 buildContext = ->
   ctx =
@@ -21,7 +22,7 @@
     [key, value] = param.split '='
     runtime[key] = value
   if runtime.protocol and runtime.address
-    runtime.id = encodeURIComponent runtime.address
+    runtime.id = runtime.id or uuid.v4()
     return runtime
   null


add additional testcase

add additional testcase


Group alias test together

Group alias test together


Loader: unnecessary mousemove removed

Loader: unnecessary mousemove removed

--- components/loader/KDLoaderView.coffee
+++ components/loader/KDLoaderView.coffee
@@ -49,14 +49,10 @@
   # easter
   mouseEnter:->

+    @canvas.setSpeed 2
     @canvas.setColor @utils.getRandomHex()
-    @canvas.setSpeed 1

   mouseLeave:->

     @canvas.setColor @getOptions().loaderOptions.color
     @canvas.setSpeed @getOptions().loaderOptions.speed
-
-  mouseMove:->
-
-    @canvas.setColor @utils.getRandomHex()


extend error testing coverage

extend error testing coverage

--- test/pagerv2_commands_test.coffee
+++ test/pagerv2_commands_test.coffee
@@ -394,6 +394,21 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd incident 1234"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          room.robot.brain.data.pagerv2 = { users: { } }
+          nock('https://api.pagerduty.com')
+          .get('/incidents/1234')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd incident 1234', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -410,6 +425,22 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd sup"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          room.robot.brain.data.pagerv2 = { users: { } }
+          nock('https://api.pagerduty.com')
+          .get('/incidents?time_zone=UTC&include%5B%5D=first_trigger_log_entry&date_range=all' +
+               '&statuses%5B%5D=triggered&statuses%5B%5D=acknowledged')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd sup', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -427,6 +458,21 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd ack"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .get('/incidents?time_zone=UTC&include%5B%5D=first_trigger_log_entry&date_range=all' +
+               '&statuses%5B%5D=triggered')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd ack', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -461,6 +507,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd ack PT4KHLK"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .put('/incidents')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd ack PT4KHLK', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -477,6 +537,21 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd res"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .get('/incidents?time_zone=UTC&include%5B%5D=first_trigger_log_entry&date_range=all&' +
+               'statuses%5B%5D=acknowledged')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd res', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -511,6 +586,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd res PT4KHLK"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .put('/incidents')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd res PT4KHLK', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -527,6 +616,21 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd assign all to me"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .get('/incidents?time_zone=UTC&include%5B%5D=first_trigger_log_entry&date_range=all' +
+               '&statuses%5B%5D=triggered&statuses%5B%5D=acknowledged')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd assign all to me', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -561,6 +665,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd assign PT4KHLK to me"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .put('/incidents')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd assign PT4KHLK to me', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -577,6 +695,21 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd snooze all"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .get('/incidents?time_zone=UTC&include%5B%5D=first_trigger_log_entry&date_range=all' +
+               '&statuses%5B%5D=triggered&statuses%5B%5D=acknowledged')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd snooze all', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -611,6 +744,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd snooze PT4KHLK"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .post('/incidents/PT4KHLK/snooze')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd snooze PT4KHLK', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -627,6 +774,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd note PT4KHLK some note"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .post('/incidents/PT4KHLK/notes')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd note PT4KHLK some note', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -643,6 +804,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd notes PT4KHLK"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .get('/incidents/PT4KHLK/notes')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd notes PT4KHLK', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -659,6 +834,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd maintenances"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .get('/maintenance windows?filter=ongoing')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd maintenances', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -675,6 +864,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd stfu"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .post('/maintenance windows')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd stfu', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')
@@ -691,6 +894,20 @@

     # ----------------------------------------------------------------------------------------------
     describe '".pd end PW98YIO"', ->
+      context 'when something goes wrong,', ->
+        beforeEach ->
+          nock('https://api.pagerduty.com')
+          .delete('/maintenance windows/PW98YIO')
+          .reply 503, { error: { code: 503, message: "it's all broken!" } }
+        afterEach ->
+          room.robot.brain.data.pagerv2 = { }
+          nock.cleanAll()
+
+        say 'pd end PW98YIO', ->
+          it 'returns the error message', ->
+            expect(hubotResponse())
+            .to.eql "503 it's all broken!"
+
       context 'when everything goes right,', ->
         beforeEach ->
           nock('https://api.pagerduty.com')


Close socket client after test

Close socket client after test

--- test/send_group_email.spec.coffee
+++ test/send_group_email.spec.coffee
@@ -23,6 +23,7 @@
           done()

   after (done) ->
+    socket_client.close()
     common.shutDown(@server, done)

   it "sends email to groups", (done) ->


moved helper functions up to separate them from the tests

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -87,11 +87,8 @@
     }

-  # The tests:
-  #-----------
-
-  it 'should have no default host configured', () ->
-    expect(scope.ponyExpressHost).toBe( window.location.host + "/api" )
+  ## Helpers:
+  ##---------

   paginateResponse = (httpBackend, baseUrl, response, action, limit=50)->
     # 1. working pagination
@@ -120,6 +117,16 @@
     # take the action and flush the backend
     action()
     httpBackend.flush()
+
+
+  # The tests:
+  #-----------
+
+  ## Configuration
+
+  it 'should have no default host configured', () ->
+    expect(scope.ponyExpressHost).toBe( window.location.host + "/api" )
+

   it 'should paginate /nodes if it supports pagination', ()->
     paginateResponse @httpBackend, '/v1/nodes', allNodes1, () -> scope.fetchNodes()


reset steroids.coffee

reset steroids.coffee

--- testApp/config/application.coffee
+++ testApp/config/application.coffee
@@ -25,7 +25,7 @@
   # {
   #   title: "FUT"
   #   icon: "icons/telescope@2x.png"
-  #   location: "http://localhost/views/navigationbar/index.html"
+  #   location: "http://localhost/views/steroids/serve.html"
   # }
   {
     title: "HTTP"


Fix indentation.

Fix indentation.

--- src/spec.coffee
+++ src/spec.coffee
@@ -328,10 +328,12 @@
               # TODO: do not use enter/exit, use "update" instead!
               enter: (task) ->
                 # TODO: better way to discard useless tasks
-                if task.$container is @key and
+                if (
+                  task.$container is @key and
                   task.status is 'pending' and
-                  task.name_label not in ['SR.scan', 'Dumping database as XML']
-                  and task.UUID not in @field # if existing UUID, but update will solve that
+                  task.name_label not in ['SR.scan', 'Dumping database as XML'] and
+                  task.UUID not in @field # if existing UUID, but update will solve that
+                )
                   @field.push task.UUID
               # TODO: use update instead
               exit: (task) ->


Allow activated callback to use dependency injection

--- src/step.coffee
+++ src/step.coffee
@@ -1,6 +1,6 @@
 angular.module 'angular.tourist'

-  .directive 'tourStep', ['$tourist', '$window', ($tourist, $window) ->
+  .directive 'tourStep', ['$tourist', '$window', '$injector', ($tourist, $window, $injector) ->
     restrict: 'EAC'
     controller: ['$scope', ($scope) ->
       @element = null
@@ -23,11 +23,16 @@

       @activate = (step) =>
         offset = @offset()
-        scrollLeft = offset.left + offset.width / 2 - $window.innerWidth / 2
-        scrollTop = offset.top + offset.height / 2 - $window.innerHeight / 2
+        scroll = {
+          left: offset.left + offset.width / 2 - $window.innerWidth / 2
+          top: offset.top + offset.height / 2 - $window.innerHeight / 2
+        }

-        if !step.activated? || step.activated.apply(@, [scrollTop, scrollLeft, offset]) != false
-          $window.scrollTo(scrollLeft, scrollTop)
+        step.activated ||= ['scroll', (scroll) ->
+          $window.scrollTo(scroll.left, scroll.top)
+        ]
+
+        $injector.invoke(step.activated, @, scroll: scroll)

       @offset = () =>
         _boundingOffset(@element[0])


fix #36 booklet html editor doesnt appear

--- webapp/views/BookletEdit.coffee
+++ webapp/views/BookletEdit.coffee
@@ -1,6 +1,7 @@
 # BookletEdit View
 templateBookletEditTab = require 'templates/BookletEditTab'
 ThingEditView = require 'views/ThingEdit'
+filter = require 'filter'

 module.exports = class BookletEditView extends ThingEditView


Minor fix

Minor fix

--- src/access/active-record.coffee
+++ src/access/active-record.coffee
@@ -71,7 +71,7 @@
         tasks = [ ]
         for key, value of data
             if (@_asyncProperties[key]?.set?)
-                do (key) =>
+                do (key, value) =>
                     tasks.push (data..., cb) => @[key](value, cb)
                 continue


add stuffs

add stuffs

--- src/coffee/services/core.coffee
+++ src/coffee/services/core.coffee
@@ -7,10 +7,11 @@
 ##

 angular.module('CoolFormServices').
-  factory('coreService', (validators)->
+  factory('coreService', (validators, directivesService)->

     return(definition) ->

+      directives = directivesService()
       _fields = {}

       class Element
@@ -18,15 +19,13 @@
           @subElemName = subElemName
           @valid = true

-        reset: ->
-          e.reset() for e in this[@subElemName]
+        reset: -> e.reset() for e in this[@subElemName]

         validate: ->
           for e in this[@subElemName]
             if e.display is true and e.validate() is false
               @valid = false; return @valid
           @valid = true; return @valid
-

       class Displayable extends Element
         _doDisplay: (def, showOrHide) ->
@@ -54,7 +53,12 @@
           @submitLabel = def['submit']
           @resetLabel = def['reset']
           @pages = (new Page(p) for p in def['pages'])
+          if def['dependencies']? then switch d for d in def['dependencies']
+            when 'validator' then validators.add(d)
+            when 'field' then dump 'field'

+        submit: -> this.validate()
+

       class Page extends Displayable
         constructor: (def) ->
@@ -76,29 +80,42 @@
           @name = def['name']
           @type = def['type']
           @label = def['label']
-          @size = def['size']
+          @size = if def['size']? then def['size']? else 1
           @help = def['help']
           @default = def['default']
+          @options = if def['options']? then def['options'] else {}
           @value = @default
-          @validators = (new Validator(v, @name) for v in def['validators'])
+          if def['validators']?
+            @validators = (new Validator(v, @name) for v in def['validators'])
+          else @validators = []
           @error = null
           @onChange = []
+          @onValidate = []
           _fields[@name] = this

+        _doValidate: (res, msg) ->
+          (h(res) for h in @onValidate)
+          @valid = res
+          @error = msg
+          return res
+
         set: (value) ->
+          if value == @value then return
           @value = value
-          for h in @onChange
-            h(@value)
-
+          (h(@value) for h in @onChange)
+          this._doValidate(true)
+
         reset: ->
           this.set(if @default then @default else null)
+          this._doValidate(true)

         validate: ->
           for v in @validators
             if v.validate() is false
-              @valid = false; @error = v.message; return @valid
-          @valid = true; @error = null; return @valid
+              return this._doValidate(false, v.message)
+          this._doValidate(true)

+        directive: -> directives.get(@type)

       class Validator
         constructor: (def, fieldName) ->


code cleanup

code cleanup

--- src/coffee_state_machine.coffee
+++ src/coffee_state_machine.coffee
@@ -19,8 +19,10 @@
         obj = options.extend ? {}

-    # state helper function
-    #
+    # ========================================
+    # STATE helper function
+    # ========================================
+
     all_states = {}
     state_hooks = {}

@@ -121,8 +123,10 @@
         call_state_hooks nextState, "enter" if (oldState is false or nextState isnt oldState) and parents.old.indexOf(nextState) is -1

-    # event helper function
-    #
+    # ========================================
+    # EVENT helper function
+    # ========================================
+
     current_event = null

     check_transition_callbacks = (trans) ->
@@ -137,28 +141,12 @@
             trans = (event_fn.transitions or= {})[current_state]
             trans = null if trans? and not check_transition_callbacks(trans)

-            all_transitions = (for all_trans in (event_fn.all_transitions or= [])
-                is_valid = all_trans.is_valid(current_state)
-                #console.log('FOUND', (if is_valid then 'valid' else 'invalid'), 'ALL-TRANSITION:', all_trans)
-                if trans? and all_trans.to?
-                    false
-                else
-                    unless all_trans.is_valid(current_state)
-                        false
-                    else
+            unless trans?
+                for all_trans in (event_fn.all_transitions or= [])
+                    if all_trans.is_valid(current_state)
                         if check_transition_callbacks(all_trans)
-                            if all_trans.to?
-                                trans = all_trans
-                                false
-                            else
-                                all_trans
-                        else
-                            false
-            )
-            all_transitions = (all_trans for all_trans in all_transitions when all_trans isnt false)
-
-            #console.log('ALL-TRANSiTIONS:', all_transitions) if all_transitions.length isnt 0
-
+                            trans = all_trans
+                            break
             if trans?
                 old_state = current_state

@@ -195,8 +183,10 @@
     event_builder.type = 'coffee_state_machine.EventHelperFunction'

-    # transition helper function
-    #
+    # ========================================
+    # TRANSITION helper function
+    # ========================================
+
     append_common_state_trans_options = (trans_def, ifCallback, unlessCallback, doAction) ->
         trans_def.if = ifCallback if typeof ifCallback is 'function'
         trans_def.unless = unlessCallback if typeof unlessCallback is 'function'
@@ -236,7 +226,12 @@
     create_state_transitions = (stateTransitionMap, transitionHook) ->
         trans_map = current_state_transitions()
         for onState, toState of stateTransitionMap
-            trans_map[onState] = create_state_trans_def onState, toState, undefined, undefined, transitionHook
+            trans_map[onState] = create_state_trans_def(
+                onState,
+                toState,
+                undefined,
+                undefined,
+                transitionHook)

     transition_builder = (args...) ->
         # inside event definition?
@@ -245,7 +240,7 @@
                 create_state_transitions args[0], args[1]

     transition_builder.from = (states, options, hook) ->
-        if current_event? # inside event definition?
+        if current_event?
             trans_map = current_state_transitions()
             _states = if typeof states is 'string' then [states] else states
             for on_state in _states
@@ -254,10 +249,10 @@
                     options.to,
                     options.if,
                     options.unless,
-                    hook)  # options.action
+                    (hook or options.action))

     transition_builder.all = (options= {}, hook= undefined) ->
-        if current_event?  # inside event definition?
+        if current_event?
             trans_alls = current_all_state_transitions()
             trans_alls.push create_all_state_trans_def(
                 options.to,
@@ -265,11 +260,14 @@
                 options.only,
                 options.if,
                 options.unless,
-                hook)
-            #console.log('ALL-TRANSITION-MAP FOR ['+current_event+']:', trans_alls)
+                (hook or options.action))

     transition_builder.type = 'coffee_state_machine.TransitionHelperFunction'

+
+    # ========================================
+    # initialize
+    # ========================================

     # call given function within context of state object
     fn.call obj, state_builder, event_builder, transition_builder


Find parent template starting from the current template.

--- exports.coffee
+++ exports.coffee
@@ -1,7 +1,7 @@
 BElements = {}

 BElements.getParentTemplate = (tmpl, parentName) ->
-  view = tmpl.view?.parentView
+  view = tmpl.view
   while (view isnt null) and
     (view isnt undefined) and
     ((view.templateInstance is undefined) or


Issue #198: Added check to see if the client's password exist

--- src/middleware/basicAuthentication.coffee
+++ src/middleware/basicAuthentication.coffee
@@ -36,6 +36,10 @@
 			return done err if err

 			if client
+				if not (client.passwordAlgorithm or client.passwordHash)
+					logger.warn "#{user.name} does not have a basic auth password set"
+					return done null, null
+
 				comparePasswordWithClientHash user.pass, client, (err, res) ->
 					return done err if err


random stuff

random stuff

--- docs/coffee/vis.coffee
+++ docs/coffee/vis.coffee
@@ -124,7 +124,12 @@
   # called once to clean up raw data and switch links to point to node instances
   # Returns modified data
   setupData = (data) ->
+    minParity = Number.MAX_SAFE_INTEGER
+    maxParity = -Number.MAX_SAFE_INTEGER
     data.nodes.forEach (n) ->
+      minParity = Math.min(minParity, n.parity)
+      maxParity = Math.max(maxParity, n.parity)
+
       # set initial x/y to values within the width/height of the visualization (make dems on left)
       if n.parity < 0.3
         n.x = randomnumber=Math.floor(Math.random() * 1.5) + (width / 4)
@@ -145,6 +150,9 @@

       # linkedByIndex is used for link sorting
       linkedByIndex["#{l.source.id},#{l.target.id}"] = 1
+    console.log minParity
+    console.log maxParity
+    nodeColors.domain([minParity, maxParity])

     data


Add bootstrap, personal projects

Add bootstrap, personal projects

--- projects.wme03370.cson
+++ projects.wme03370.cson
@@ -86,4 +86,35 @@
       "/home/aflanagan/Devel/utl_lookup"
     ]
   }
+  {
+    title: "Bootstrap 3 3 6"
+    group: "Frameworks"
+    paths: [
+      "/home/aflanagan/src/bootstrap/bootstrap-3.3.6"
+    ]
+  }
+  {
+    title: "Gmail Intfc"
+    group: "Mail"
+    paths: [
+      "/home/aflanagan/Devel/personal/mail-electron-intfc"
+    ]
+    icon: "icon-mail-read"
+  }
+  {
+    title: "Gmail Web Svcs"
+    group: "Mail"
+    paths: [
+      "/home/aflanagan/Devel/personal/mail-web-srvcs"
+    ]
+    icon: "icon-mail-read"
+  }
+  {
+    title: "Badge2017"
+    group: "HackRVA"
+    paths: [
+      "/home/aflanagan/Devel/personal/hackrva/Harmony-Badge-2017/firmware"
+    ]
+    icon: "icon-circuit-board"
+  }
 ]


add nebhssports

add nebhssports

--- projects.wme03370.cson
+++ projects.wme03370.cson
@@ -106,7 +106,8 @@
     title: "Badge2017"
     group: "HackRVA"
     paths: [
-      "~/Devel/personal/hackrva/Harmony-Badge-2017/firmware"
+      "/home/aflanagan/Devel/personal/hackrva/Harmony-Badge-2017/firmware"
+      "/home/aflanagan/Devel/personal/hackrva/microchip/harmony/v1_09"
     ]
     icon: "icon-circuit-board"
   }
@@ -134,4 +135,11 @@
       "/home/aflanagan/Devel/site_setup"
     ]
   }
+  {
+    title: "Nebhssports"
+    group: "BH Media"
+    paths: [
+      "/home/aflanagan/Devel/nebhssports"
+    ]
+  }
 ]


[atom-reason] Add more operators so that ligatures render correctly (#757)

[ci skip]

--- editorSupport/language-reason/grammars/reason.cson
+++ editorSupport/language-reason/grammars/reason.cson
@@ -253,7 +253,7 @@
   {
     'comment': 'Operator'
     'name': 'keyword.operator.reason'
-    'match': '(\\+|-|/|\\*|=|<=|>=|\\^|&|\\||!|%|<<|>>|>|<)'
+    'match': '(\\+|-|\\|>|>>=|!==|!=|/|\\*|=|<=|>=|\\^|&|\\||!|%|<<|>>|>|<)'
   }
   # Standard types and traits
   { 'include': '#core_types' }


.on was not correctly used. fixes #251

.on was not correctly used. fixes #251

--- app/assets/javascripts/modules/tag_like.coffee
+++ app/assets/javascripts/modules/tag_like.coffee
@@ -11,12 +11,12 @@
            # TODO: Eventuell ein schöneres Alert
            alert "Da ging wat schief: #{data.message}"

-  $(".m-tag-love-click").on "click", ->
+  $(document).on "click", ".m-tag-love-click", ->
     addTag("like", $(this).attr("data-tag-name"))
     $(this).parent().hide()
     return false

-  $(".m-tag-hate-click").on "click", ->
+  $(document).on "click", ".m-tag-hate-click", ->
     addTag("hate", $(this).attr("data-tag-name"))
     $(this).parent().hide()
     return false


Improve the usability of proyect values

Improve the usability of proyect values

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -160,58 +160,7 @@
             promise.then null, (data) ->
                 $confirm.notify("error", data._error_message)

-        $el.on "submit", "form", (event) ->
-            event.preventDefault()
-            submit()
-
-        $el.on "click", "form a.button-green", (event) ->
-            event.preventDefault()
-            submit()
-
-        $el.on "click", ".show-add-new", (event) ->
-            event.preventDefault()
-            $el.find(".new-value").css('display': 'flex')
-
-            goToBottomList(true)
-
-        $el.on "click", ".add-new", (event) ->
-            event.preventDefault()
-            form = $el.find(".new-value").parents("form").checksley()
-            return if not form.validate()
-
-            $scope.newValue.project = $scope.project.id
-
-            $scope.newValue.order = if $scope.maxValueOrder then $scope.maxValueOrder + 1 else 1
-
-            promise = $repo.create(valueType, $scope.newValue)
-            promise.then =>
-                $ctrl.loadValues().then ->
-                    animationFrame.add () ->
-                         goToBottomList()
-
-                $el.find(".new-value").hide()
-                initializeNewValue()
-
-            promise.then null, (data) ->
-                $confirm.notify("error")
-                form.setErrors(data)
-
-        $el.on "click", ".delete-new", (event) ->
-            event.preventDefault()
-            $el.find(".new-value").hide()
-            initializeNewValue()
-
-        $el.on "click", ".edit-value", (event) ->
-            event.preventDefault()
-            target = angular.element(event.currentTarget)
-
-            row = target.parents(".row.table-main")
-            row.hide()
-            row.siblings(".edition").css("display": "flex")
-
-        $el.on "click", ".save", (event) ->
-            event.preventDefault()
-            target = angular.element(event.currentTarget)
+        saveValue = (target)->
             form = target.parents("form").checksley()
             return if not form.validate()

@@ -226,15 +175,82 @@
                 $confirm.notify("error")
                 form.setErrors(data)

-        $el.on "click", ".cancel", (event) ->
-            event.preventDefault()
-            target = angular.element(event.currentTarget)
+        cancel = (target) ->
             row = target.parents(".row.table-main")
             value = target.scope().value
             $scope.$apply ->
                 row.hide()
                 value.revert()
                 row.siblings(".visualization").css("display": "flex")
+
+        $el.on "submit", "form", (event) ->
+            event.preventDefault()
+            submit()
+
+        $el.on "click", "form a.button-green", (event) ->
+            event.preventDefault()
+            submit()
+
+        $el.on "click", ".show-add-new", (event) ->
+            event.preventDefault()
+            $el.find(".new-value").css('display': 'flex')
+
+            goToBottomList(true)
+
+        $el.on "click", ".add-new", (event) ->
+            event.preventDefault()
+            form = $el.find(".new-value").parents("form").checksley()
+            return if not form.validate()
+
+            $scope.newValue.project = $scope.project.id
+
+            $scope.newValue.order = if $scope.maxValueOrder then $scope.maxValueOrder + 1 else 1
+
+            promise = $repo.create(valueType, $scope.newValue)
+            promise.then =>
+                $ctrl.loadValues().then ->
+                    animationFrame.add () ->
+                         goToBottomList()
+
+                $el.find(".new-value").hide()
+                initializeNewValue()
+
+            promise.then null, (data) ->
+                $confirm.notify("error")
+                form.setErrors(data)
+
+        $el.on "click", ".delete-new", (event) ->
+            event.preventDefault()
+            $el.find(".new-value").hide()
+            initializeNewValue()
+
+        $el.on "click", ".edit-value", (event) ->
+            event.preventDefault()
+            target = angular.element(event.currentTarget)
+
+            row = target.parents(".row.table-main")
+            row.hide()
+            editionRow = row.siblings(".edition")
+            editionRow.css("display": "flex")
+            editionRow.find('input:visible').first().focus().select()
+
+        $el.on "keyup", ".edition input", (event) ->
+            if event.keyCode == 13
+                target = angular.element(event.currentTarget)
+                saveValue(target)
+            else if event.keyCode == 27
+                target = angular.element(event.currentTarget)
+                cancel(target)
+
+        $el.on "click", ".save", (event) ->
+            event.preventDefault()
+            target = angular.element(event.currentTarget)
+            saveValue(target)
+
+        $el.on "click", ".cancel", (event) ->
+            event.preventDefault()
+            target = angular.element(event.currentTarget)
+            cancel(target)

         $el.on "click", ".delete-value", (event) ->
             event.preventDefault()


working on network delay

working on network delay

--- Framework/Network.coffee
+++ Framework/Network.coffee
@@ -2,7 +2,7 @@
   constructor: (options) ->
     @settings = root._.extend({
       timeout: 15000
-      retryCount: 3
+      retryCount: 30
       retryDelay: 500
     }, options)

@@ -36,7 +36,7 @@
     if options.try < @settings.retryCount
       Ti.API.info("root.network : retrying ##{options.try}")
       options.onRetry(options.try)
-      @ajax(options)
+      root.app.delay(options.retryDelay, (options) => @ajax(options))
     else
       options.onError


An easier-to-use dev script

An easier-to-use dev script

--- dev/xsalsa20.iced
+++ dev/xsalsa20.iced
@@ -3,8 +3,12 @@
 {WordArray} = require '../src/wordarray'

 argv = require('optimist').alias('n','nonce').alias('k','key').argv
+A = "0123456789abcdef"
+alpha = (n) -> (A for i in [0...n]).join("")
+key = argv.k or alpha 4
+nonce = argv.n or alpha 3

-eng = new Salsa20 (WordArray.from_hex argv.k), (WordArray.from_hex argv.n)
+eng = new Salsa20 (WordArray.from_hex key), (WordArray.from_hex nonce)
 pad = eng.getBytes 64
 console.log pad.toString 'hex'


More descriptive message for iterator binding

--- src/view/bindings/iterator_binding.coffee
+++ src/view/bindings/iterator_binding.coffee
@@ -21,7 +21,7 @@
     @view.prevent('ready')
     @_handle = Batman.setImmediate =>
       if @backingView.isDead
-        Batman.developer.warn "IteratorBinding trying to insert dead backing view into DOM"
+        Batman.developer.warn "IteratorBinding (data-foreach-#{@iteratorName}='#{@keyPath}') trying to insert dead backing view into DOM (#{@view.constructor.name})"
         return

       parentNode = @prototypeNode.parentNode


add properties should happend in initialize, not dinitialize

--- static/coffee/renderers/guide/axis.coffee
+++ static/coffee/renderers/guide/axis.coffee
@@ -272,8 +272,7 @@
       for i in [0..labels.length-1]
         if not labels[i]?
           continue
-        w = @plot_view.ctx.measureText(labels[i]).width * 1.3
-
+        w = @plot_view.ctx.measureText(labels[i]).width * 1.3 # height overestimates, compensate
         h = @plot_view.ctx.measureText(labels[i]).ascent
         val = w*s + (h/factor)*c
         if val > extent
@@ -282,7 +281,7 @@
       for i in [0..labels.length-1]
         if not labels[i]?
           continue
-        w = @plot_view.ctx.measureText(labels[i]).width * 1.3
+        w = @plot_view.ctx.measureText(labels[i]).width * 1.3 # height overestimates, compensate
         h = @plot_view.ctx.measureText(labels[i]).ascent
         val = w*c + (h/factor)*s
         if val > extent
@@ -339,9 +338,10 @@
   default_view: LinearAxisView
   type: 'GuideRenderer'

-  dinitialize: (attrs, options)->
+  initialize: (attrs, options)->
     super(attrs, options)

+    console.log "FOOOOOO"
     @register_property('bounds', @_bounds, false)
     @add_dependencies('bounds', this, ['guidespec'])
     @add_dependencies('bounds', @get_obj('plot'), ['x_range', 'y_range'])