Use complete URLs

Use complete URLs

--- models/mixins/relations/artist.coffee
+++ models/mixins/relations/artist.coffee
@@ -1,4 +1,4 @@
-{ API_URL } = require('sharify').data
+{ API_URL, APP_URL } = require('sharify').data
 Backbone = require 'backbone'

 module.exports =
@@ -29,19 +29,19 @@
     artworks.url = "#{@url()}/artworks?published=true"

     articles = new Articles
-    articles.url = "/artist/data/#{@id}/publications?merchandisable[]=false"
+    articles.url = "#{APP_URL}/artist/data/#{@id}/publications?merchandisable[]=false"

     merchandisable = new Books
-    merchandisable.url = "/artist/data/#{@id}/publications?merchandisable[]=true"
+    merchandisable.url = "#{APP_URL}/artist/data/#{@id}/publications?merchandisable[]=true"

     bibliography = new Books
-    bibliography.url = "/artist/data/#{@id}/publications"
+    bibliography.url = "#{APP_URL}/artist/data/#{@id}/publications"

     collections = new Backbone.Collection
-    collections.url = "/artist/data/#{@id}/collections"
+    collections.url = "#{APP_URL}/artist/data/#{@id}/collections"

     exhibitions = new Backbone.Collection
-    exhibitions.url = "/artist/data/#{@id}/exhibitions"
+    exhibitions.url = "#{APP_URL}/artist/data/#{@id}/exhibitions"

     # Return:
     @__related__ =


converted array#shuffle to use fisher-yates/knuth algorithm

--- src/array.coffee
+++ src/array.coffee
@@ -100,13 +100,13 @@

 complement = curry (ax, bx) -> ax.filter (c) -> !(c in bx)

+# https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 shuffle = (ax) ->
   bx = cat ax
-  unless bx.length <= 1
-    for b, i in bx
-      j = i
-      while j == i
-        j = Math.floor Math.random() * bx.length
+  i = bx.length
+  unless i <= 1
+    while --i > 0
+      j = Math.floor Math.random() * (i + 1)
       [bx[i], bx[j]] = [bx[j], bx[i]]
     if deepEqual ax, bx then shuffle ax else bx
   else


working on packmanager tests

working on packmanager tests

--- test/unit/coffee/PackManager/PackManagerTests.coffee
+++ test/unit/coffee/PackManager/PackManagerTests.coffee
@@ -237,3 +237,64 @@
 				it "should call the callback", ->
 					@callback.called.should.equal true

+
+	describe "getOpsByVersionRange", ->
+
+	describe "loadPacksByVersionRange", ->
+
+	describe "fetchPacksIfNeeded", ->
+
+	describe "makeProjectIterator", ->
+
+	describe "getPackById", ->
+
+	describe "increaseTTL", ->
+
+	describe "getIndex", ->
+
+	describe "getPackFromIndex", ->
+# getLastPackFromIndex:
+# getIndexWithKeys
+# initialiseIndex
+# updateIndex
+# findCompletedPacks
+# findUnindexedPacks
+# insertPacksIntoIndexWithLock
+# _insertPacksIntoIndex
+# archivePack
+# checkArchivedPack
+# processOldPack
+# 	updateIndexIfNeeded
+# 	findUnarchivedPacks
+
+	describe "checkArchiveNotInProgress", ->
+
+		describe "when an archive is in progress", ->
+			beforeEach ->
+				@db.docHistoryIndex =
+					findOne: sinon.stub().callsArgWith(2, null, {inS3:false})
+				@PackManager.checkArchiveNotInProgress @project_id, @doc_id, @pack_id, @callback
+			it "should call the callback", ->
+				@callback.called.should.equal true
+			it "should return an error", ->
+				@callback.calledWith(new Error()).should.equal true
+
+		describe "when an archive is completed", ->
+			beforeEach ->
+				@db.docHistoryIndex =
+					findOne: sinon.stub().callsArgWith(2, null, {inS3:true})
+				@PackManager.checkArchiveNotInProgress @project_id, @doc_id, @pack_id, @callback
+			it "should call the callback", ->
+				@callback.called.should.equal true
+			it "should return an error", ->
+				@callback.calledWith(new Error()).should.equal true
+
+		describe "when the archive has not started or completed", ->
+			beforeEach ->
+				@db.docHistoryIndex =
+					findOne: sinon.stub().callsArgWith(2, null, {})
+				@PackManager.checkArchiveNotInProgress @project_id, @doc_id, @pack_id, @callback
+			it "should call the callback", ->
+				@callback.called.should.equal true
+			it "should return with no error", ->
+				@callback.calledWith(undefined).should.equal true


removed unnecessary folder create from ng-resource generator

--- src/steroids/generators/resources/NgResource.coffee
+++ src/steroids/generators/resources/NgResource.coffee
@@ -64,7 +64,6 @@
      @addFile path.join("app", "views", "#{@options.name}", "show.html"), "show.html.template"

      @ensureDirectory path.join("www", "data")
-     @ensureDirectory path.join("www", "data", @options.name)
      @addFile path.join("www", "data", "#{@options.name}.json"), "all.json"

      @addBowerDependency "angular", "1.0.7"


Made suggested style changes.

Made suggested style changes.

Details: https://github.com/github/hubot/pull/447

--- src/brain.coffee
+++ src/brain.coffee
@@ -130,7 +130,7 @@
     extend @data._private, pair
     @emit 'loaded', @data

-    this
+    @

   # Public: Get value by key from the private namespace in @data
   # or return null if not found.
@@ -138,7 +138,6 @@
   # Returns the value.
   get: (key) -> @data._private[key] ? null

-# - Utils -
 # Private: Extend obj with objects passed as additional args.
 #
 # Returns the original object with updated changes.
@@ -146,7 +145,6 @@
   for source in sources
     obj[key] = value for own key, value of source

-  # Result
   obj

 # Private: Check if argument is an object.


cleaning up array constructions

cleaning up array constructions

--- src/color.coffee
+++ src/color.coffee
@@ -121,7 +121,7 @@
       if alpha? then parseFloat(alpha) else 0.0
     ]

-    self =
+    self =
       channels: ->
         channels.copy()

@@ -190,9 +190,8 @@
         "##{hexFromNumber(channels[0])}#{hexFromNumber(channels[1])}#{hexFromNumber(channels[2])}"

       toHsl: ->
-        r = channels[0] / 255.0
-        g = channels[1] / 255.0
-        b = channels[2] / 255.0
+        [r, g, b] = channels.map (channel) ->
+          channel / 255.0

         min = Math.min(r, g, b)
         max = Math.max(r, g, b)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -17,6 +17,9 @@

     switch hexString.length
       when 3, 4 then
+        return [
+
+        ]
       else
         return [
           parseInt(hexString.substr(0, 2), 16)


Add the /just listen/ command in the comment

This is for documentation too.
See https://github.com/github/hubot-scripts#documentation

--- src/ector.coffee
+++ src/ector.coffee
@@ -11,6 +11,7 @@
 #   sentence not triggering any other command - Makes hubot reply (see "shut up" and "speak").
 #   hubot shut up - Make hubot quiet.
 #   hubot speak - Make hubot speak again.
+#   hubot just listen - Make the bot learn while quiet
 #
 # Notes:
 #   None


Minor improvements in tests

Minor improvements in tests

--- test/unit/controllers/projectsControllerSpec.coffee
+++ test/unit/controllers/projectsControllerSpec.coffee
@@ -45,7 +45,6 @@
         httpBackend = null
         scope = null
         ctrl = null
-        getProjectsHasError = false

         beforeEach(inject(($rootScope, $controller, $httpBackend) ->
             scope = $rootScope.$new()
@@ -54,12 +53,6 @@
             })
             httpBackend = $httpBackend
             httpBackend.whenGET(APIURL+"/sites").respond(200, {test: "test"})
-            httpBackend.whenGET(APIURL+"/projects").respond(() ->
-                if getProjectsHasError
-                    return [404, []]
-                return [200, [{name: "test proj 1", slug: "test-proj-1"},
-                              {name: "test proj 2", slug: "test-proj-2"}]]
-            )
             httpBackend.flush()
         ))

@@ -68,7 +61,8 @@
             httpBackend.verifyNoOutstandingRequest()

         it "should have the project list when call showProjects", ->
-            getProjectsHasError = false
+            httpBackend.expectGET(APIURL+"/projects").respond(200, [{name: "test proj 1", slug: "test-proj-1"},
+                                                                    {name: "test proj 2", slug: "test-proj-2"}])
             ctrl.scope.showProjects()
             httpBackend.flush()

@@ -77,7 +71,7 @@
             expect(ctrl.scope.myProjects[1].slug).to.be.equal("test-proj-2")

         it "should have an empty project list when call showProjects and ther server have no projects", ->
-            getProjectsHasError = true
+            httpBackend.expectGET(APIURL+"/projects").respond(400, [])
             ctrl.scope.showProjects()
             httpBackend.flush()


Update helper.

Update helper.

--- test/helper.coffee
+++ test/helper.coffee
@@ -15,6 +15,11 @@

 # Helper to run shortcake in tests
 run = (cmd) ->
-  exec "#{bin} #{cmd}", {cwd: cwd}
+  p = exec "#{bin} #{cmd}", {cwd: cwd}
+  p.then (res) ->
+    res.stdout = res.stdout.trim()
+    res.stderr = res.stdout.trim()
+  p.catch (err) ->
+    throw err

 before -> global.run = run


Add a last.page item to contents  & current pageNum variable

--- examples/blog/plugins/paginator.coffee
+++ examples/blog/plugins/paginator.coffee
@@ -43,7 +43,7 @@
         return callback new Error "unknown paginator template '#{ options.template }'"

       # setup the template context
-      ctx = {@articles, @prevPage, @nextPage}
+      ctx = {@articles, @pageNum, @prevPage, @nextPage}

       # extend the template context with the enviroment locals
       env.utils.extend ctx, locals
@@ -76,6 +76,7 @@
     for page in pages
       rv.pages["#{ page.pageNum }.page"] = page # file extension is arbitrary
     rv['index.page'] = pages[0] # alias for first page
+    rv['last.page'] = pages[(numPages-1)] # alias for last page

     # callback with the generated contents
     callback null, rv


Add uk TODOs

Add uk TODOs

--- dev/uk.cson
+++ dev/uk.cson
@@ -25,3 +25,10 @@
     { type: 'car-left', color: 'black' }
     { type: 'car-right', color: 'red' }
   ]
+
+'children':
+  category: 'danger'
+  name: 'children'
+  elements: [
+    { type: 'circle-bg', color: 'black' }
+  ]


Add logging

Add logging

--- src/index.coffee
+++ src/index.coffee
@@ -51,7 +51,7 @@

 Ubret.Loader = (tools, cb) ->
   isScriptLoaded = (script) ->
-    not (typeof window[script] is 'undefined' or typeof Ubret[script] is 'undefined')
+    not (typeof window[script] is 'undefined' and typeof Ubret[script] is 'undefined')

   loadScript = (source, cb=null) ->
     script = document.createElement 'script'
@@ -84,6 +84,7 @@
       return
     callback = if tools.length is 1 then cb else loadScripts
     tool = tools.pop()
+    console.log ((isScriptLoaded tool) or (isScriptLoaded Ubret.Dependencies[tool].symbol))
     unless (isScriptLoaded tool) or (isScriptLoaded Ubret.Dependencies[tool].symbol)
       source = Ubret.Dependencies[tool].source
       loadScript source, callback


* inspect options use inherited properies now

--- src/index.coffee
+++ src/index.coffee
@@ -89,7 +89,7 @@
         if isObject aOptions
           v = {}
           for key in getObjectKeys @getProperties()
-            v[key] = aOptions[key] if aOptions.hasOwnProperty key
+            v[key] = aOptions[key] if aOptions[key]?
           aOptions = v
       else
         aOptions = @


logging

logging

--- src/index.coffee
+++ src/index.coffee
@@ -13,6 +13,6 @@

 server.start (error) ->
   if error
-    console.log error
+    console.trace error
   else
     console.log 'ce-engine started'


adding parallelisation option to resolve, but not enabling it

--- noddy/service/oabutton/resolve.coffee
+++ noddy/service/oabutton/resolve.coffee
@@ -1,5 +1,5 @@

-
+import Future from 'fibers/future'
 import request from 'request'
 import unidecode from 'unidecode'

@@ -44,7 +44,7 @@

   return meta

-API.service.oab.resolve = (meta,content,sources,all=false,titles=true,journal=true) ->
+API.service.oab.resolve = (meta,content,sources,all=false,titles=true,journal=true,parallel=false) ->
   API.log msg: 'Resolving academic content', meta: meta, sources: sources, level: 'debug'
   sources ?= ['oabutton','eupmc','oadoi','base','dissemin','share','core','openaire','figshare','doaj']
   # all source get checked by default (but oabutton availability overrides to not botehr with base and dissemin as covered by oadoi)
@@ -57,6 +57,7 @@
   meta.found = {}
   meta.checked = {identifiers:[],titles:[]}
   meta.original = meta.url
+  meta.parallel = parallel

   if not meta.pmid
     lurl = meta.url.toLowerCase()
@@ -122,8 +123,10 @@
   meta.url = undefined if meta.url is meta.original

   if meta.doi
+    done = 0
+    suitable = false
     API.log 'Resolving for DOI', doi: meta.doi
-    for src in sources
+    _doi = (src) ->
       if src isnt 'oabutton' and (src isnt 'eupmc' or (not meta.pmid and not meta.pmc)) # because we try eupmc directly earlier if these are present, so don't run again
         try
           # will only work for use endpoints that provide a doi method
@@ -136,7 +139,26 @@
             meta.source = src
             meta.licence ?= res.best_oa_location?.license
             meta.journal = res.journal?.title?.split('(')[0].trim()
-            return meta if not all and res.redirect isnt false
+            suitable = not all and res.redirect isnt false
+
+    for sr in sources
+      if parallel
+        (() ->
+          src = sr
+          Meteor.setTimeout (() ->
+            _doi src
+            done += 1) , 10
+        )()
+      else
+        _doi sr
+        return meta if suitable
+    while done isnt sources.length
+      if suitable
+        return meta
+      else
+        future = new Future();
+        Meteor.setTimeout (() -> future.return()), 500
+        future.wait()

   if titles
     if not meta.title and meta.doi
@@ -154,8 +176,10 @@
     if meta.title
       meta.title = unidecode meta.title
       meta.titles = true
+      done = 0
+      suitable = false
       API.log 'Resolving for title', title: meta.title
-      for src in sources
+      _title = (src) ->
         if src isnt 'oabutton'
           try
             # will only work for use endpoints that provide a title method
@@ -165,7 +189,26 @@
               meta.source = src
               meta.url = if res.redirect then res.redirect else res.url
               meta.found[src] = res.url
-              return meta if not all and res.redirect isnt false
+              suitable = not all and res.redirect isnt false
+
+      for sr in sources
+        if parallel
+          (() ->
+            src = sr
+            Meteor.setTimeout (() =>
+              _title src
+              done += 1), 10
+          )()
+        else
+          _title sr
+          return meta if suitable
+      while done isnt sources.length
+        if suitable
+          return meta
+        else
+          future = new Future();
+          Meteor.setTimeout (() -> future.return()), 500
+          future.wait()

   if not meta.url and journal and meta.journal and 'doaj' in sources # can check DOAJ for journal
     try


Social: public channel will be always public channel

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1426,7 +1426,7 @@

   createGroupChannel:(client, options, callback)->
-    options.name = if @slug is "koding" then "public" else @slug
+    options.name = "public"
     options.varName = "socialApiChannelId"
     options.typeConstant = "group"


Social: fix undefined group channel id problem

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -446,7 +446,7 @@
             queue.next()
         -> save_ 'group', group, queue, (err)->
            if err
-             JName.release group.slug, => callback err
+             JName.release group.slug, -> callback err
            else
              queue.next()
         -> group.addMember owner, (err)->
@@ -1629,18 +1629,20 @@
         subscription.checkUsage pack, callback

   createSocialApiChannelId: (callback) ->
-    return callback null, @socialApiChannelId  if @socialApiChannelId
+    # disable for now
+    # return callback null, @socialApiChannelId  if @socialApiChannelId
     @fetchOwner (err, owner)=>
       return callback err if err
-      return callback { message: "Owner not found for #{@slug} group" } unless owner
+      unless owner
+        return callback { message: "Owner not found for #{@slug} group" }
       owner.createSocialApiId (err, socialApiId)=>
         return callback err if err
         # required data for creating a channel
         data =
-          name     : @slug
-          creatorId: socialApiId
-          group    : @slug
-          type     : "group"
+          name         : @slug
+          creatorId    : socialApiId
+          group        : @slug
+          typeConstant : "group"

         {createChannel} = require '../socialapi/requests'
         createChannel data, (err, socialApiChannel)=>


SocialWorker: replace scriptblock dash usage

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -85,13 +85,15 @@
     """

   queue = [
-    ->
+
+    (next) ->
       socialApiCacheFn = require '../cache/socialapi'
       socialApiCacheFn options, (err, data) ->
         console.error 'could not get prefetched data', err  if err
         socialapidata = data
-        queue.fin()
-    ->
+        next()
+
+    (next) ->
       groupName = session?.groupName or 'koding'

       # due to some reason, I suspect JSON.stringify somewhere, undefined
@@ -104,30 +106,34 @@

         currentGroup = group  if group

-        queue.fin()
-    ->
+        next()
+
+    (next) ->
       bongoModels.JWorkspace.fetchByMachines$ client, (err, workspaces) ->
         console.log err  if err
         userWorkspaces = workspaces or []
-        queue.fin()
-    ->
+        next()
+
+    (next) ->
       bongoModels.JMachine.some$ client, {}, (err, machines) ->
         console.log err  if err
         userMachines = machines or []
-        queue.fin()
-    ->
+        next()
+
+    (next) ->
       bongoModels.Sidebar.fetchEnvironment client, (err, data) ->
         userEnvironmentData = data
-        queue.fin()
-    ->
+        next()
+
+    (next) ->
       client.connection.delegate.fetchUser (err, user) ->
         if err
           console.error '[scriptblock] user not found', err
-          return queue.fin()
+          return next()

         if user then userId = user.getId()
         else console.error '[scriptblock] user not found', err
-        queue.fin()
+        next()
   ]

   dash queue, -> callback null, createHTML(), socialapidata


Fix language loading from cordova

Fix language loading from cordova

--- packages/rocketchat-cors/cors.coffee
+++ packages/rocketchat-cors/cors.coffee
@@ -4,6 +4,14 @@
 	res.setHeader("Access-Control-Allow-Origin", "*")
 	res.setHeader("X-Rocket-Chat-Version", VERSION)
 	res.setHeader("Access-Control-Expose-Headers",  "X-Rocket-Chat-Version")
+
+	# Block next handlers to override CORS with value http://meteor.local
+	setHeader = res.setHeader
+	res.setHeader = (key, val) ->
+		if key.toLowerCase() is 'access-control-allow-origin' and val is 'http://meteor.local'
+			return
+		return setHeader.apply @, arguments
+
 	return next()

 _staticFilesMiddleware = WebAppInternals.staticFilesMiddleware


Cleaning

Cleaning

--- packages/shrub-grunt/tests/run.coffee
+++ packages/shrub-grunt/tests/run.coffee
@@ -95,10 +95,7 @@
         protractorConfig = gruntConfig.taskConfiguration(
           'protractor', 'testsE2e'
         )
-        baseUrl = "http://#{
-          siteHostname
-        }/"
-        protractorConfig.options.args = baseUrl: baseUrl
+        protractorConfig.options.args = baseUrl: "http://#{siteHostname}/"

         # Wait for the server to come up.
         grunt.log.write 'Waiting for E2E server to come up...'


Gets markov model by looking up user_id from brain.

--- src/mimic.coffee
+++ src/mimic.coffee
@@ -46,6 +46,13 @@

   models = {}

+  getModel = (user_name) ->
+    return all_model if user_name == 'all'
+    user = robot.brain.userForName(user_name)
+    return null if !user
+    models[user.id] = models[user.id] or makeNewModel(user.id)
+    return models[user.id]
+
   # NSAbot
   robot.catchAll (msg) ->
     # Return on empty messages
@@ -60,8 +67,8 @@

   # Generate markov chains on demand, optionally seeded by some initial state.
   robot.respond /MIMIC\s+(\w+)(\s+(.+))?$/i, (msg) ->
-    user_id = msg.match[1]
-    model = if user_id == 'all' then all_model else models[user_id]
+    user_name = msg.match[1]
+    model = getModel(user_name)
     if not model
       msg.send 'Derp'
     else


Correct distribution for _chooseWeighted

--- src/model.coffee
+++ src/model.coffee
@@ -14,14 +14,14 @@
   _words: (phrase) ->
     (word for word in phrase.split /\s+/ when word.length > 0)

-  # Generate a uniformly distributed random number between 0 and max, inclusive.
+  # Generate a uniformly distributed random number between 0 and max.
   _random: (max) ->
-    Math.floor(Math.random() * (max + 1))
+    Math.random() * max

   # Given an object with possible choices as keys and relative frequencies as values,
   # choose a key with probability proportional to its frequency.
   _chooseWeighted: (choices) ->
-    return sentinel unless choices
+    return MarkovModel.sentinel unless choices? and choices.length isnt 0

     # Sum the frequencies of the available choices and choose a value within that
     # range.
@@ -37,7 +37,7 @@
       return key if chosen <= acc

     # If we get here, "chosen" was greater than total.
-    throw "Bad choice: #{chosen} from #{total}"
+    throw new Error("Bad choice: #{chosen} from #{total}")

   # Generate each state transition of order @ply among "words". For example,
   # with @ply 2 and a phrase ["a", "b", "c", "d"], this would generate:


FeaturedLinks have images

FeaturedLinks have images

--- models/featured_link.coffee
+++ models/featured_link.coffee
@@ -1,8 +1,11 @@
+_         = require 'underscore'
 Q         = require 'q'
 Backbone  = require 'backbone'
 Items     = require '../collections/items.coffee'
+Image     = require './mixins/image.coffee'

 module.exports = class FeaturedLink extends Backbone.Model
+  _.extend @prototype, Image

   fetchItems: (cache=false) ->
     items = new Items null, { id: @id }


cleaned up class generation

cleaned up class generation

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1177,59 +1177,6 @@
       @variable.error "class variable name may not be #{decl}"
     decl and= IDENTIFIER.test(decl) and decl

-  getMembers: (o) ->
-    members = []
-    boundFuncs = []
-    ctor = null
-
-    addMember = (member)->
-      members.push member
-      if (assign = member) instanceof Assign
-        if assign.variable.isNamed "constructor"
-          ctor = assign.value
-          ctor.isConstructor = yes
-        else if assign.value instanceof Code and assign.value.bound
-          boundFuncs.push assign.variable
-          assign.value.bound = no
-
-    for node in @body.expressions
-      if node instanceof Value and node.base instanceof Object
-        for assign in node.base.properties
-          addMember assign
-      else
-        addMember node
-
-    shouldPutBindingsInConstructors = no
-    if shouldPutBindingsInConstructors
-      members = members.concat @genConstructor(ctor, boundFuncs)
-    else
-      b.warn "bound methods are not supported in TypeScript" for b in boundFuncs
-    members
-
-  genConstructor: (ctor, boundFuncs) ->
-    # add constructor if there isn't one and it's needed to bind functions
-    return [] if boundFuncs.length == 0
-    unless ctor
-      fakeConstructor = yes
-      @variable.warn "generating constructor calling super with probably the wrong number of arguments" if @parent
-
-      ctor = new Code([], new Block([]), 'func')
-      ctor.isConstructor = yes
-      sCall = new Call(mkVanillaID("super"), []) if @parent
-
-    else if ctor.body.expressions[0]?.isSuper
-      sCall = ctor.body.expressions.shift()
-
-    # bind the members
-    for bvar in boundFuncs
-      lhs = "this.#{bvar.compile({})}"
-      ctor.body.expressions.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)"
-
-    ctor.body.expressions.unshift sCall if sCall?
-
-    if fakeConstructor
-      new Assign(new Value(new Literal("constructor")), ctor, 'object')
-    else []

   # Instead of generating the JavaScript string directly, we build up the
   # equivalent syntax tree and compile that, in pieces. You can see the
@@ -1256,7 +1203,17 @@
     _o = o
     o = merge o, indent: o.indent + TAB

-    members = for assign in @getMembers(o)
+    memberDecls = flatten (for node in @body.expressions
+      if node instanceof Value and node.base instanceof Obj
+        node.base.properties
+      else
+        node
+    )
+    for member in memberDecls
+      if {ctor} = member.isa(new Assign(mkVanillaID('constructor'), M("ctor", Code)))
+        ctor.isConstructor = yes
+
+    members = for assign in memberDecls
       if assign not instanceof Assign
         assign.compileToFragments o
       else


Changed to using Monokai theme

Changed to using Monokai theme

--- editors/atom/config.cson
+++ editors/atom/config.cson
@@ -15,7 +15,7 @@
     projectHome: "~/Projects"
     themes: [
       "atom-dark-ui"
-      "atom-dark-syntax"
+      "monokai"
     ]
   minimap:
     v4Preview: true


Support multiple speakers or mentions.

Support multiple speakers or mentions.

--- src/quote.coffee
+++ src/quote.coffee
@@ -65,19 +65,23 @@
       words = ['']
     _.filter words, (part) -> part.length > 0

-  quotesMatching = (query = [], speaker = null, mention = null) ->
+  quotesMatching = (query = [], speakers = [], mentions = []) ->
     results = quotes

-    if speaker? or mention?
+    if speakers.length > 0 or mentions.length > 0
       results = _.filter results, (quote) ->
-        match = false
+        speakersNotSeen = new Set(speakers)
+        mentionsNotSeen = new Set(mentions)
+
         for line in quote.split /\n/
           m = line.match /^\[[^\]]+\] @?([^:]+): (.*)$/
           if m?
-            [x, sp, rest] = m
-            match = true if speaker? and sp is speaker
-            match = true if mention? and rest.includes mention
-        match
+            [x, speaker, rest] = m
+            speakersNotSeen.delete(speaker)
+            for mention in mentions
+              mentionsNotSeen.delete(mention) if rest.includes mention
+
+        speakersNotSeen.size is 0 and mentionsNotSeen.size is 0

     if query.length > 0
       rxs = (new RegExp(q, 'i') for q in query)
@@ -105,35 +109,53 @@
   robot.respond /quoteby\s+@?(\S+)(\s+.*)?$/i, (msg) ->
     return unless isLoaded(msg)

-    speaker = msg.match[1].trim()
+    speakers = msg.match[1].split('+')
     query = queryFrom msg, 2

-    potential = quotesMatching query, speaker, null
+    potential = quotesMatching query, speakers, null

     if potential.length > 0
       chosen = _.random potential.length - 1
       msg.send potential[chosen]
     else
-      m = "No quotes spoken by #{speaker}"
+      m = "No quotes with lines spoken by "
+
+      if speakers.length is 1
+        m += speakers[0]
+      else if speakers.length is 2
+        m += "both #{speakers[0]} and #{speakers[1]}"
+      else
+        m += "all of #{speakers.join ', '}"
+
       if query.length > 0
         m += " about that"
+
       msg.send m + '.'

   robot.respond /quoteabout\s+@?(\S+)(\s+.*)?$/i, (msg) ->
     return unless isLoaded(msg)

-    mention = msg.match[1].trim()
+    mentions = msg.match[1].split('+')
     query = queryFrom msg, 2

-    potential = quotesMatching query, null, mention
+    potential = quotesMatching query, null, mentions

     if potential.length > 0
       chosen = _.random potential.length - 1
       msg.send potential[chosen]
     else
-      m = "No quotes about #{mention}"
+      m = "No quotes about "
+
+      if mentions.length is 1
+        m += mentions[0]
+      else if mentions.length is 2
+        m += "both #{mentions[0]} and #{mentions[1]}"
+      else
+        m += "all of #{mentions.join ', '}"
+
       if query.length > 0
         m += " about that"
+
       msg.send m + '.'

   robot.respond /howmany(\s.*)/i, (msg) ->


Attempt to clarify range logic with comments

I switched to using a strict greater than comparison here because
it privileges the start offset: rather than adding 1 to the start,
we remove 1 from the end which, to me, is a clearer reflection of
what's going on. The Range specification describes the endOffset as
the boundary offset, therefore excluding the character at that index.

--- src/range.coffee
+++ src/range.coffee
@@ -381,9 +381,14 @@
       # the combined length of the textNodes to that point exceeds or
       # matches the value of the offset.
       length = 0
-      targetOffset = this[p + 'Offset'] + (if p is "start" then 1 else 0)
+      targetOffset = this[p + 'Offset']
+
+      # Range excludes its endpoint because it describes the boundary position.
+      # Target the string index of the last character inside the range.
+      if p is 'end' then targetOffset--
+
       for tn in Util.getTextNodes($(node))
-        if (length + tn.nodeValue.length >= targetOffset)
+        if (length + tn.nodeValue.length > targetOffset)
           range[p + 'Container'] = tn
           range[p + 'Offset'] = this[p + 'Offset'] - length
           break


Factor out normalize() to normalizeBoundaries()

--- src/range.coffee
+++ src/range.coffee
@@ -9,6 +9,47 @@
 # https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
 ELEMENT_NODE = 1
 TEXT_NODE = 3
+
+
+normalizeBoundaries = (range) ->
+  {startContainer, startOffset, endContainer, endOffset} = range
+
+  # Move the start container to the last leaf before any sibling boundary,
+  # guaranteeing that any children of the container are within the range.
+  if startContainer.childNodes.length and startOffset > 0
+    startContainer = lastLeaf(startContainer.childNodes[startOffset-1])
+    startOffset = startContainer.length or startContainer.childNodes.length
+
+  # Move the end container to the first leaf after any sibling boundary,
+  # guaranteeing that any children of the container are within the range.
+  if endOffset < endContainer.childNodes.length
+    endContainer = firstLeaf(endContainer.childNodes[endOffset])
+    endOffset = 0
+
+  # Any TextNode in the traversal is valid unless excluded by the offset.
+  isTextNodeInRange = (node) ->
+    if not isTextNode(node) then return false
+    if node is startContainer and startOffset > 0 then return false
+    if node is endContainer and endOffset == 0 then return false
+    return true
+
+  # Find the start TextNode.
+  # The guarantees above provide that a document order traversal visits every
+  # Node in the Range before visiting the last leaf of the end container.
+  node = startContainer
+  next = (node) -> node isnt last and documentForward(node) or null
+  last = lastLeaf(endContainer)
+  node = next(node) while not isTextNodeInRange(node)
+  range.setStartBefore(node)
+
+  # Find the end TextNode.
+  # Similarly, a reverse document order traversal visits every Node in the
+  # Range before visiting the first leaf of the start container.
+  node = endContainer
+  next = (node) -> node isnt last and documentReverse(node) or null
+  last = firstLeaf(startContainer)
+  node = next(node) while not isTextNodeInRange(node)
+  range.setEndAfter(node)

 splitBoundaries = (range) ->
@@ -49,61 +90,35 @@
     @endContainer            = obj.endContainer
     @endOffset               = obj.endOffset

-
   setStart: (@startContainer, @startOffset) ->
     @commonAncestorContainer = commonAncestor(@startContainer, @endContainer)

+  setStartBefore: (@startContainer) ->
+    @startOffset = 0
+
+  setStartAfter: (@startContainer) ->
+    @startOffset = @startContainer.length or @startContainer.childNodes.length
+
   setEnd: (@endContainer, @endOffset) ->
     @commonAncestorContainer = commonAncestor(@startContainer, @endContainer)
+
+  setEndBefore: (@endContainer) ->
+    @endOffset = 0
+
+  setEndAfter: (@endContainer) ->
+    @endOffset = @endContainer.length or @endContainer.childNodes.length

   # Public: Normalize the start and end to TextNode boundaries.
   #
   # Returns an instance of NormalizedRange
   normalize: (root) ->
     splitBoundaries(this)
-    commonAncestor = @commonAncestorContainer
-
-    # Get (a copy of) the boundaries of the range.
-    {startContainer, startOffset, endContainer, endOffset} = this
-
-    # Move the start container to the last leaf before any sibling boundary,
-    # guaranteeing that any children of the container are within the range.
-    if startContainer.childNodes.length and startOffset > 0
-      startContainer = lastLeaf(startContainer.childNodes[startOffset-1])
-      startOffset = startContainer.length or startContainer.childNodes.length
-
-    # Move the end container to the first leaf after any sibling boundary,
-    # guaranteeing that any children of the container are within the range.
-    if endOffset < endContainer.childNodes.length
-      endContainer = firstLeaf(endContainer.childNodes[endOffset])
-      endOffset = 0
-
-    # Any TextNode in the traversal is valid unless excluded by the offset.
-    isTextNodeInRange = (node) ->
-      if not isTextNode(node) then return false
-      if node is startContainer and startOffset > 0 then return false
-      if node is endContainer and endOffset == 0 then return false
-      return true
-
-    # Find the start TextNode.
-    # The guarantees above provide that a document order traversal visits every
-    # Node in the Range before visiting the last leaf of the end container.
-    node = startContainer
-    next = (node) -> node isnt last and documentForward(node) or null
-    last = lastLeaf(endContainer)
-    node = next(node) while not isTextNodeInRange(node)
-    start = node
-
-    # Find the end TextNode.
-    # Similarly, a reverse document order traversal visits every Node in the
-    # Range before visiting the first leaf of the start container.
-    node = endContainer
-    next = (node) -> node isnt last and documentReverse(node) or null
-    last = firstLeaf(startContainer)
-    node = next(node) while not isTextNodeInRange(node)
-    end = node
-
-    return new NormalizedRange({commonAncestor, start, end})
+    normalizeBoundaries(this)
+    return new NormalizedRange({
+      commonAncestor: @commonAncestorContainer
+      start: @startContainer,
+      end: @endContainer
+    })

   # Public: Creates a range suitable for storage.
   #
@@ -449,5 +464,6 @@
   BrowserRange
   NormalizedRange
   SerializedRange
+  normalizeBoundaries
   splitBoundaries
 }


Reduce log output during normal startup (redux)

My pull request for eacfeaaddda2ba89bbf8cb521620b44ac95ed2b7 updated
load to use logger.info. This does the same for loadHubotScripts :)

--- src/robot.coffee
+++ src/robot.coffee
@@ -161,7 +161,7 @@
   #
   # Returns nothing.
   loadHubotScripts: (path, scripts) ->
-    @logger.info "Loading hubot-scripts from #{path}"
+    @logger.debug "Loading hubot-scripts from #{path}"
     for script in scripts
       @loadFile path, script


msgflo-setup: Allow to specify --shell

msgflo-setup: Allow to specify --shell

--- src/setup.coffee
+++ src/setup.coffee
@@ -293,6 +293,7 @@
     .option('--forward [stderr,stdout]', 'Forward child process stdout and/or stderr', String, '')
     .option('--discover [BOOL]', 'Whether to wait for FBP discovery messages for queue info', Boolean, false)
     .option('--forever [BOOL]', 'Keep running until killed by signal', Boolean, false)
+    .option('--shell [shell]', 'Run participant commands in a shell', String, '')
     .action (gr, env) ->
       graph = gr
     .parse args


Support parameters and data context for all syntaxes

This allows {{isActiveRoute ‘home’ class=‘is-selected’}} syntax.
It should also allow {{isActiveRoute options}} syntax, where options
is an object with at least on or name, path or regex properties
set.

Closes #31.

--- client/helpers.coffee
+++ client/helpers.coffee
@@ -10,21 +10,23 @@

   (view = {hash: {}}) ->
     if Match.test view, String
+      hash = _.extend arguments[1]?.hash, @, {}
       if share.config.equals 'regex', true
-        hash =
-          regex: view
+        hash.regex = view

       else if type is 'Path'
-        hash =
-          path: view
+        hash.path = view

       else
-        hash =
-          name: view
+        hash.name = view

       view = hash: hash

-    pattern =
+    else if not Match.test view, Spacebars.kw
+      view =
+        hash: _.extend view, @, {}
+
+    pattern = Match.ObjectIncluding
       class: Match.Optional String
       className: Match.Optional String
       regex: Match.Optional Match.OneOf RegExp, String


Fixup d3f82d4, update Background fake.

Fixup d3f82d4, update Background fake.

--- tests/fakes.coffee
+++ tests/fakes.coffee
@@ -32,15 +32,11 @@
     platforms.tileWidth = platforms.tileHeight = 32

   createBackgroundProps: (background) ->
-    group:
-      game:
-        height: 600
     layers: [
       { zIndex: 1, image: { y: 0, height: 1000 }, scrollFactor: 1 }
       { zIndex: 2, image: { y: 0, height: 1000 }, scrollFactor: 1 }
       { zIndex: 3, image: { y: 0, height: 1000 }, scrollFactor: 1 }
     ]
-    width: 300

   createPlatformsProps: (platform) ->
     config: { mapH: 3152 }


Remove a describe block from the time-slider tests

--- modules/time-slider/test/spec.coffee
+++ modules/time-slider/test/spec.coffee
@@ -2,42 +2,41 @@

 export default () ->
   describe 'time-slider', ->
-    describe 'api', ->
-      it 'value: setter/getter works', ->
-        ts = new TimeSlider(document.createElement('div'))
-        date = (new Date)
-        ts.value(date).should.equal(ts)
-        ts.value().should.eql(date)
+    it 'value: setter/getter works', ->
+      ts = new TimeSlider(document.createElement('div'))
+      date = (new Date)
+      ts.value(date).should.equal(ts)
+      ts.value().should.eql(date)

-      it 'value: setter/getter works for range', ->
-        ts = new TimeSlider(document.createElement('div'), {type: 'range'})
-        range = {
-          start: (new Date((new Date).getTime() - 1000))
-          end: (new Date)
-        }
-        ts.value(range).should.equal(ts)
-        ts.value().should.eql(range)
+    it 'value: setter/getter works for range', ->
+      ts = new TimeSlider(document.createElement('div'), {type: 'range'})
+      range = {
+        start: (new Date((new Date).getTime() - 1000))
+        end: (new Date)
+      }
+      ts.value(range).should.equal(ts)
+      ts.value().should.eql(range)

-      it 'min: setter/getter works with a Date', ->
-        ts = new TimeSlider(document.createElement('div'))
-        date = (new Date)
-        ts.min(date).should.equal(ts)
-        ts.min().should.eql(date)
+    it 'min: setter/getter works with a Date', ->
+      ts = new TimeSlider(document.createElement('div'))
+      date = (new Date)
+      ts.min(date).should.equal(ts)
+      ts.min().should.eql(date)

-      it 'min: setter/getter works with a Number', ->
-        ts = new TimeSlider(document.createElement('div'))
-        date = 123452
-        ts.min(date).should.equal(ts)
-        ts.min().should.eql(new Date(date))
+    it 'min: setter/getter works with a Number', ->
+      ts = new TimeSlider(document.createElement('div'))
+      date = 123452
+      ts.min(date).should.equal(ts)
+      ts.min().should.eql(new Date(date))

-      it 'max: setter/getter works with a Date', ->
-        ts = new TimeSlider(document.createElement('div'))
-        date = (new Date)
-        ts.max(date).should.equal(ts)
-        ts.max().should.eql(date)
+    it 'max: setter/getter works with a Date', ->
+      ts = new TimeSlider(document.createElement('div'))
+      date = (new Date)
+      ts.max(date).should.equal(ts)
+      ts.max().should.eql(date)

-      it 'max: setter/getter works with a Number', ->
-        ts = new TimeSlider(document.createElement('div'))
-        date = 123452
-        ts.max(date).should.equal(ts)
-        ts.max().should.eql(new Date(date))
+    it 'max: setter/getter works with a Number', ->
+      ts = new TimeSlider(document.createElement('div'))
+      date = 123452
+      ts.max(date).should.equal(ts)
+      ts.max().should.eql(new Date(date))


Delete conversation.coffee

Delete conversation.coffee

--- scripts/conversation.coffee
+++ scripts/conversation.coffee
@@ -1,69 +0,0 @@
-# Description:
-#   Extends robot adding conversation features
-#
-# Dependencies:
-#   None
-#
-# Configuration:
-#   None
-#
-# Commands:
-#   None
-#
-# Author:
-#   pescuma
-
-module.exports = (robot) ->
-  robot.eatListeners = {}
-
-  # Public: Adds a Listener that receives the next message from the user and av
-  # further processing of it.
-  #
-  # user     - The user name.
-  # callback - A Function that is called with a Response object. msg.match[1] w
-  #            contain the message text without the bot name
-  #
-  # Returns nothing.
-  robot.eatOneResponse = (user, callback) ->
-    robot.eatListeners[user.id] = new Listener(robot, callback)
-
-  # Change default receive command, addind processing of eatListeners
-  robot.origReceive = robot.receive
-  robot.receive = (message) ->
-    if message.user? and robot.eatListeners[message.user.id]?
-      lst = robot.eatListeners[message.user.id]
-      delete robot.eatListeners[message.user.id]
-
-      if lst.call message
-        return
-
-      # Put back to process next message
-      robot.eatListeners[message.user.id] = lst
-
-    robot.origReceive(message)
-
-  # Public: Waits for the next message from the current user.
-  #
-  # callback - Called with the user response
-  #
-  # Returns nothing.
-  robot.Response.prototype.waitResponse = (callback) ->
-    robot.eatOneResponse this.message.user, callback
-
-class Listener
-  constructor: (@robot, @callback) ->
-    if robot.enableSlash
-      @regex = new RegExp("^(?:\/|#{robot.name}:?)\\s*(.*?)\\s*$", 'i')
-    else
-      @regex = new RegExp("^#{robot.name}:?\\s*(.*?)\\s*$", 'i')
-
-    @matcher = (message) =>
-      if message.text?
-        message.text.match @regex
-
-  call: (message) =>
-    if match = @matcher message
-      @callback new @robot.Response(@robot, message, match)
-      return true
-    else
-return false


Make sure tests are always run without any preexisting tokens from the environment

--- tests/setup.coffee
+++ tests/setup.coffee
@@ -16,6 +16,9 @@
 	tokenKey: 'token'
 })

+# Make sure any existing tokens are removed before the tests start
+auth.removeKey()
+
 getRequest = require('../lib/request')

 getCustomRequest = (opts, mockFetch = true) ->


Fix: some define calls are misinterpreted

Encountered this bug in aurelia-default-loader, which calls define somewhere but the moduleName is not recognized (it's dynamic).
`define` calls with 3 arguments are not anonymous and this condition fixes the issue I'm having.

--- src/trace.coffee
+++ src/trace.coffee
@@ -164,7 +164,7 @@

       (file, definitions, callback) ->

-        if _.filter(definitions, (def) -> return def.method == "define" and def.moduleName == undefined and def.argumentsLength > 0).length > 1
+        if _.filter(definitions, (def) -> return def.method == "define" and def.moduleName == undefined and 0 < def.argumentsLength < 3).length > 1
           callback(new Error("A module must not have more than one anonymous 'define' calls."))
           return


Add timeout attribute to XHR check

Add timeout attribute to XHR check

Add an attribute to the xhr check to configure the timeout property of browsers where it is available.

--- coffee/offline.coffee
+++ coffee/offline.coffee
@@ -21,6 +21,7 @@
       url: ->
         # This can be any endpoint, even one that will 404.
         "/favicon.ico?_=#{ Math.floor(Math.random() * 1000000000) }"
+      timeout: 5000

     image:
       url: ->
@@ -146,6 +147,9 @@
   # It doesn't matter what this hits, even a 404 is considered up.  It is important however that
   # it's on the same domain and port, so CORS issues don't come into play.
   xhr.open('HEAD', Offline.getOption('checks.xhr.url'), true)
+
+  if xhr.timeout?
+    xhr.timeout = Offline.getOption('checks.xhr.timeout')

   checkXHR xhr, Offline.markUp, Offline.markDown


Delegate Kona.Utils.random

Delegate Kona.Utils.random

--- src/utils.coffee
+++ src/utils.coffee
@@ -23,6 +23,10 @@
   #
   find: (list, props={}) ->
     _.where(list, props)[0] || null
+
+
+  # Delete Utils.random() to Underscore
+  random: _.random

   # Public: Modify obj1 to also contain the contents of obj2


put "expandAllGroups" and "shrinkAllGroups" into their own functions in case we want to call them directly (e.g. on a separate button)

--- jsapp/xlform/src/view.surveyApp.coffee
+++ jsapp/xlform/src/view.surveyApp.coffee
@@ -377,21 +377,28 @@
       @$el.removeClass("survey-editor--loading")
       @

+    shrinkAllGroups: ->
+      $('.survey__row--group:not(.group--shrunk)').each (i, el) ->
+        if !$(el).hasClass('group--shrunk')
+          $(el).find('.group__caret').click()
+
+    expandAllGroups: ->
+      depth = 0
+      while $('.survey__row--group.group--shrunk').length > 0
+        $('.survey__row--group.group--shrunk').each (i, el) ->
+          $(el).find('.group__caret').click()
+        if depth++ > 10
+          break
+
     expandMultioptions: ->
       if @expand_all_multioptions()
-        $('.survey__row--group:not(.group--shrunk)').each (i, el) ->
-          if !$(el).hasClass('group--shrunk')
-            $(el).find('.group__caret').click()
+        @shrinkAllGroups()
         @$(".card--expandedchoices").each (i, el)=>
           @_getViewForTarget(currentTarget: el).hideMultioptions()
           ``
         _expanded = false
       else
-        depth = 0
-        while $('.survey__row--group.group--shrunk').length > 0
-          $('.survey__row--group.group--shrunk').each (i, el) => $(el).find('.group__caret').click()
-          if depth++ > 10
-            break
+        @expandAllGroups()
         @$(".card--selectquestion").each (i, el)=>
           @_getViewForTarget(currentTarget: el).showMultioptions()
           ``


revert unrelated change since spec failed for that

--- spec/occurrence-spec.coffee
+++ spec/occurrence-spec.coffee
@@ -258,7 +258,7 @@

     beforeEach ->
       searchEditor = vimState.searchInput.editor
-      searchEditorElement = vimState.searchInput.editorElement
+      searchEditorElement = searchEditor.element
       jasmine.attachToDOM(getView(atom.workspace))
       settings.set('incrementalSearch', true)
       set


tabs > spaces

tabs > spaces

--- src/wodry.coffee
+++ src/wodry.coffee
@@ -1,61 +1,61 @@
 $ = jQuery

 $.fn.extend
-	wodry : (config = {}) ->
-		settings = $.extend({}, config)
-		settings.separator ?= '|'
-		settings.delay ?= 2000
-		settings.animationTime ?= 500
-		settings.arm ?= 100
-		settings.callback ?= ->
+    wodry : (config = {}) ->
+        settings = $.extend({}, config)
+        settings.separator ?= '|'
+        settings.delay ?= 2000
+        settings.animationTime ?= 500
+        settings.arm ?= 100
+        settings.callback ?= ->

-		@each ->
-			flip_container = $(this)
-			array = []
-			$.each(flip_container.text().split(settings.separator), (key, value) -> array.push value)
-			flip_container.text array[0]
+        @each ->
+            flip_container = $(this)
+            array = []
+            $.each(flip_container.text().split(settings.separator), (key, value) -> array.push value)
+            flip_container.text array[0]

-			prefixer = (properties, values) ->
-				result = {}
-				propHash = {}
-
-				for property in properties
-					i = properties.indexOf property
-					propHash[property] = values[i]
+            prefixer = (properties, values) ->
+                result = {}
+                propHash = {}
+
+                for property in properties
+                    i = properties.indexOf property
+                    propHash[property] = values[i]

-				if properties.length is values.length
-					for own property, value of propHash
-						[webkit, moz, o] = ["-webkit-#{property}","-moz-#{property}","-o-#{property}"]
-						result["#{webkit}"] = value
-						result["#{moz}"] = value
-						result["#{o}"] = value
-						result["#{property}"] = value
-					result
+                if properties.length is values.length
+                    for own property, value of propHash
+                        [webkit, moz, o] = ["-webkit-#{property}","-moz-#{property}","-o-#{property}"]
+                        result["#{webkit}"] = value
+                        result["#{moz}"] = value
+                        result["#{o}"] = value
+                        result["#{property}"] = value
+                    result

-			flip = ->
-				if flip_container.find(".back-face").length > 0
-					flip_container.html do flip_container.find(".back-face").html
+            flip = ->
+                if flip_container.find(".back-face").length > 0
+                    flip_container.html do flip_container.find(".back-face").html

-				front_text = do flip_container.text
-				back_text_index = $.inArray front_text, array
-				if (back_text_index + 1) is array.length
-					back_text_index = -1
+                front_text = do flip_container.text
+                back_text_index = $.inArray front_text, array
+                if (back_text_index + 1) is array.length
+                    back_text_index = -1

-				flip_container.html ""
-				$ "#{front_text}"
-					.appendTo flip_container
-				$ ".front-face"
-					.css prefixer(["transform"],["translate3d(0,0,#{settings.arm}px)"])
-				$ "#{array[back_text_index + 1]}"
-					.appendTo flip_container
-				$ ".back-face"
-					.css prefixer(["transform"], ["translate3d(0,0,#{settings.arm}px) rotateY(180deg)"])
+                flip_container.html ""
+                $ "#{front_text}"
+                    .appendTo flip_container
+                $ ".front-face"
+                    .css prefixer(["transform"],["translate3d(0,0,#{settings.arm}px)"])
+                $ "#{array[back_text_index + 1]}"
+                    .appendTo flip_container
+                $ ".back-face"
+                    .css prefixer(["transform"], ["translate3d(0,0,#{settings.arm}px) rotateY(180deg)"])

-				flip_container.wrapInner ""
-					.find(".adjecting").hide().show().css prefixer(["transform","transition"],[ " rotateY(180deg)"," #{settings.animationTime}ms"])
-
-			setInterval ->
-				do flip
-				do settings.callback
-			, (settings.delay + settings.animationTime)+                flip_container.wrapInner ""
+                    .find(".adjecting").hide().show().css prefixer(["transform","transition"],[ " rotateY(180deg)"," #{settings.animationTime}ms"])
+
+            setInterval ->
+                do flip
+                do settings.callback
+            , (settings.delay + settings.animationTime)


enable input method for literal agda

enable input method for literal agda

--- lib/agda-mode.coffee
+++ lib/agda-mode.coffee
@@ -113,7 +113,7 @@
         filepath = editor.getPath?()
     else
         filepath = atom.workspace.getActivePaneItem().getPath()
-    /\.agda$/.test filepath
+    /\.agda$|\.lagda$/.test filepath

 toCamalCase = (str) ->
     str


Update `socket_session` to work with Socket.IO 1.1.

We no longer try to use whatever was configured for Socket.IO. We only depend on the (e.g. Redis) store set up for Express.
Basically we use the Socket.IO session id (provided by the client) as if it was an Express session id, for storage keying purposes.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -16,8 +16,6 @@

 vendor_module = (module,args...) ->
   fs.readFileSync (path.join (path.dirname require.resolve module), args...), 'utf-8'
-
-socketio_key = '__session'

 # Soft dependencies:
 uglify = null
@@ -392,14 +390,15 @@

   # The callback will receive (err,session).
   socket_session = (socket,cb) ->
-    socket.get socketio_key, (err,data) ->
+    # First retrieve the data record associated with the socket.id
+    context.session_store.get socket.id, (err,data) ->
       if err
-        return cb err
-      data = JSON.parse data
-      if data.id?
+        return cb err.toString(), null
+      if data?.id?
+        # Then retrieve the session data stored by Express
         context.session_store.get data.id, cb
       else
-        cb err
+        cb 'Invalid data record', null

   context.param = (obj) ->
     build = (callback) ->
@@ -590,7 +589,7 @@
     client_bundled.content = minify client_bundled()
     socketjs.content = minify socketjs()

-  if io?
+  do ->
     zappa_prefix = app.settings.zappa_prefix
     context.get zappa_prefix+'/socket/:channel_name/:socket_id', ->
       if @session?
@@ -610,15 +609,17 @@
             id: socket_id
             key: key

-          # Update the Socket.IO store
-          io_client = io.sockets.store.client(socket_id)
-          io_data = JSON.stringify
-            id: @req.sessionID
+          # Update the store.
+          data =
+            id: @req.sessionID   # local Express Session ID
             key: key
-          io_client.set socketio_key, io_data
-
-          # Let the client know which key to use.
-          @json channel_name: channel_name, key: key
+            cookie: {}
+          context.session_store.set socket_id, data, (err) =>
+            if err
+              @json error: err.toString()
+              return
+            # Let the client know which key to use.
+            @json channel_name: channel_name, key: key
       else
         @json error:'No session'
       return


first try to implement a feature that a event handler is replacable & registerable even after the socket is connected

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -112,7 +112,6 @@
         else
           for k, v of arguments[0]
             route verb: verb, path: k, handler: v
-
   context.js = (obj) ->
     for k, v of obj
       js = String(v)
@@ -129,9 +128,14 @@
       helpers[k] = v

   context.on = (obj) ->
-    for k, v of obj
-      ws_handlers[k] = v
-
+    for name, h of obj
+      if ws_handlers[name]?
+        for socket in @io.sockets.clients()
+          socket.removeAllListeners name
+          socket.on name, (data) ->
+            ctx = build_ctx(socket)
+            h.apply(ctx, [ctx])
+      ws_handlers[name] = h
   context.view = (obj) ->
     for k, v of obj
       views["#{context.id}/#{k}"] = v
@@ -250,49 +254,49 @@
         res.contentType(r.contentType) if r.contentType?
         if typeof result is 'string' then res.send result
         else return result
-
+
+  build_ctx = (socket)->
+    ctx =
+      app: app
+      io: io
+      settings: app.settings
+      socket: socket
+      id: socket.id
+      emit: ->
+        if typeof arguments[0] isnt 'object'
+          socket.emit.apply socket, arguments
+        else
+          for k, v of arguments[0]
+            socket.emit.apply socket, [k, v]
+      broadcast: ->
+        if typeof arguments[0] isnt 'object'
+          socket.broadcast.emit.apply socket.broadcast, arguments
+        else
+          for k, v of arguments[0]
+            socket.broadcast.emit.apply socket.broadcast, [k, v]
+
+    for name, helper of helpers
+      do (name, helper) ->
+        ctx[name] = ->
+          helper.apply(ctx, arguments)
+    ctx
+
   # Register socket.io handlers.
   io.sockets.on 'connection', (socket) ->
     c = {}
-
-    build_ctx = ->
-      ctx =
-        app: app
-        io: io
-        settings: app.settings
-        socket: socket
-        id: socket.id
-        emit: ->
-          if typeof arguments[0] isnt 'object'
-            socket.emit.apply socket, arguments
-          else
-            for k, v of arguments[0]
-              socket.emit.apply socket, [k, v]
-        broadcast: ->
-          if typeof arguments[0] isnt 'object'
-            socket.broadcast.emit.apply socket.broadcast, arguments
-          else
-            for k, v of arguments[0]
-              socket.broadcast.emit.apply socket.broadcast, [k, v]
-
-      for name, helper of helpers
-        do (name, helper) ->
-          ctx[name] = ->
-            helper.apply(ctx, arguments)
-      ctx
-
-    ctx = build_ctx()
+
+    ctx = build_ctx(socket)
     ws_handlers.connection.apply(ctx, [ctx]) if ws_handlers.connection?

     socket.on 'disconnect', ->
-      ctx = build_ctx()
+      ctx = build_ctx(socket)
       ws_handlers.disconnect.apply(ctx, [ctx]) if ws_handlers.disconnect?

     for name, h of ws_handlers
       do (name, h) ->
         if name isnt 'connection' and name isnt 'disconnect'
           socket.on name, (data) ->
-            ctx = build_ctx()
+            ctx = build_ctx(socket)
             ctx.data = data
             switch app.settings['databag']
               when 'this' then h.apply(data, [ctx])


Stop using transactions in character.coffee.

--- lib/character.coffee
+++ lib/character.coffee
@@ -31,14 +31,6 @@
 # Creates new character for user.
 # Returns id of new character.
 exports.createCharacter = ((dbConnection, user_id, name, race, gender) ->
-	# да что за фигня опять тут происходит?!
-	# https://github.com/grncdr/node-any-db-transaction
-	# > by default any queries that error during a transaction will cause an automatic rollback
-	# я уж не знаю, что и куда оно там АВТОМАТИЧЕСКИ откатывает, но
-	# без второго параметра в transaction() 'tx.rollback.sync tx' дальше ВИСНЕТ
-	# если и rollback убрать, в следующий раз из transaction() вывалится
-	#   Fatal error: current transaction is aborted, commands ignored until end of transaction block
-	tx = transaction dbConnection, autoRollback: false
 	try
 		energies = {
 			'orc-male': 220
@@ -49,63 +41,64 @@
 			'elf-female': 140
 		}
 		energy = energies["#{race}-#{gender}"]
-		charid = tx.query.sync(tx,
+		charid = dbConnection.query.sync(dbConnection,
 			"INSERT INTO characters (name, player, location, race, gender, energy, energy_max) "+
 			"VALUES ($1, $2, (SELECT id FROM locations WHERE initial = 1), $3, $4, $5, $5) RETURNING id",
 			[ name, user_id, race, gender, energy ]).rows[0].id
 	catch ex
-		tx.rollback.sync tx
-		if (ex.constraint is 'players_character_unique_name_index')
+		if ex.constraint is 'players_character_unique_name_index'
 			throw new Error('character already exists')
 		throw ex

-	tx.query.sync(tx,
+	dbConnection.query.sync(dbConnection,
 		"UPDATE uniusers SET character_id = $1 WHERE id = $2",
 		[ charid, user_id ])
-	tx.commit.sync(tx)
 	return charid
 ).async()

 # Removes character
 exports.deleteCharacter = ((dbConnection, user_id, character_id, force=false) ->
-	tx = transaction dbConnection
+	# do checks first

-	res = tx.query.sync(tx,
-		"DELETE FROM characters WHERE id = $1 AND player = $2",
+	res = dbConnection.query.sync(dbConnection,
+		"SELECT id FROM characters WHERE id = $1 AND player = $2",
 		[ character_id, user_id ])

 	if res.rowCount == 0
-		tx.rollback.sync(tx)
 		return {
 			result: 'fail'
 			reason: "character ##{character_id} of user ##{user_id} not found"
 		}

 	unless force
-		res = tx.query.sync(tx,
+		res = dbConnection.query.sync(dbConnection,
 			"SELECT battle FROM battle_participants WHERE character_id = $1",
 			[ character_id ])

 		if res.rowCount > 0
-			tx.rollback.sync(tx)
 			return {
 				result: 'fail'
 				reason: "character ##{character_id} is in battle ##{res.rows[0].battle}"
 			}

-	game.goEscape.sync(null, tx, character_id)
+	# start deleting
+
+	game.goEscape.sync(null, dbConnection, character_id)

 	# if the character we delete is active, unselect it
-	tx.query.sync(tx,
+	dbConnection.query.sync(dbConnection,
 		"UPDATE uniusers SET character_id = NULL WHERE id = $1 AND character_id = $2",
 		[ user_id, character_id ])

-	tx.query.sync(tx,
+	dbConnection.query.sync(dbConnection,
 		"DELETE FROM items WHERE owner = $1",
 		[ character_id ])

-	tx.commit.sync(tx)
+	dbConnection.query.sync(dbConnection,
+		"DELETE FROM characters WHERE id = $1 AND player = $2",
+		[ character_id, user_id ])
+
 	return {
 		result: 'ok'
 	}


Use validate log lvl in setLogLevel

Use validate log lvl in setLogLevel

--- lib/clog/clog.coffee
+++ lib/clog/clog.coffee
@@ -41,7 +41,7 @@
           return value

   setLogLevel: (logLvl) ->
-    @logLvl = logLvl
+    @logLvl = @_validateLogLvl(logLvl)

   setFormatter: (formatter) ->
     @formatter = formatter


ability to add kdview in any element

ability to add kdview in any element

--- lib/core/view.coffee
+++ lib/core/view.coffee
@@ -59,11 +59,13 @@
       objects.overrider[title] = item
     objects.overrider

-  appendToDomBody: ->
+  appendToParentElement: (parent) ->
     @parentIsInDom = yes
     unless @lazy
-      $("body").append @$()
+      $(parent).append @$()
       @utils.defer => @emit "viewAppended"
+
+  appendToDomBody: -> @appendToParentElement document.body

 # #
 # INSTANCE LEVEL
@@ -919,4 +921,4 @@
     windowController = KD.getSingleton 'windowController'
     windowController.setKeyView null

-  activateKeyView: -> @emit? 'KDViewBecameKeyView'+  activateKeyView: -> @emit? 'KDViewBecameKeyView'


Disable position-at-end in text areas.

Disable position-at-end in text areas.

When we `simulateSelect` an input and the selection is at the start, we
move it to the end.  This works well for single-line inputs.  However,
the UX is *bad* for multiline inputs (such as text areas), and doubly so
if the end of the input happens to be out of the viewport.

This commit simply disables the repositioning of the selection within
text areas.

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -223,15 +223,16 @@
       handlerStack.bubbleEvent "click", target: element
     else
       element.focus()
-      # If the cursor is at the start of the element's contents, send it to the end. Motivation:
-      # * the end is a more useful place to focus than the start,
-      # * this way preserves the last used position (except when it's at the beginning), so the user can
-      #   'resume where they left off'.
-      # NOTE(mrmr1993): Some elements throw an error when we try to access their selection properties, so
-      # wrap this with a try.
-      try
-        if element.selectionStart == 0 and element.selectionEnd == 0
-          element.setSelectionRange element.value.length, element.value.length
+      if element.tagName.toLowerCase() != "textarea"
+        # If the cursor is at the start of the (non-textarea) element's contents, send it to the end. Motivation:
+        # * the end is a more useful place to focus than the start,
+        # * this way preserves the last used position (except when it's at the beginning), so the user can
+        #   'resume where they left off'.
+        # NOTE(mrmr1993): Some elements throw an error when we try to access their selection properties, so
+        # wrap this with a try.
+        try
+          if element.selectionStart == 0 and element.selectionEnd == 0
+            element.setSelectionRange element.value.length, element.value.length


Refactor formatter to using lines array

Refactor formatter to using lines array

--- lib/formatter.coffee
+++ lib/formatter.coffee
@@ -13,22 +13,21 @@
   @param {Object} sectionizedLines
   ###
   formatRange: (editor, range, character, offsets, sectionizedLines) ->
-    scope     = editor.getRootScopeDescriptor().getScopeChain()
-    config    = operatorConfig.getConfig character, scope
-    textBlock = ""
+    scope  = editor.getRootScopeDescriptor().getScopeChain()
+    config = operatorConfig.getConfig character, scope
+    lines  = []

     for currentRow in range.getRows()
+      currentLine   = ""
       tokenizedLine = helper.getTokenizedLineForBufferRow(editor, currentRow)
       lineCharacter = helper.getAlignCharacter editor, currentRow
       canAlignWith  = operatorConfig.canAlignWith character, lineCharacter, config

       if not lineCharacter or not canAlignWith or tokenizedLine.isComment()
-        textBlock += editor.lineTextForBufferRow(currentRow)
-        textBlock += "\n" unless currentRow is range.end.row
+        lines.push(editor.lineTextForBufferRow(currentRow))
         continue

       sectionizedLine = sectionizedLines[currentRow]
-      currentLine     = ""

       for section, i in sectionizedLine.sections
         offset = section.offset + (if sectionizedLine.hasPrefix() then 1 else 0)
@@ -64,8 +63,7 @@
           currentLine += leftSpace + lineCharacter + rightSpace
           currentLine += section.after

-      textBlock += currentLine
-      textBlock += "\n" unless currentRow is range.end.row
+      lines.push currentLine

     # Set the first line to the start of the line
     range.start.column = 0
@@ -73,7 +71,7 @@
     range.end.column = Infinity

     # Replace the whole block
-    editor.setTextInBufferRange(range, textBlock)
+    editor.setTextInBufferRange(range, lines.join('\n'))

     # Update the cursor to the end of the original line
     editor.setCursorBufferPosition range.end


Use try catch in storeData for localStorage

--- app/assets/javascripts/ab_admin/core/utils.js.coffee
+++ app/assets/javascripts/ab_admin/core/utils.js.coffee
@@ -161,9 +161,9 @@
     str = JSON.stringify(data)
   else
     str = '' + data
-  if window.localStorage
+  try
     window.localStorage[key] = str
-  else
+  catch
     $.cookie(key, str, {path: '/'})

 window.fetchData = (key) ->


OX-187 performance testing on server

OX-187 performance testing on server

--- app/assets/javascripts/model/binary/pullqueue.coffee
+++ app/assets/javascripts/model/binary/pullqueue.coffee
@@ -6,7 +6,7 @@
 class PullQueue

   # Constants
-  BATCH_LIMIT : 1
+  BATCH_LIMIT : 100
   BATCH_SIZE : 1
   ROUND_TRIP_TIME_SMOOTHER : .125
   BUCKET_TIME_SMOOTHER : .125


fix settings parsing

fix settings parsing

--- app/assets/javascripts/uploadcare/settings.js.coffee
+++ app/assets/javascripts/uploadcare/settings.js.coffee
@@ -70,7 +70,7 @@

   intOptions = (settings, keys) ->
     for key in keys when settings[key]?
-      value = parseInt(settings[key])
+      settings[key] = parseInt(settings[key])
     settings

   parseCrop = (cropValue) ->


reworded sentence to address comma splice and typo

Change-Id: I6e1593e74c47ce681951af8bf2631e2ee08ad439
Reviewed-on: https://gerrit.instructure.com/41218
Reviewed-by: Ryan Florence <2d8900489f91ac272fd9ce9d5789db6522668ca2@instructure.com>
Product-Review: Ryan Florence <2d8900489f91ac272fd9ce9d5789db6522668ca2@instructure.com>
QA-Review: Ryan Florence <2d8900489f91ac272fd9ce9d5789db6522668ca2@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/widget/UploadMediaTrackForm.coffee
+++ app/coffeescripts/widget/UploadMediaTrackForm.coffee
@@ -49,7 +49,7 @@
         $.ajaxJSON "/media_objects/#{@mediaCommentId}/media_tracks", 'POST', params, =>
           submitDfd.resolve()
           @$dialog.dialog('close')
-          $.flashMessage I18n.t 'track_uploaded_successfully', "Track uploaded successfuly, refresh to see it."
+          $.flashMessage I18n.t 'track_uploaded_successfully', "Track uploaded successfully; please refresh your browser."

     getFileContent: ->
       dfd = new $.Deferred


Comment

Comment

--- lib/list-edit.coffee
+++ lib/list-edit.coffee
@@ -118,7 +118,6 @@
           # editor.setSelectedBufferRange cutRange # for debugging: select the range that will be cut
           textBuffer.setTextInRange cutRange, newWhitespace

-  # Store first pre, last post, one middle, and column of opening bracket for handling empty/one elt insertions (column is only for multi line lists)
   copyCmd: ->
     console.log 'Executing command list-edit-copy'
     @withSelectedList (editor, textBuffer, bufferText, selectionIxRange, listElements) ->
@@ -157,8 +156,6 @@
     #           between i and i+1:    newElt.whitespace.pre = element[i+1].whitepace.pre, newElt.whitespace.post = element[i].whitepace.post
     # maybe always using third rule and having special cases for i=0 and i = n-1 is elegant? (patching the existing elt's whitespace in those cases may make it less elegant)

-    # maybe declare bracketRightWhitespace bracketLeftWhitespace, separatorLeftWhitespace and separatorRightWhitespace
-    # and use those to format the necessary elements? Or maybe not, as explicit distinction may not be necessary

     # for testing, use digits as whitespace

@@ -185,6 +182,11 @@
       else
         (f.bind this) editor, textBuffer, bufferText, selectionIxRange, listElements

+  # TODO: store separator, so we can handle switching elements in list of two
+  # Maybe also store first pre (trailing opening bracket), last post (leading closing bracket), one middle post pre (leading, trailing separator),
+  # and column nr of opening bracket for handling empty/one elt insertions (column nr is only for multi line lists)
+
+
   mkListEditMeta: ->
     {id: 'list-edit-clip-meta'}


resolve base merge

resolve base merge

--- src/concept-coach/base.cjsx
+++ src/concept-coach/base.cjsx
@@ -3,14 +3,16 @@
 {SmartOverflow} = require 'openstax-react-components'

 {Task} = require '../task'
-{Navigation} = require './navigation'
+navigation = {Navigation} = require '../navigation'
 CourseRegistration = require '../course/registration'
+
 UserLoginButton = require '../user/login-button'
 UserLogin = require '../user/login'
 UserProfile = require '../user/profile'

 {ExerciseStep} = require '../exercise'
 {Dashboard} = require '../dashboard'
+{Progress} = require '../progress'

 User = require '../user/model'
@@ -25,9 +27,14 @@
     moduleUUID:     React.PropTypes.string.isRequired
     collectionUUID: React.PropTypes.string.isRequired

+  getDefaultProps: ->
+    defaultView: 'task'
+
   getInitialState: ->
+    {defaultView} = @props
+
     userState = @getUserState()
-    userState.view = 'task'
+    userState.view = defaultView

     userState

@@ -35,16 +42,25 @@
     User.ensureStatusLoaded()

   componentDidMount: ->
-    mountData = coach: {el: @getDOMNode(), action: 'mount'}
+    mountData = @getMountData('mount')
     channel.emit('coach.mount.success', mountData)
+
     User.channel.on('change', @updateUser)
-    channel.on('show.*', @updateView)
+    navigation.channel.on('show.*', @updateView)

   componentWillUnmount: ->
-    mountData = coach: {el: @getDOMNode(), action: 'unmount'}
+    mountData = @getMountData('ummount')
     channel.emit('coach.unmount.success', mountData)
+
     User.channel.off('change', @updateUser)
-    channel.off('show.*', @updateView)
+    navigation.channel.off('show.*', @updateView)
+
+  getMountData: (action) ->
+    {moduleUUID, collectionUUID} = @props
+    {view} = @state
+    el = @getDOMNode()
+
+    coach: {el, action, view, moduleUUID, collectionUUID}

   updateView: (eventData) ->
     {view} = eventData
@@ -55,6 +71,7 @@

   getUserState: ->
     course = User.getCourse(@props.collectionUUID)
+
     isLoggedIn: User.isLoggedIn(),
     isLoaded: User.isLoaded,
     isRegistered: course?.isRegistered()
@@ -77,8 +94,10 @@

       if view is 'task'
         <Task {...@props} key='task'/>
+      else if view is 'progress'
+        <Progress id={course.id}/>
       else if view is 'dashboard'
-        <Dashboard id={course.id}/>
+        <Dashboard cnxUrl={@props.cnxUrl}/>
       else if view is 'profile'
         <UserProfile onComplete={@showTasks} />
       else


Reimplement Yank as a single editor operation

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -98,13 +98,13 @@
   #
   # Returns nothing.
   execute: (count=1) ->
-    text = ''
-    type = if @motion.isLinewise then 'linewise' else 'character'
     originalPosition = @editor.getCursorScreenPosition()

-    _.times count, =>
-      if _.last(@motion.select())
-        text += @editor.getSelection().getText()
+    if _.contains(@motion.select(count), true)
+      text = @editor.getSelection().getText()
+    else
+      text = ''
+    type = if @motion.isLinewise?() then 'linewise' else 'character'

     @vimState.setRegister(@register, {text, type})


added support for reporting metrics on behalf of non primary route mediators

--- src/middleware/stats.coffee
+++ src/middleware/stats.coffee
@@ -25,8 +25,6 @@
         sdc.increment domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name # Per non-primary route
         sdc.increment domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.statusCodes.' + route.response.status # Per route response status

-        console.log util.inspect route
-
         if route.metrics?
           for metric in route.metrics
             if metric.type == 'counter'
@@ -48,6 +46,21 @@
             sdc.increment domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name + '.statusCodes.' + orchestrationStatus
             sdc.increment domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.statusCodes.' + route.response.status + '.orchestrations.' + orchestration.name
             sdc.increment domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.statusCodes.' + route.response.status + '.orchestrations.' + orchestration.name + '.statusCodes.' + orchestrationStatus
+
+#           Log custom orchestration metrics
+            if orchestration.metrics?
+              for metric in orchestration.metrics
+                if metric.type == 'counter'
+                  logger.info 'incrementing '+ route.name + ' orchestration counter ' + metric.name
+                  sdc.increment   domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name + '.' + metric.name, metric.value
+
+                if metric.type == 'timer'
+                  logger.info 'incrementing '+ route.name + 'orchestration timer ' + metric.name
+                  sdc.timing      domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name + '.' + metric.name, metric.value
+
+                if metric.type == 'gauge'
+                  logger.info 'incrementing '+ route.name + 'orchestration gauge ' + metric.name
+                  sdc.gauge       domain + '.channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.orchestrations.' + orchestration.name  + '.' + metric.name, metric.value

     if ctx.mediatorResponse?


Support all the types we think Emblem supports, except sub-expressions

--- lib/processor.coffee
+++ lib/processor.coffee
@@ -23,10 +23,12 @@
   paramsToString = (params) ->
     return '' unless params? && params.length > 0
     ' ' + _(params).map((p) ->
-      if p.type == "STRING"
-        "\"#{p.string}\""
-      else
-        p.string
+      switch p.type
+        when "STRING" then "\"#{p.string}\""
+        when "ID" then p.original
+        when "BOOLEAN" then p.bool
+        when "INTEGER" then p.integer
+        else throw new Error("Unsupported param type: #{p.type}")
     ).join(' ')

   processStatements = (statements) ->


Use `@list` instead  of `@find`

Use `@list` instead  of `@find`

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -156,7 +156,7 @@

   resizeToFitContent: ->
     extraPadding = 15
-    treeViewWidth = @find('.tree-view.list-tree').outerWidth()
+    treeViewWidth = @list.outerWidth()
     @width(treeViewWidth + extraPadding) if treeViewWidth > @width()

   updateRoot: (expandedEntries={}) ->


Minor bugfixes

Minor bugfixes

--- lib/tunegenie.coffee
+++ lib/tunegenie.coffee
@@ -11,7 +11,7 @@
     doRequest "http://#{@brand}.tunegenie.com/api/v1/brand/tophits/", "http://#{@brand}.tunegenie.com/tophits/"

   getOnAir: (day, hours, minimumPlays = 1) ->
-    day = moment().day(day).subtract(1, 'week').endOf('day');
+    day = moment().utcOffset(-4).day(day).subtract(1, 'week').endOf('day');
     periods = getHourlyPeriods day, hours

     requests = _.map periods, getHourlySongs, this
@@ -43,7 +43,7 @@
     end = start.clone().minute(59).second(59)

     url = "http://#{@brand}.tunegenie.com/api/v1/brand/nowplaying/?hour=#{start.hour()}&since=#{start.format()}&until=#{end.format()}"
-    doRequest url, "http://#{@brand}.tunegenie.com/onair/"
+    doRequest url, "http://#{@brand}.tunegenie.com/"

   doRequest = (url, referer) ->
     options =
@@ -56,5 +56,7 @@
       data = JSON.parse data
       songs = data.response
       _.map songs, (item) -> "#{item.artist} - #{item.song}"
+    .catch (err) ->
+      console.log "Request failed for #{url}"

 module.exports = TuneGenie


:lipstick: take over registerOperationCommands with New

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -93,14 +93,14 @@

     # InsertMode
     # -------------------------
-    @registerNewOperationCommands InsertMode, [
+    @registerOperationCommands InsertMode, [
       # ctrl-r [a-zA-Z*+%_"]
       'insert-register'
     ]

     # Operator
     # -------------------------
-    @registerNewOperationCommands TextObjects, [
+    @registerOperationCommands TextObjects, [
       # w
       'select-inside-word', 'select-a-word',
       # W
@@ -127,7 +127,7 @@

     # Motion
     # -------------------------
-    @registerNewOperationCommands Motions, [
+    @registerOperationCommands Motions, [
       'move-to-beginning-of-line' #: (e) => @moveOrRepeat(e)
       # ;, ,
       'repeat-find', 'repeat-find-reverse'
@@ -175,7 +175,7 @@

     # Operator
     # -------------------------
-    @registerNewOperationCommands Operators, [
+    @registerOperationCommands Operators, [
       # i, a
       'activate-insert-mode', 'insert-after'
       # r
@@ -210,7 +210,7 @@

     # Scroll
     # -------------------------
-    @registerNewOperationCommands Scroll, [
+    @registerOperationCommands Scroll, [
       # ctrl-e, ctrl-y
       'scroll-down', 'scroll-up'
       # z enter, zt
@@ -233,30 +233,19 @@
       do (fn) =>
         @subscriptions.add atom.commands.add(@editorElement, "vim-mode:#{name}", fn)

-  # Private: Register multiple Operators via an {Object} that
-  # maps command names to functions that return operations to push.
-  #
-  # Prefixes the given command names with 'vim-mode:' to reduce redundancy in
-  # the given object.
-  registerOperationCommands: (operationCommands) ->
-    commands = {}
-    for name, fn of operationCommands
-      do (fn) =>
-        commands[name] = (event) => @operationStack.push(fn(event))
-    @registerCommands(commands)
-
   # 'New' is 'new' way of registration to distinguish exisiting function.
   # By maping command name to correspoinding class.
   #  e.g.
   # join -> Join
   # scroll-down -> ScrollDown
-  registerNewOperationCommands: (kind, names) ->
+  registerOperationCommands: (kind, names) ->
     commands = {}
     for name in names
       do (name) =>
         klass = _.capitalize(_.camelize(name))
-        commands[name] = => new kind[klass](this)
-    @registerOperationCommands(commands)
+        commands[name] = => @operationStack.push new kind[klass](this)
+    @registerCommands(commands)
+    # @registerOperationCommands(commands)

   onDidFailToCompose: (fn) ->
     @emitter.on('failed-to-compose', fn)


Ensure only one 'mode' is shown in the status bar

I don't really like the repetition of

  if !$('#status-bar-vim-mode').length

but it works for now.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -433,7 +433,8 @@
   updateStatusBar: ->
     if !$('#status-bar-vim-mode').length
       atom.packages.once 'activated', ->
-        atom.workspaceView.statusBar?.prependRight("
Command
")
+        if !$('#status-bar-vim-mode').length
+          atom.workspaceView.statusBar?.prependRight("
Command
")

     if @mode is "insert"
       $('#status-bar-vim-mode').html("Insert")


Tab -> space

Tab -> space

--- game.coffee
+++ game.coffee
@@ -2,7 +2,7 @@
 canvas = atom.canvas

 if /Windows.*Chrome\/16/.test navigator.userAgent
-	canvas.style.borderRadius = 0
+  canvas.style.borderRadius = 0
 ctx = atom.ctx
 audioCtx = atom.audioContext


enabled doctests for prelude

enabled doctests for prelude

--- test/collectdoctests.coffee
+++ test/collectdoctests.coffee
@@ -6,15 +6,15 @@
 trim = (s) -> s.replace /(^ +| +$)/g, ''

 tests = []
-for f in glob.sync __dirname + '/../src/**/*.coffee'
+for f in glob.sync __dirname + '/../src/**/*.*'
   lines = fs.readFileSync(f, 'utf8').split '\n'
   i = 0
   while i < lines.length
     line = lines[i++]
-    while i < lines.length and (m = lines[i].match(/^ *# *\.\.\.(.*)$/))
+    while i < lines.length and (m = lines[i].match(/^ *[#⍝] *\.\.\.(.*)$/))
       line += '\n' + m[1]
       i++
-    if m = line.match /^ *# ([^]*)(<=>|!!!)([^]+)$/
+    if m = line.match /^ *[#⍝] ([^]*)(<=>|!!!)([^]+)$/
       tests.push [trim(m[1]), trim(m[2]), trim(m[3])]

 process.stdout.write "[\n#{tests.map(JSON.stringify).join ',\n'}\n]\n"


one more valid truthy/falsy value

one more valid truthy/falsy value

--- jsapp/xlform/src/model.configs.coffee
+++ jsapp/xlform/src/model.configs.coffee
@@ -345,12 +345,14 @@

   configs.truthyValues = [
     "yes",
+    "YES",
     "true",
     "true()",
     "TRUE",
   ]
   configs.falsyValues = [
     "no",
+    "NO",
     "false",
     "false()",
     "FALSE",


fix a test

fix a test

--- test/unit/model/user.coffee
+++ test/unit/model/user.coffee
@@ -47,7 +47,7 @@
       @user.set @attrs, validate: true
       @eventSpy.calledOnce.should.be.true

-describe 'User', ->
+describe 'User (Server)', ->
   before ->
     mongoose.connect process.env.CU_DB

@@ -87,7 +87,7 @@
       # TODO: Stub actual DB calls?
       User.findByShortName 'ickletest', (err, user) ->
         should.exist user
-        user.displayName.should.equal 'Mr Ickle Test'
+        user.displayName.should.equal 'Ickle Test'
         done()

     it "returns null when the user doesn't exist", (done) ->


added rate limiting on wiki endpoint

added rate limiting on wiki endpoint

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -176,7 +176,12 @@
 		webRouter.get  "/project/:Project_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, ChatController.getMessages
 		webRouter.post "/project/:Project_id/messages", AuthorizationMiddlewear.ensureUserCanReadProject, ChatController.sendMessage

-		webRouter.get  /learn(\/.*)?/, WikiController.getPage
+		webRouter.get  /learn(\/.*)?/, RateLimiterMiddlewear.rateLimit({
+			endpointName: "wiki"
+			params: []
+			maxRequests: 60
+			timeInterval: 60
+		}), WikiController.getPage

 		webRouter.post "/project/:Project_id/references/index", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.index
 		webRouter.post "/project/:Project_id/references/indexAll", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.indexAll


take possible dependency from CompositeModelForm on Backbone.FormDataTransport.Model - allow for file uploads from forms

--- guts.coffee
+++ guts.coffee
@@ -49,6 +49,8 @@
           context[model_name + '_url'] = model.url
           context[model_name + '_cid'] = model.cid
       else
+        if not @model
+          throw new Error 'BasicModelView : error : model is not set'
         context =
           model: @model.toJSON()
           cid: @model.cid
@@ -129,12 +131,12 @@
         context[model_name + '_url'] = model.url
         context[model_name + '_cid'] = model.cid
     else
+      if not @model
+        throw 'CompositeModelView : error : model is not set'
       context =
         model: @model.toJSON()
         cid: @model.cid
         url: @model.url
-      if not @model
-        throw 'CompositeModelView : error : model is not set'

     @_rendered = true
     template_result = render do @get_template, context
@@ -186,6 +188,22 @@
     e.preventDefault()
     @save

+  file_chosen: (e) =>
+    # https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
+    if typeof @model.set_file_field isnt 'function'
+      console.log 'Guts.CompositeModelForm : warning : file inputs can be handled using Backbone.FormDataTransport.Model associated with this CompositeModelForm'
+      return
+
+    for file_element in @$('form input[type=file]')
+      if file_element.files.length > 0
+        for file in file_element.files
+          console.log "CompositeModelForm : info : loading file '#{file.name}' from file field '#{file_element.name}'"
+
+          @model.set_file_field file_element.name, file
+          @model.save()
+    return
+
+
   keyup: (e) =>
     data = Backbone.Syphon.serialize(@)
     @model.set data
@@ -199,6 +217,7 @@
       'submit form': 'submitted'
       'keyup input': 'keyup'
       'keyup textarea': 'keyup'
+      'change input[type=file]': 'file_chosen'
     if @extra_events
       form_events = _.extend(form_events, _.result(@, 'extra_events'))
     form_events
@@ -252,8 +271,7 @@
     el = @$el.append(childEl)

   remove: (model) =>
-    _viewToRemove = _.where @_child_views, (view) ->
-      view.model is model
+    _viewToRemove = _.where @_child_views, (view) -> view.model is model
     if not _viewToRemove or not _viewToRemove[0]
       throw "BaseCollectionView : error : couldn\'t find view to remove from collection corresponding to model #{model.cid}"
     viewToRemove = _viewToRemove[0]


Don't set loading when query is cleared.

--- client/results.coffee
+++ client/results.coffee
@@ -16,9 +16,15 @@
   Session.set 'currentSearchQueryReady', false
   if Session.get('currentSearchLimit') and Session.get('currentSearchQuery')
     Session.set 'currentSearchQueryLoading', true
-    Meteor.subscribe 'search-results', Session.get('currentSearchQuery'), Session.get('currentSearchLimit'), ->
-      Session.set 'currentSearchQueryReady', true
-      Session.set 'currentSearchQueryLoading', false
+    Meteor.subscribe 'search-results', Session.get('currentSearchQuery'), Session.get('currentSearchLimit'),
+      onReady: ->
+        Session.set 'currentSearchQueryReady', true
+        Session.set 'currentSearchQueryLoading', false
+      onError: ->
+        # TODO: Should we display some error?
+        Session.set 'currentSearchQueryLoading', false
+  else
+    Session.set 'currentSearchQueryLoading', false

 Deps.autorun ->
   if Session.get 'indexActive'


bumping version to 2.10.4

bumping version to 2.10.4

--- client/version.coffee
+++ client/version.coffee
@@ -1 +1 @@
-@version = '2.10.3'
+@version = '2.10.4'


Rejigged the buffer passing

Rejigged the buffer passing

Hopefully a fix for the occasional errors while passing responses

--- lib/engines/dbgp/dbgp-instance.coffee
+++ lib/engines/dbgp/dbgp-instance.coffee
@@ -49,15 +49,16 @@
     while buffer.split("\0").length >= 2
       n = buffer.indexOf("\0")
       len = parseInt(buffer.slice(0, n))
-      message = buffer.slice(n+1, n+1+len)
-      buffer = buffer.slice(n+2+len)
-      if message != ""
-        o = parseString message, (err, result) =>
-          type = Object.keys(result)[0]
-          switch type
-            when "init" then @onInit result
-            when "response"
-              @parseResponse result
+      if buffer.length >= n + len + 2
+        message = buffer.slice(n+1, n+1+len)
+        buffer = buffer.slice(n+2+len)
+        if message != ""
+          o = parseString message, (err, result) =>
+            type = Object.keys(result)[0]
+            switch type
+              when "init" then @onInit result
+              when "response"
+                @parseResponse result
     return buffer

   parseResponse: (data) =>


prefix the name of uploaded file

prefix the name of uploaded file

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -58,7 +58,9 @@
                 params: Bucket: awsS3.bucket
             )

+            _prefixRand = "#{ Math.floor(Math.random() * 10000) }-#{ Date.now() }_"
             _fileName = if awsS3.destFolder then "#{ awsS3.destFolder }/#{ file.name }" else file.name
+            _fileName = "#{_prefixRand}#{_fileName}"

             fileParams =
                 Key: _fileName


layout

layout

--- models/person.coffee
+++ models/person.coffee
@@ -206,9 +206,8 @@
   @votedOnTeamIds (err, votedOn) =>
     next err if err

-
+    # judges focus on good stuff
     if @judge and votedOn.length < 15
-      # judges focus on good stuff
       filter['$or'] = [
         'scores.judge_count': { $lt: 4 }
         'scores.judge_utility': { $gt: 3 }
@@ -216,7 +215,6 @@
         'scores.judge_innovation': { $gt: 3 }
         'scores.judge_completeness': { $gt: 3 }
       ]
-

     # every third vote should be for something good
     if votedOn.length % 3 is 0


bugfix: fix strange events with called without _id

--- client/neuropils.coffee
+++ client/neuropils.coffee
@@ -1,5 +1,8 @@
 Template.neuropil_from_id_block.helpers
   neuropil_from_id: ->
+    if !this? or Object.keys(this).length ==0
+      return # Cannot deal with this situation...
+
   # We can get called 3 ways:
   # 1) From something that keeps track of what expression type is in the neuropil.
   # 2) Already as a full document from the database.


change to using fat-arrow for __express method

Previously, apps were crashing with 500 TypeError: Object #<View> has no method '_readFile'. Artifact of refactor, where __express function was moved to a method on the 'HulkHogan' class. Since application authors hand over __express method to the framework, the 'this' value was being lost. Changing to a fat-arrow binds this to the HulkHogan instance, and a reference to @_readfile is restored.

--- hulk.coffee
+++ hulk.coffee
@@ -65,7 +65,7 @@
 		return ->
 			return compiled.render options, partials

-	__express : (filename, options, callback) ->
+	__express : (filename, options, callback) =>
 		# add support for Express 3.x templating scheme
 		# in Express use like this:
 		#


Animate win-loss bars

Animate win-loss bars

--- source/javascripts/win-loss.js.coffee
+++ source/javascripts/win-loss.js.coffee
@@ -121,10 +121,12 @@
       .append("rect")
       .attr("class" , "win")
       .attr("x"     , barX)
+      .attr("width" , barWidth)
+      .attr("y"     , (d) -> y(0))
+      .attr("height", (d) -> 0)
+      .transition().delay((d,i) -> i * 300).duration(1000)
       .attr("y"     , (d) -> y d[1])
-      .attr("width" , barWidth)
-      .attr "height", (d) ->
-        y(0) - y(d[1])
+      .attr("height", (d) -> y(0) - y(d[1]))

     player.selectAll(".loss")
       .data((d) -> [d])
@@ -132,8 +134,11 @@
       .append("rect")
       .attr("class" , "loss")
       .attr("x"     , barX)
+      .attr("width" , barWidth)
+      .attr("y"     , (d) -> $(this).parent().find('.win').attr('y'))
+      .attr("height", (d) -> 0)
+      .transition().delay((d,i) -> i * 300).duration(1000)
       .attr("y"     , (d) -> y d[1] + d[2])
-      .attr("width" , barWidth)
       .attr "height", (d) ->
         h = y(d[1]) - y(d[1] + d[2])
         if h > 0 then h - 1 else h


un-nest waitsForPromise

un-nest waitsForPromise

--- spec/git-repository-async-spec.coffee
+++ spec/git-repository-async-spec.coffee
@@ -182,13 +182,13 @@
       # fail if it was rejected..
       waitsForPromise ->
         repo.checkoutHead(filePath)
-
-      runs ->
-        onSuccess = jasmine.createSpy('onSuccess')
-        waitsForPromise ->
-          repo.isPathModified(filePath).then(onSuccess)
-        runs ->
-          expect(onSuccess.mostRecentCall.args[0]).toBeFalsy()
+      runs ->
+        onSuccess = jasmine.createSpy('onSuccess')
+
+      waitsForPromise ->
+        repo.isPathModified(filePath).then(onSuccess)
+      runs ->
+        expect(onSuccess.mostRecentCall.args[0]).toBeFalsy()

     it "restores the contents of the path to the original text", ->
       fs.writeFileSync(filePath, 'ch ch changes')


moving chat box below board

moving chat box below board

--- views/index.coffee
+++ views/index.coffee
@@ -1,8 +1,3 @@
-div id: 'messages'
-div id: 'chat_input', ->
-  input id: 'text_input', type: 'text'
-  input id: 'send_button', type: 'submit', value: 'Chat'
-
 div id: 'chessboard', ->
   table cellpadding: 0, cellspacing: 0, ->
     for num in [8..1]
@@ -56,6 +51,12 @@
                 img src: 'images/chess_pieces/black_bishop.png', class: "chess_piece #{num+letter}"
               if num == 8 and letter == 'e'
                 img src: 'images/chess_pieces/black_queen.png', class: "chess_piece #{num+letter}"
+div id: 'chat_input', ->
+  input id: 'text_input', type: 'text'
+  input id: 'send_button', type: 'submit', value: 'Chat'
+
+div id: 'messages'
+
 coffeescript ->
   jQuery(document).ready ->
     now.ready ->
@@ -97,7 +98,7 @@

     now.receiveMessage = (name, message) ->
       if message
-        $("#messages").append("
" + name + ": " + message + "
")
+        $("#messages").prepend("
" + name + ": " + message + "
")

     now.receiveElement= (className, top, left) ->
       $(".#{className.replace(" ",  ".").replace(" ui-draggable", "")}").css({"position":"relative","left":"#{left}px","top":"#{top}px"})


Tests rewritten but intermittently failing due to async timing issues

--- spec/soft-wrap-indicator-spec.coffee
+++ spec/soft-wrap-indicator-spec.coffee
@@ -13,15 +13,12 @@
     waitsForPromise -> atom.packages.activatePackage('soft-wrap-indicator')

     runs ->
-      indicator = SoftWrapIndicator.view
+      indicator = SoftWrapIndicator.component.element
       expect(indicator).toExist()

   describe 'when no editor is open', ->
-    it 'has the text "Wrap"', ->
-      expect(indicator.link.textContent).toBe 'Wrap'
-
     it 'hides the indicator when there is no open editor', ->
-      expect(indicator).toBeHidden()
+      expect(indicator.querySelector('a')).toBeNull()

   describe 'when an editor is open', ->
     [editor] = []
@@ -34,33 +31,43 @@
         editor = atom.workspace.getActiveTextEditor()

     it 'shows the indicator', ->
-      expect(indicator).not.toBeHidden()
+      expect(indicator.querySelector('a')).not.toBeNull()

     it 'is not lit when the grammar is not soft wrapped', ->
-      expect(indicator.link.classList.contains('lit')).toBeFalsy()
+      anchor = indicator.querySelector('a')
+
+      expect(anchor.classList.contains('status-lit')).toBeFalsy()

     it 'is lit when the grammar is soft wrapped', ->
       waitsForPromise ->
         atom.workspace.open('sample.md')

       runs ->
-        expect(indicator.link.classList.contains('lit')).toBeTruthy()
+        anchor = indicator.querySelector('a')
+
+        expect(anchor.classList.contains('status-lit')).toBeTruthy()

     it 'is lit when the soft wrap setting is changed', ->
       editor.toggleSoftWrapped()

-      expect(indicator.link.classList.contains('lit')).toBeTruthy()
+      anchor = indicator.querySelector('a')
+
+      expect(anchor.classList.contains('status-lit')).toBeTruthy()

     it 'is lit when the grammar is changed to a soft wrapped grammar', ->
       grammar = atom.grammars.grammarForScopeName('source.gfm')
       editor.setGrammar(grammar)

-      expect(indicator.link.classList.contains('lit')).toBeTruthy()
+      anchor = indicator.querySelector('a')
+
+      expect(anchor.classList.contains('status-lit')).toBeTruthy()

     describe 'when clicked', ->
       it 'toggles the soft wrap value', ->
         spyOn(editor, 'toggleSoftWrapped')
-        indicator.click()
+
+        anchor = indicator.querySelector('a')
+        anchor.click()
         expect(editor.toggleSoftWrapped).toHaveBeenCalled()

   describe 'when the package is deactivated', ->


remove question type input bc it's default

--- init.coffee
+++ init.coffee
@@ -1,31 +1,25 @@
 exports.configure = [
   {
-    type: 'input',
     name: 'name',
     message: 'What is the name of your project?'
   },
   {
-    type: 'input',
     name: 'description',
     message: 'Describe your project'
   },
   {
-    type: 'input',
     name: 'delivery_token',
     message: 'What is your Contentful Delivery API token?'
   },
   {
-    type: 'input',
     name: 'management_token',
     message: 'What is your Contentful Managment API token?'
   },
   {
-    type: 'input',
     name: 'space_id',
     message: 'What is your Contentful Space ID?'
   },
   {
-    type: 'input',
     name: 'netlify_token',
     message: 'What is your Netlify access token?'
   }


A function in init.coffee to fix energy levels. Closes #468.

--- init.coffee
+++ init.coffee
@@ -122,6 +122,12 @@
 	]
 	type: 'bool'
 	help: 'Set predefined attributes for all players.'
+,
+	names: [
+		'fix-energy'
+	]
+	type: 'bool'
+	help: 'Set predefined energy level for all players.'
 ]

@@ -375,6 +381,16 @@
 	console.log chalk.green 'ok'

+fixEnergy = ->
+	process.stdout.write chalk.magenta 'Setting predefined energy'+'... '
+	dbConnection = createAnyDBConnection(config.DATABASE_URL)
+	dbConnection.query.sync(
+		dbConnection
+		'UPDATE characters SET energy = 220, energy_max = 220'
+	)
+	console.log chalk.green 'ok'
+
+

 sync(
 	->
@@ -393,6 +409,7 @@
 		unifyExport() if opts.unify_export
 		insertMonsters() if opts.monsters
 		fixAttrs() if opts.fix_attributes
+		fixEnergy() if opts.fix_energy
 		insertArmor() if opts.armor
 		optimize() if opts.optimize_tables # must always be the last
 		process.exit 0


DateTime: Improved "format": Fix "store" for BC dates.

--- src/elements/DateTime/DateTime.coffee
+++ src/elements/DateTime/DateTime.coffee
@@ -320,7 +320,11 @@
 		CUI.util.assert(output_format, "CUI.DateTime.format", "output_type must be in known formats", formats: @__input_formats_known, output_type: output_type)

 		# console.debug "display format", s, output_type, CUI.util.dump(output_format), output_format[type], type
-		return CUI.DateTime.formatMomentWithBc(mom, output_format[type])
+		switch type
+			when "store"
+				return CUI.DateTime.formatMoment(mom, output_format[type])
+			else
+				return CUI.DateTime.formatMomentWithBc(mom, output_format[type])

 	regexpMatcher: ->
 		YYYY:
@@ -1361,6 +1365,12 @@

 		@formatMomentWithBc(mom, dt.getCurrentFormatDisplay())

+	@formatMoment: (mom, format) ->
+		if mom.bc
+			return "-"+mom.bc
+
+		return mom.format(format)
+
 	@formatMomentWithBc: (mom, format) ->
 		if mom.bc
 			return mom.bc + " " + CUI.DateTime.defaults.bc_appendix[0]