A bunch of comment noise

A bunch of comment noise

Since it's all one file forever

--- scripts/rc.coffee
+++ scripts/rc.coffee
@@ -7,11 +7,27 @@
 prettyMs = require 'pretty-ms'
 wolfram = require 'wolfram'

-# Configs
+## Config
+##
 WOLFRAM_APPID = process.env.WOLFRAM_APPID

-# Robot hooks
+## Robot hooks
+##
 module.exports = (robot) ->
+  ## Check out someone's RC or take a look
+  ## at some graphs.
+  ##
+  ##  - .rc for Someone
+  ##     Get the last RC for a name
+  ##
+  ##  - .rc recently # TODO(sshirokov): <--
+  ##     Get RC's from the past interval
+  ##
+  ##  - .rc for Someone graph #TODO(sshirokov): <--
+  ##     Show a spark graph for the past bit for someone
+  ##
+  ##  + Intervals for interval operations that use time can be specified as
+  ##    hours or days as "3h" or "5d", etc.
   robot.respond /(?:rc)\s+for\s([^\s]+)\s*([^\s]+.*)?\s*$/i, (msg) ->
     who = msg.match[1].toLowerCase()
     options = (msg.match[2] ? "").toLowerCase()
@@ -22,6 +38,10 @@
       distance = Date.now() - self.timestamp
       msg.reply "#{who} was at #{self.score} something like #{prettyMs distance} ago"

+  ## Check in for Roll Call
+  ##  - .rc expression
+  ##    Check in your current RC value, however you choose to express it
+  ##    It will be stored as a floating point decimal number.
   robot.respond /(?:rc)\s+([^\s]+)\s*$/i, (msg) ->
     who = msg.message.user.name.toLowerCase()
     expr = msg.match[1]
@@ -39,7 +59,8 @@
     else
       submitScore expr

-# Bases and utilities
+## Bases and utilities
+##
 class RCError extends Error
 class RCBase
   prefix: "rc:"
@@ -74,7 +95,8 @@
     return cb("Couldn't make #{expr} into decimal", null) if isNaN(decimal)
     cb(false, decimal)

-# Drivers
+## Drivers
+##
 class RCScore extends RCBase
   constructor: (@who, @options={}) -> super()


login to tesla with username/password

login to tesla with username/password

--- lib/adapters/tesla/TeslaAdapter.coffee
+++ lib/adapters/tesla/TeslaAdapter.coffee
@@ -6,22 +6,30 @@
   name: 'Tesla'

   defaults:
-    accounts: [] # tokens
+    accounts: []             # { email, password }
     pollInterval:  300       # seconds between attempted vehicle data loads
-    wakeInterval:  1800      # seconds beteen wake attempts on sleeping vehicle
+    wakeInterval:  3600      # seconds beteen wake attempts on sleeping vehicle
     postWakePollInterval: 30 # seconds after wake attempt to load data
     postWakePollAttempts: 5  # times to try polling after wake attempt
     retryInterval: 300       # seconds after an error before trying again

   start: ->
     @setValid false
-    @discoverVehicles(token) for token in @get('accounts')
+    @login(account) for account in @get('accounts')
+
+  login: (account) ->
+    @log 'verbose', "Logging in to #{account.email}"
+    teslajs.login account.email, account.password, (err, result) =>
+      if err?
+        @log 'error', "Couldn't log in to #{account.email}, giving up (#{err})"
+      else
+        # XXX Does not make use of refresh token; will crap out after 2 months
+        @discoverVehicles result.authToken

   discoverVehicles: (token) ->
-    @log 'verbose', "Discovering vehicles for #{token}"
     teslajs.vehicles {authToken: token}, (err, vehicles) =>
       if err?
-        @log 'error', "Couldn't retrieve vehicles for #{token} (#{err})"
+        @log 'error', "Couldn't retrieve vehicles for #{account.email} (#{err})"
         setTimeout (=> @discoverVehicles(token)), @get('retryInterval')
       else
         for vehicle in vehicles


only expose imported functions

only expose imported functions

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -42,7 +42,7 @@

   available_context_fns = (notebook) ->
-    all_context_fns = _.extend {}, _.map(notebook.modules, (m) -> m.context_fns)...
+    _.extend {}, _.map(notebook.imports, (i) -> notebook.modules[i].context_fns)...

   init_codemirror = ->


Add ability to close all modals when closing

Signed-off-by: Steven Hoffman <f5bcacb8ce1651b7d1e56f2693431f20e085fdeb@valenciamgmt.com>

--- vendor/assets/javascripts/fustrate/components/modal.coffee
+++ vendor/assets/javascripts/fustrate/components/modal.coffee
@@ -121,7 +121,7 @@
           @modal.removeClass('locked').trigger('opened.modal')
     ), 125

-  close: =>
+  close: ({openPrevious} = {openPrevious: true}) =>
     return if @modal.hasClass('locked') || !@modal.hasClass('open')

     @modal.addClass 'locked'
@@ -141,7 +141,12 @@
             .css @settings.css.close
             .removeClass 'locked'
             .trigger 'closed.modal'
-          @openPreviousModal()
+
+          if openPrevious
+            @openPreviousModal()
+          else
+            @settings.previousModal = $()
+
         .removeClass('open')
     ), 125


Fix insert mode should grey button per tab

--- extension/packages/events.coffee
+++ extension/packages/events.coffee
@@ -73,6 +73,10 @@
   return unless browser = gBrowser.getBrowserForTab(tab)
   vimBucket.forget(browser.contentWindow)

+updateButton = (vim) ->
+  return unless rootWindow = utils.getRootWindow(vim.window)
+  updateToolbarButton(rootWindow, {blacklisted: vim.blacklisted, insertMode: vim.mode == 'insert'})
+
 # The following listeners are installed on every top level Chrome window
 windowsListeners =
   keydown:     keyListener
@@ -97,8 +101,7 @@
   TabSelect: (event) ->
     return unless window = event.originalTarget?.linkedBrowser?.contentDocument?.defaultView
     return unless vim = vimBucket.get(window)
-    return unless rootWindow = utils.getRootWindow(window)
-    updateToolbarButton(rootWindow, {blacklisted: vim.blacklisted})
+    updateButton(vim)

 # This listener works on individual tabs within Chrome Window
 tabsListener =
@@ -112,9 +115,8 @@
     if vim.mode == 'hints'
       vim.enterNormalMode()

-    return unless rootWindow = utils.getRootWindow(vim.window)
     vim.blacklisted = utils.isBlacklisted(location.spec)
-    updateToolbarButton(rootWindow, {blacklisted: vim.blacklisted})
+    updateButton(vim)

 addEventListeners = (window) ->
   for name, listener of windowsListeners


minor comment

minor comment

--- fizzylogo_populateMethods.coffee
+++ fizzylogo_populateMethods.coffee
@@ -27,6 +27,8 @@

   classToAddThemTo.addNativeMethod \
     (flParse "'s ('variable) = (value)"),
+    # in this case the arrow notation, which
+    # evaluates both sides, is pretty handy!
     flParse "in (self) do (variable ← value)"

   classToAddThemTo.addNativeMethod \


add public file tests

add public file tests

--- src/smc-hub/test/api/text_files.coffee
+++ src/smc-hub/test/api/text_files.coffee
@@ -49,3 +49,65 @@
                 expect(resp?.event).toBe('text_file_read_from_project')
                 expect(resp?.content).toBe(content)
                 done(err)
+
+    it "tries to read a private file as public and fails", (done) ->
+        api.call
+            event : 'public_get_text_file'
+            body  :
+                project_id: project_id
+                path      : 'A1/h1.txt'
+            cb    : (err, resp) ->
+                expect(resp?.event).toBe('error')
+                expect(resp?.error).toInclude('is not public')
+                done(err)
+
+    it "tries to read a nonexistent public file and fails", (done) ->
+        api.call
+            event : 'public_get_text_file'
+            body  :
+                project_id: project_id
+                path      : 'nonexistent.txt'
+            cb    : (err, resp) ->
+                expect(resp?.event).toBe('error')
+                expect(resp?.error).toInclude('is not public')
+                done(err)
+
+    it "tries to list public files in a private project and fails", (done) ->
+        api.call
+            event : 'public_get_directory_listing'
+            body  :
+                project_id: project_id
+                path      : 'A1'
+            cb    : (err, resp) ->
+                expect(resp?.event).toBe('error')
+                expect(resp?.error).toBe('not_public')
+                done(err)
+
+    it "uses the database to make a file public", (done) ->
+        api.db.user_query
+            account_id : api.account_id
+            query      : {public_paths:{project_id:project_id, path:'A1/h1.txt', description:'Handout #1'}}
+            cb         : done
+
+    it "reads a public text file in a project", (done) ->
+        api.call
+            event : 'public_get_text_file'
+            body  :
+                project_id: project_id
+                path      : 'A1/h1.txt'
+            cb    : (err, resp) ->
+                expect(resp?.event).toBe('public_text_file_contents')
+                expect(resp?.data).toBe(content)
+                done(err)
+
+    it "lists public files in a public project", (done) ->
+        api.call
+            event : 'public_get_directory_listing'
+            body  :
+                project_id: project_id
+                path      : 'A1'
+            cb    : (err, resp) ->
+                expect(resp?.event).toBe('public_directory_listing')
+                expect(resp?.result?.files?.length).toBe(1)
+                expect(resp?.result?.files?[0]).toIncludeKeys(['mtime','name','size'])
+                done(err)


socialworker: add endpoints to the list

socialworker: add endpoints to the list

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -580,6 +580,9 @@
   deleteNotificationSetting
   expireSubscription
   dispatchEvent
+  storeCredential
+  getCredential
+  deleteCredential
   fetchBotChannel
   post
   get


Team id is a number, not a string. :laughing:

--- server/index.coffee
+++ server/index.coffee
@@ -163,7 +163,7 @@
           spark.send('receiveTeams', teams.toJSON())

     spark.on 'destroyTeam', (teamId) ->
-      return  unless _.isString(teamId)
+      return  unless _.isFinite(teamId)
       attributes = {
         id: teamId
       }


change order of init_global and init_common for hash-highlighting

--- main/static/src/script/site/app.coffee
+++ main/static/src/script/site/app.coffee
@@ -1,6 +1,6 @@
 $ ->
+  init_global()
   init_common()
-  init_global()
   setTimeout ->
       ($ '.social').css('opacity', '1')
     , 2000


Attempt to make links support a custom data attr

--- src/trix/config/text_attributes.coffee
+++ src/trix/config/text_attributes.coffee
@@ -17,7 +17,9 @@
       {attachmentSelector} = Trix.AttachmentView
       matchingSelector = "a:not(#{attachmentSelector})"
       if link = Trix.findClosestElementFromNode(element, {matchingSelector})
-        {href: link.getAttribute("href"), target: link.getAttribute("target")}
+        {href: link.getAttribute("href"),
+				 target: link.getAttribute("target"),
+				 'data-is-wrapping-image': [...link.children].find(e => e.tagName === 'IMG') ? true : false }
   strike:
     tagName: "del"
     inheritable: true


Map cmd-l to editor:select-line

Map cmd-l to editor:select-line

--- keymaps/darwin.cson
+++ keymaps/darwin.cson
@@ -111,6 +111,7 @@
   'cmd-alt-p': 'editor:log-cursor-scope'
   'cmd-k cmd-u': 'editor:upper-case'
   'cmd-k cmd-l': 'editor:lower-case'
+  'cmd-l': 'editor:select-line'

 'body.platform-darwin .editor:not(.mini)':
   # Atom specific


Context.properties tests 2nd part

Context.properties tests 2nd part

--- test/contextSpec.coffee
+++ test/contextSpec.coffee
@@ -76,4 +76,12 @@
             expect(subject[name]).toBeAFunction()

         describe 'with empty PrivateContext', ->
-          xit 'pending', ->
+
+          beforeEach ->
+            CurrentContext = new Context(PrivateContext, ContextFactory)
+            subject        = CurrentContext.properties
+
+          it 'should not contain any properties', ->
+            expect(subject).not.toHaveProperties()
+            CurrentContext.defineProperty()
+            expect(subject).not.toHaveProperties()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -58,5 +58,4 @@

   equals o.test2, "coolio"

-module()
-
+module undefi


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -33,6 +33,8 @@

   equals o.closest([other, other2]), other

+test "#"
+
 test "[event] create", 2, ->
   GameObject
     create: ->


Add parsing test for letter-numbered articles

--- test/normalizers.coffee
+++ test/normalizers.coffee
@@ -19,6 +19,11 @@
 					article: 33
 				"Après l'article 47":
 					article: 47
+					placement: AssembleeNationale.AFTER
+				"Article 52 B":
+					article: 52
+				"Après l'article 53 C":
+					article: 53
 					placement: AssembleeNationale.AFTER

 			_.each TESTS, (expected, source) ->


runner spec for setting reporter by function

--- test/runner_test.coffee
+++ test/runner_test.coffee
@@ -53,6 +53,12 @@
       runner.reporter("spec")

       expect(runner.reporter()).instanceOf(require("mocha").reporters.Spec)
+
+    it "works by sending the reporter function", (suite) ->
+      runner = new Runner(suite)
+      runner.reporter(require("mocha").reporters.Dot)
+
+      expect(runner.reporter()).instanceOf(require("mocha").reporters.Dot)

     it "raises error when using invalid option", (suite) ->
       runner = new Runner(suite)


Perhaps working analysis API

Perhaps working analysis API

--- lib/analysis.coffee
+++ lib/analysis.coffee
@@ -1,5 +1,6 @@
 R = require 'ramda'

+###
 data =
 	motions:
 		guggis_kamu:
@@ -45,18 +46,77 @@
 			vote_event_source_id: 'guggis_valtuusto',
 			vote_event_target_id: 'guggis_kamu'
 			estimate: (src_vote) -> src_vote
+###
+#
+data =
+	motions:
+		guggis_kamu:
+			text: "Kannatan Guggenheimin rakentamista"
+			organization_id: "kansanmuisti"
+
+	persons:
+		CURRENT_USER:
+			name: "Current User"
+
+
+	votes: {}
+
+	organizations:
+		hel_council:
+			name: "Helsingin kaupunginvaltuusto"
+		kansanmuisti:
+			name: "Kansan muisti"
+
+	vote_events:
+		guggis_kamu: organization_id: 'kansanmuisti', motion_id: 'guggis_kamu'
+
+
+	vote_congruences:
+		guggenheim:
+			vote_event_source_id: '583801197c6d17175722ee22',
+			vote_event_target_id: 'guggis_kamu'
+			estimate: (src_vote) -> src_vote
+
+
+
+popoloize_hel_council_votes = (data, votes) ->
+	# TODO: Impute absents and empties
+	for vote in votes
+		motion_id = vote.register_id
+		data.motions[motion_id] = vote
+		vote_event_id = vote.vote_event.issue_id
+		data.vote_events[vote_event_id] =
+			organization_id: "hel_council"
+			motion_id: motion_id
+		for vote in vote.vote_event.votes
+			voter_id = vote.name
+			unless voter_id of data.persons
+				data.persons[voter_id] =
+					name: vote.name
+					party: vote.party
+			vote_id = "#{vote_event_id}/#{voter_id}"
+			data.votes[vote_id] =
+				vote_event_id: vote_event_id
+				voter_id: voter_id
+				vote: vote.vote
+				vote_value: ("FOR": 1, "AGAINST": -1)[vote.vote] ? 0
+
+	return data

 getWhere = (obj, filter) ->
 	R.filter R.whereEq(filter), obj

-estimate_vote = (voter_id, vote_event_id) ->
+getOneWhere = (obj, filter) ->
+	R.values(getWhere obj, filter)[0]
+
+
+estimate_vote = (data, voter_id, vote_event_id) ->
 	congruents = getWhere data.vote_congruences, vote_event_target_id: vote_event_id
 	ests = []
 	for _, congruent of congruents
-		src_vote = getWhere data.votes,
+		src_vote = getOneWhere data.votes,
 			vote_event_id: congruent.vote_event_source_id
 			voter_id: voter_id
-		src_vote = R.values(src_vote)[0]
 		continue unless src_vote?
 		ests.push congruent.estimate src_vote.vote_value
 	est = R.mean ests
@@ -66,23 +126,22 @@
 		vote_value: est
 	return ret

-estimated_votes = ->
+estimated_votes = (data) ->
 	est = []
 	for vote_event_id, _ of data.vote_events
 		for voter_id, _ of data.persons
-			est.push estimate_vote voter_id, vote_event_id
+			est.push estimate_vote data, voter_id, vote_event_id
 	return est

-my_agreements = (me) ->
-	ests = estimated_votes()
-	my_votes = getWhere data.votes, voter_id: me
+voter_agreements = (data, voter_id) ->
+	ests = estimated_votes(data)
+	my_votes = getWhere data.votes, voter_id: voter_id
 	for person_id, person of data.persons
 		paggs = []
 		for _, my_vote of my_votes
-			other_vote = R.values getWhere ests,
+			other_vote = getOneWhere ests,
 				voter_id: person_id
 				vote_event_id: my_vote.vote_event_id
-			other_vote = other_vote[0]
 			continue unless other_vote?
 			my_value = my_vote.vote_value
 			other_value = other_vote.vote_value
@@ -92,6 +151,40 @@
 			voter_id: person_id
 			agreement: R.mean paggs
 		yield res
-	#console.log my_votes
+
+export get_hack_data = ->
+	popoloize_hel_council_votes data, require '../importer/cases.json'
+
+export set_user_vote = (data, vote_event_id, vote_value) ->
+	# Horrible! Shouldn't depend on the key format!
+	vote_id = "#{vote_event_id}/CURRENT_USER"
+	data.votes[vote_id] =
+		voter_id: "CURRENT_USER"
+		vote_event_id: vote_event_id
+		vote_value: vote_value
+	return data
+
+export get_user_agreements = (data) ->
+	Array.from voter_agreements data, "CURRENT_USER"
+
+export get_user_party_agreements = (data) ->
+	agreements = Array.from get_user_agreements data
+	voter_party = (v) -> data.persons[v.voter_id].party
+	party_agreements = R.groupBy voter_party, (R.sortBy voter_party, agreements)
+
+	party_agreements = for party, agrs of party_agreements
+		v = R.map (R.prop 'agreement'), agrs
+		v = R.filter ((v) -> v == v), v
+
+		party: party
+		agreement: R.mean v
+
+	[good, bad] = R.partition ((v) -> v.agreement == v.agreement), party_agreements
+	R.concat (R.sortBy ((v) -> -v.agreement), good), bad
+
+
 if require.main == module
-	console.log Array.from my_agreements('user1')
+	#console.log Array.from my_agreements('user1')
+	data = popoloize_hel_council_votes(data, require './cases.json')
+	data = set_user_vote(data, 'guggis_kamu', -1.0)
+	console.log get_user_party_agreements data


Make the test node 0.11 compatible

Make the test node 0.11 compatible

--- test/test-airbud.coffee
+++ test/test-airbud.coffee
@@ -24,7 +24,9 @@
           "user-Agent": "Airbud"
       Airbud.retrieve opts, (err, data, meta) ->
         expect(err).to.be.null
-        data.should.equal "{\"msg\":\"OK\",\"received_headers\":{\"user-agent\":\"Airbud\",\"host\":\"localhost:7001\",\"connection\":\"keep-alive\"}}"
+        parsed = JSON.parse data
+        expect(parsed).to.have.property("msg").that.equals "OK"
+        expect(parsed).to.have.property("received_headers").with.property("user-agent").that.equals "Airbud"
         meta.should.have.property("statusCode").that.equals 202
         meta.should.have.property("attempts").that.equals 1
         done()


fix visitBreak/visitContinue's label lookup

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -463,26 +463,22 @@
                 @visit n.body

         visitBreak: (n) ->
-                console.log "0"
-                # unlabeled breaks just exit our enclosing exitable scope
-                ExitableScope.scopeStack.exitAft true if not n.label
-                console.log "1"
+                # unlabeled breaks just exit our enclosing exitable scope.  breaks are valid in
+                # switch statements, though, so we can't just use LoopExitableScope.findFirst()
+                return TryExitableScope.findFirstNonTry().exitAft true if not n.label
                 # for the labeled case, we search up the stack for the right scope and exitAft that one
-                scope = LoopExitableScope.findLabeled n.label
-                console.log "2"
+                scope = LoopExitableScope.findLabeled n.label.name
                 # XXX check if scope is null, throw if so
-                console.log "3"
                 scope.exitAft true
-                console.log "4"

         visitContinue: (n) ->
                 # unlabeled continue just exit our enclosing exitable scope
-                ExitableScope.scopeStack.exitFore if not n.label
+                return LoopExitableScope.findFirst().exitFore() if not n.label

                 # for the labeled case, we search up the stack for the right scope and exitFore that one
-                scope = LoopExitableScope.findLabeled n.label
+                scope = LoopExitableScope.findLabeled n.label.name
                 # XXX check if scope is null, throw if so
-                scope.exitFore true
+                scope.exitFore()

         visitFor: (n) ->
                 insertBlock = irbuilder.getInsertBlock()


Fixed problem with auto closing panels when process shuts down

Now only closes panel automatically when user stops debugger

--- lib/debugger.coffee
+++ lib/debugger.coffee
@@ -374,12 +374,12 @@

   cleanup: =>
     @processManager.cleanup()
+    NodeDebuggerView.destroy()
     @cleanupInternal()

   cleanupInternal: =>
     @client.destroy() if @client
     @client = null
-    NodeDebuggerView.destroy()
     @emit 'disconnected'

   isConnected: =>


Fixed wrong XRef size

Fixed wrong XRef size

--- lib/document.coffee
+++ lib/document.coffee
@@ -203,7 +203,7 @@
     # trailer
     @_write 'trailer'
     @_write PDFObject.convert
-      Size: @_offsets.length
+      Size: @_offsets.length + 1
       Root: @_root
       Info: @_info


Added: 'copy a file locally' test

Added: 'copy a file locally' test

--- tests/functional.coffee
+++ tests/functional.coffee
@@ -26,7 +26,7 @@
     # Cleans up local system
     after cliHelpers.stopSync
     after cliHelpers.restoreGetPassword
-    after helpers.cleanFolder syncPath
+    #after helpers.cleanFolder syncPath
     #after filesHelpers.deleteAll
     after cliHelpers.resetDatabase

@@ -52,9 +52,10 @@
                         done()
             , 3000

-    it "Rename a file locally", (done) ->
+    it.skip "Rename a file locally", (done) ->
         @timeout 5500

+        expectedContent = "TEST ME"
         fileName = "test.txt"
         filePath = "#{syncPath}/#{fileName}"
         newName = "test_changed.txt"
@@ -94,9 +95,10 @@
             , 3000

-    it "Move a file locally into a subfolder", (done) ->
+    it.skip "Move a file locally into a subfolder", (done) ->
         @timeout 5500

+        expectedContent = "TEST ME"
         fileName = 'test_changed.txt'
         filePath = "#{syncPath}/#{fileName}"
         folderName = 'test_folder'
@@ -120,9 +122,10 @@
                             done()
             , 3000

-    it "Move a file locally from a subfolder", (done) ->
+    it.skip "Move a file locally from a subfolder", (done) ->
         @timeout 5500

+        expectedContent = "TEST ME"
         fileName = 'test_changed.txt'
         folderName = 'test_folder'
         filePath = "#{syncPath}/#{folderName}/#{fileName}"
@@ -142,7 +145,29 @@
                         done()
             , 3000

-    it "Copy a file locally"
+    it "Copy a file locally", (done) ->
+        @timeout 5500
+
+        expectedContent = "TEST ME"
+        fileName = 'test.txt' # 'test_changed.txt'
+        filePath = "#{syncPath}/#{fileName}"
+        newFileName =  'test_copied.txt'
+        newFilePath = "#{syncPath}/#{newFileName}"
+
+        command = "cp #{filePath} #{newFileName}"
+        exec command, cwd: syncPath, ->
+            # file should exist at the new path
+            (fs.lstatSync.bind null, newFilePath).should.not.throw()
+
+            setTimeout ->
+                filesHelpers.getFolderContent 'root', (err, files) ->
+                    file = filesHelpers.getElementByName newFileName, files
+                    should.exist file
+                    filesHelpers.getFileContent file, (err, content) ->
+                        content.should.equal "#{expectedContent}\n"
+                        done()
+            , 3000
+
     it "Edit a file content locally"
     it "Delete a file locally"
     it "Create a big file locally"


Allow component name overriding to define custom titles

--- src/cachet.coffee
+++ src/cachet.coffee
@@ -35,7 +35,8 @@
   Fixed: 4

 declare_incident = (component_name, incident_name, incident_msg, status, msg) ->
-  component_id = _components[component_name] ? 0
+  component_id  = _components[component_name] ? 0
+  incident_name = component_name if component_id == 0

   data = JSON.stringify {
     name: incident_name,


Add tests for baseDir inheritance

Add tests for baseDir inheritance

--- spec/Network.coffee
+++ spec/Network.coffee
@@ -623,3 +623,13 @@
           chai.expect(err).to.be.an 'error'
           chai.expect(err.message).to.contain 'No process defined for inbound node'
           done()
+  describe 'baseDir setting', ->
+    it 'should set baseDir based on given graph', ->
+      g = new noflo.Graph
+      g.baseDir = root
+      n = new noflo.Network g
+      chai.expect(n.baseDir).to.equal root
+    it 'should fall back to CWD if graph has no baseDir', ->
+      g = new noflo.Graph
+      n = new noflo.Network g
+      chai.expect(n.baseDir).to.equal process.cwd()


add Perl 6 FE support

add Perl 6 FE support

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -347,6 +347,14 @@
       command: "perl6"
       args: (context) -> [context.filepath]

+  "Perl 6 FE":
+    "Selection Based":
+      command: "perl6"
+      args: (context)  -> ['-e', context.getCode()]
+    "File Based":
+      command: "perl6"
+      args: (context) -> [context.filepath]
+
   PowerShell:
     "File Based":
       command: "powershell"


Fixed C++ not running on Linux

Fixed C++ not running on Linux

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -45,6 +45,10 @@
       "File Based":
         command: "bash"
         args: (context) -> ['-c', "xcrun clang++ -fcolor-diagnostics -Wc++11-extensions -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+    else if GrammarUtils.OperatingSystem.isLinux()
+      "File Based":
+        command: "bash"
+        args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]

   'C# Script File':
     "File Based":


error handle a bit

error handle a bit

--- lib/npm-tree.coffee
+++ lib/npm-tree.coffee
@@ -7,21 +7,10 @@
   npm.load ->
     deps(moduleName, cb)

-module.exports.stringify = (results) ->
-  lines(results).join('\n')
-
-lines = (result, indent = 1) ->
-  return [toS(result, indent)] if _(result).isString()
-  _(result).chain().map (v, k) ->
-    [toS(k, indent)].concat(lines(v, indent + 1))
-  .flatten().value()
-
-toS = (name, indent) ->
-  Array(indent).join('  ') + name
-
 deps = (name, cb) ->
   npm.commands.view [name], true, (err, retval) ->
-    dependencies = Object.keys(retval[Object.keys(retval)[0]].dependencies || {})
+    return cb(err) if err?
+    dependencies = _(retval[_(retval).keys()[0]].dependencies).keys()
     async.parallel _(dependencies).map(subdeps), (err, results) ->
       cb(err, _(results).reduce( (memo, obj) ->
         _(memo).extend(obj)
@@ -35,6 +24,15 @@
       callback(err, obj)

+## Stringy outputty stuff
+module.exports.stringify = (results) ->
+  lines(results).join('\n')

-# specify @version correctly (how?)
-#/ cache pkgs we've already fetched -- npm itself caches.. yay
+lines = (result, indent = 1) ->
+  return [toS(result, indent)] if _(result).isString()
+  _(result).chain().map (v, k) ->
+    [toS(k, indent)].concat(lines(v, indent + 1))
+  .flatten().value()
+
+toS = (name, indent) ->
+  Array(indent).join('  ') + name


:art: let each operator register to select-list

Intentionally avoid to extend Base.extend() thing 3rd party user class
is not recommended to call extend()

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -246,6 +246,13 @@
   stayOnLinewise: true
   setPoint: true
   autoIndent: false
+  @transformers: []
+
+  @registerToSelectList: ->
+    @transformers.push(this)
+
+  getTransformers: ->
+    @constructor.transformers

   mutateSelection: (selection) ->
     text = @getNewText(selection.getText(), selection)
@@ -256,6 +263,7 @@
 # -------------------------
 class ToggleCase extends TransformString
   @extend()
+  @registerToSelectList()
   displayName: 'Toggle ~'
   hover: icon: ':toggle-case:', emoji: ':clap:'
   toggleCase: (char) ->
@@ -270,12 +278,14 @@

 class ToggleCaseAndMoveRight extends ToggleCase
   @extend()
+  @registerToSelectList()
   hover: null
   setPoint: false
   target: 'MoveRight'

 class UpperCase extends TransformString
   @extend()
+  @registerToSelectList()
   displayName: 'Upper'
   hover: icon: ':upper-case:', emoji: ':point_up:'
   getNewText: (text) ->
@@ -283,6 +293,7 @@

 class LowerCase extends TransformString
   @extend()
+  @registerToSelectList()
   displayName: 'Lower'
   hover: icon: ':lower-case:', emoji: ':point_down:'
   getNewText: (text) ->
@@ -291,11 +302,13 @@
 # DUP meaning with SplitString need consolidate.
 class SplitByCharacter extends TransformString
   @extend()
+  @registerToSelectList()
   getNewText: (text) ->
     text.split('').join(' ')

 class CamelCase extends TransformString
   @extend()
+  @registerToSelectList()
   displayName: 'Camelize'
   hover: icon: ':camel-case:', emoji: ':camel:'
   getNewText: (text) ->
@@ -303,6 +316,7 @@

 class SnakeCase extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "CamelCase -> camel_case"
   displayName: 'Underscore _'
   hover: icon: ':snake-case:', emoji: ':snake:'
@@ -311,6 +325,7 @@

 class PascalCase extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "text_before -> TextAfter"
   displayName: 'Pascalize'
   hover: icon: ':pascal-case:', emoji: ':triangular_ruler:'
@@ -319,6 +334,7 @@

 class DashCase extends TransformString
   @extend()
+  @registerToSelectList()
   displayName: 'Dasherize -'
   hover: icon: ':dash-case:', emoji: ':dash:'
   getNewText: (text) ->
@@ -326,6 +342,7 @@

 class TitleCase extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "CamelCase -> Camel Case"
   displayName: 'Titlize'
   getNewText: (text) ->
@@ -333,6 +350,7 @@

 class EncodeUriComponent extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "URI encode string"
   displayName: 'Encode URI Component %'
   hover: icon: 'encodeURI', emoji: 'encodeURI'
@@ -341,6 +359,7 @@

 class DecodeUriComponent extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "Decode URL encoded string"
   displayName: 'Decode URI Component %%'
   hover: icon: 'decodeURI', emoji: 'decodeURI'
@@ -349,6 +368,7 @@

 class TrimString extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "trim() string"
   displayName: 'Trim string'
   getNewText: (text) ->
@@ -356,6 +376,7 @@

 class CompactSpaces extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "Compact multiple spaces to single space"
   displayName: 'Compact space'
   mutateSelection: (selection) ->
@@ -440,35 +461,13 @@
     @stdoutBySelection.get(selection)

 # -------------------------
-class TransformStringBySelectList extends Operator
+class TransformStringBySelectList extends TransformString
   @extend()
   @description: "Transform string by specified oprator selected from select-list"
   requireInput: true
-  # Member of transformers can be either of
-  # - Operation class name: e.g 'CamelCase'
-  # - Operation class itself: e.g. CamelCase
-  transformers: [
-    'CamelCase'
-    'DashCase'
-    'SnakeCase'
-    'TitleCase'
-    'EncodeUriComponent'
-    'DecodeUriComponent'
-    'Reverse'
-    'Surround'
-    'MapSurround'
-    'IncrementNumber'
-    'DecrementNumber'
-    'JoinByInput'
-    'JoinWithKeepingSpace'
-    'SplitString'
-    'LowerCase'
-    'UpperCase'
-    'ToggleCase'
-  ]

   getItems: ->
-    @transformers.map (klass) ->
+    @getTransformers().map (klass) ->
       klass = Base.getClass(klass) if _.isString(klass)
       displayName = klass::displayName if klass::hasOwnProperty('displayName')
       displayName ?= _.humanizeEventName(_.dasherize(klass.name))
@@ -735,6 +734,7 @@

 class JoinWithKeepingSpace extends TransformString
   @extend()
+  @registerToSelectList()
   input: ''
   requireTarget: false
   trim: false
@@ -757,6 +757,7 @@

 class JoinByInput extends JoinWithKeepingSpace
   @extend()
+  @registerToSelectList()
   @description: "Transform multi-line to single-line by with specified separator character"
   hover: icon: ':join:', emoji: ':couple:'
   requireInput: true
@@ -772,6 +773,7 @@
 class JoinByInputWithKeepingSpace extends JoinByInput
   @description: "Join lines without padding space between each line"
   @extend()
+  @registerToSelectList()
   trim: false
   join: (rows) ->
     rows.join(@input)
@@ -780,6 +782,7 @@
 # String suffix in name is to avoid confusion with 'split' window.
 class SplitString extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "Split single-line into multi-line by splitting specified separator chars"
   hover: icon: ':split-string:', emoji: ':hocho:'
   requireInput: true
@@ -797,6 +800,7 @@

 class Reverse extends TransformString
   @extend()
+  @registerToSelectList()
   @description: "Reverse lines(e.g reverse selected three line)"
   mutateSelection: (selection) ->
     swrap(selection).expandOverLine()


:art:

:art:

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -44,7 +44,6 @@

     scopes = scope.getScopesArray()
     return true if scopes.indexOf('entity.other.attribute-name.html') isnt -1
-
     return false unless @hasTagScope(scopes)

     scopes.indexOf('punctuation.definition.tag.html') isnt -1 or


Getting Physical

Getting Physical

--- src/Physical.coffee
+++ src/Physical.coffee
@@ -8,6 +8,7 @@
   window.Physical = (I, self) ->
     $.reverseMerge I,
       dynamic: false
+      rotatable: false

     fixDef = new b2FixtureDef()
     fixDef.density = 1.0
@@ -20,7 +21,7 @@

     if I.dynamic
       bodyDef.type = b2Body.b2_dynamicBody
-      bodyDef.fixedRotation = true
+      bodyDef.fixedRotation = !I.rotatable
     else
       bodyDef.type = b2Body.b2_staticBody

@@ -37,6 +38,7 @@
     self.bind "step", ->
       I.x = (body.GetPosition().x / SCALE) - (I.width / 2)
       I.y = (body.GetPosition().y / SCALE) - (I.height / 2)
+      I.rotation = body.GetAngle()

     applyImpulse: (vector) ->
       body.ApplyImpulse(new b2Vec2(vector.x, vector.y), body.GetPosition())


[Finishes #35735589] update polyglot's literate docs

--- src/polyglot.coffee
+++ src/polyglot.coffee
@@ -1,7 +1,11 @@
 _ = require "underscore"

 # Use this as a mixin for classes that have words to name / translate.
-# See _./itemerge.coffee_ as an example of what the nested @words hash is like.
+# See [lin](http://astrolet.github.com/lin/)'s `Ensemble`
+# as an example of what the `@words` object must be like.
+# For a more varied example of words / localization data, see
+# [there](http://astrolet.github.com/there/)'s
+# [care](http://astrolet.github.com/there/annotations/care.html).

 # Suppose an attribute of "name" - quite usual, often the only (necessary).
 # Thus expect that `@words.expect is ['name']`, while the actual words data
@@ -16,9 +20,11 @@
 # A value of false means it's deemed unfit for Hellenistic Astrology.

 # In order to be `@translatable` a collection needs to have its initial data
-# in one or more hashes - look at ./itemerge.coffee and how `@planets.the`
-# and `@planets.attributes.use` are expected in addition to `@words`.
-# There can be more than one initialization hashes, but a single `@words`
+# set up.  Look at [lin](http://astrolet.github.com/lin)'s
+# [Ensemble](http://astrolet.github.com/lin/annotations/ensemble.html)
+# - more pecifically how `@planets.the` and `@planets.attributes.use`
+# are expected in addition to `@words`.
+# There can be more than one initialization objects, but a single `@words`
 # data set for translation - basically the collection as a whole.

 # Example usage within the `constructor` / `initialize`:


Typo fix

Typo fix

--- src/protocol.coffee
+++ src/protocol.coffee
@@ -90,9 +90,9 @@
     .then((sources) ->
       tests = {}
       for source in sources
-        continue unless payload.tests
+        continue unless source.tests
         name = if source.library then "#{source.library}/#{source.name}" else source.name
-        tests[name] = payload.tests
+        tests[name] = source.tests
       return tests
     )
     .then(((tests) -> callback(null, tests)), callback)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/animated.coffee
+++ src/animated.coffee
@@ -112,6 +112,7 @@
     sprite = I.spriteLookup[frames[I.currentFrameIndex]]

     updateSprite(sprite)
+
     I.hflip = I.activeAnimation.transform?[I.currentFrameIndex]?.hflip
     I.vflip = I.activeAnimation.transform?[I.currentFrameIndex]?.vflip


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/animated.coffee
+++ src/animated.coffee
@@ -64,7 +64,7 @@
     transform: Matrix.IDENTITY

   loadByName = (name, callback) ->
-    url = "#{BASE_URL}/data/#{name}.animation?#{new Date().getTime()}"
+    url = "#{BASE_URL}/animations/#{name}.animation?#{new Date().getTime()}"

     $.getJSON url, (data) ->
       I.data = data


Fix contexts in api-mock

Fix contexts in api-mock

--- src/api-mock.coffee
+++ src/api-mock.coffee
@@ -19,16 +19,16 @@
     @configuration = config
     @app = express()

-    if configuration.options['ssl-enable']
+    if @configuration.options['ssl-enable']
       sslSupport = new SslSupport(
         @app,
-            port: configuration.options['ssl-port'],
-            host: configuration.options['ssl-host'],
-            cert: configuration.options['ssl-cert'],
-            key: configuration.options['ssl-key']
+            port: @configuration.options['ssl-port'],
+            host: @configuration.options['ssl-host'],
+            cert: @configuration.options['ssl-cert'],
+            key: @configuration.options['ssl-key']
       )

-    if !configuration.options['cors-disable']
+    if !@configuration.options['cors-disable']
       corsSupport = new CorsSupport @app

   run: () ->
@@ -40,7 +40,7 @@

     # Get JSON representation of the blueprint file
     ast_json = ""
-    protagonist.parse data,  (error, result) ->
+    protagonist.parse data,  (error, result) =>
       if error? then throw error
       ast_json = result.ast


RESTXMLService now serializes boolean elements into the request XML.

--- test/unit/rest_xml_service.spec.coffee
+++ test/unit/rest_xml_service.spec.coffee
@@ -166,9 +166,25 @@
         """
         matchXML(buildRequest(params).body, xml)

-      xit 'serializes booleans (true)', ->
+      it 'serializes booleans (true)', ->
+        operation.i = {Data:{t:'o',l:'body',m:{Enabled:{t:'b'}}}}
+        params = { Enabled: true }
+        xml = """
+        <Data xmlns="#{xmlns}">
+          <Enabled>true</Enabled>
+        </Data>
+        """
+        matchXML(buildRequest(params).body, xml)

-      xit 'serializes booleans (false)', ->
+      it 'serializes booleans (false)', ->
+        operation.i = {Data:{t:'o',l:'body',m:{Enabled:{t:'b'}}}}
+        params = { Enabled: false }
+        xml = """
+        <Data xmlns="#{xmlns}">
+          <Enabled>false</Enabled>
+        </Data>
+        """
+        matchXML(buildRequest(params).body, xml)

       xit 'serializes timestamps (iso8601)', ->


Only perform reads and observing on @key if it exists, otherwise assume the @model is the root value to be used.

--- src/bindings.coffee
+++ src/bindings.coffee
@@ -92,7 +92,10 @@

   # Syncs up the view binding with the model.
   sync: =>
-    @set @view.adapters[@key.interface].read @model, @key.path
+    @set if @key
+      @view.adapters[@key.interface].read @model, @key.path
+    else
+      @model

   # Publishes the value currently set on the input element back to the model.
   publish: =>
@@ -112,7 +115,7 @@
   # to the model.
   bind: =>
     @binder.bind?.call @, @el
-    @view.adapters[@key.interface].subscribe @model, @key.path, @sync
+    @view.adapters[@key.interface].subscribe(@model, @key.path, @sync) if @key
     @sync() if @view.config.preloadData

     if @options.dependencies?.length
@@ -135,7 +138,7 @@
   # Unsubscribes from the model and the element.
   unbind: =>
     @binder.unbind?.call @, @el
-    @view.adapters[@key.interface].unsubscribe @model, @key.path, @sync
+    @view.adapters[@key.interface].unsubscribe(@model, @key.path, @sync) if @key

     if @dependencies.length
       for dep in @dependencies
@@ -147,9 +150,9 @@
   # the old model first and then re-binds with the new model.
   update: (models = {}) =>
     if models[@rootKey.path]
-      @view.adapters[@key.interface].unsubscribe @model, @key.path, @sync
+      @view.adapters[@key.interface].unsubscribe(@model, @key.path, @sync) if @key
       @setModel()
-      @view.adapters[@key.interface].subscribe @model, @key.path, @sync
+      @view.adapters[@key.interface].subscribe(@model, @key.path, @sync) if @key
       @sync()

     @binder.update?.call @, models
@@ -214,8 +217,6 @@
 class Rivets.TextBinding extends Rivets.Binding
   # Initializes a text binding for the specified view and text node.
   constructor: (@view, @el, @type, @keypath, @options = {}) ->
-    interfaces = (k for k, v of @view.adapters)
-    tokens = Rivets.KeypathParser.parse(@keypath, interfaces, '.')
     @formatters = @options.formatters || []
     @setModel()


[FIX] Limit the size of ffmpeg file buffer

--- src/streamer.coffee
+++ src/streamer.coffee
@@ -60,10 +60,17 @@
     dst = me.buffer_path + 'stream.ogg'
     if isURL (path)
       console.log 'Buffering from url ' + path
-      me.ctr_buffering = spawn 'ffmpeg', ["-y", "-r", "48000", "-i", path,  me.buffer_path + 'stream.wav']
+      # -fs limit the size of buffer file then reload
+      me.ctr_buffering = spawn 'ffmpeg', ["-y", "-r", "48000", "-i", path, "-fs", 10000000,  me.buffer_path + 'stream.wav']
+      me.ctr_buffering .on 'close', (code) ->
+        if code == 0
+          me.prototype.startBuffering(path, f)
+
       me.prototype.initSample()
       me.prototype.setState('ready')
-      setTimeout me.prototype.play, 1500
+
+      if me.state == "pause" || me.state == "terminated"
+        setTimeout me.prototype.play, 1500

     else
       path = me.streamPath + path;
@@ -124,6 +131,7 @@
         #me.prototype.initSample()

   play:  () ->
+    console.log 'play'
     if me.state == "pause" || me.state == "terminated"
       me.prototype.setState("ready")
       return


refactored lexical category inference for {} nodes

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -36,22 +36,19 @@
   err = (node, message) ->
     syntaxError message, file: opts.file, line: node.startLine, col: node.startCol, aplCode: opts.aplCode

-  (markOperators = (node) ->
+  assert VERB < ADVERB < CONJUNCTION # we are relying on this ordering below
+  (categorizeLambdas = (node) ->
     switch node[0]
       when 'body', 'guard', 'assign', 'index', 'lambda', 'expr'
-        r = 0; for i in [1...node.length] by 1 when node[i] then r |= markOperators node[i]
-        if r and node[0] is 'lambda'
-          if r & 1 then node.isAdverb = true
-          if r & 2 then node.isConjunction = true
-          0
-        else
-          r
+        r = VERB
+        for i in [1...node.length] by 1 when node[i] then r = Math.max r, categorizeLambdas node[i]
+        if node[0] is 'lambda' then (node.category = r; VERB) else r
       when 'string', 'number', 'embedded' then 0
       when 'symbol'
         switch node[1]
-          when '⍺⍺', '⍶', '∇∇' then 1
-          when '⍵⍵', '⍹' then 3
-          else 0
+          when '⍺⍺', '⍶', '∇∇' then ADVERB
+          when '⍵⍵', '⍹' then CONJUNCTION
+          else VERB
       else assert 0
   ) ast

@@ -74,20 +71,20 @@
           for i in [1...node.length]
             queue.push extend (body = node[i]),
               scopeNode: scopeNode
-              scopeDepth: (d = scopeNode.scopeDepth + 1 + !!(node.isAdverb or node.isConjunction))
+              scopeDepth: d = scopeNode.scopeDepth + 1 + (node.category isnt VERB)
               nSlots: 3
               vars: v = extend Object.create(vars),
                 '⍵': slot: 0, scopeDepth: d, category: NOUN
                 '∇': slot: 1, scopeDepth: d, category: VERB
                 '⍺': slot: 2, scopeDepth: d, category: NOUN
-            if node.isConjunction
+            if node.category is CONJUNCTION
               v['⍵⍵'] = v['⍹'] = slot: 0, scopeDepth: d - 1, category: VERB
               v['∇∇'] =          slot: 1, scopeDepth: d - 1, category: CONJUNCTION
               v['⍺⍺'] = v['⍶'] = slot: 2, scopeDepth: d - 1, category: VERB
-            else if node.isAdverb
+            else if node.category is ADVERB
               v['⍺⍺'] = v['⍶'] = slot: 0, scopeDepth: d - 1, category: VERB
               v['∇∇'] =          slot: 1, scopeDepth: d - 1, category: ADVERB
-          if node.isConjunction then CONJUNCTION else if node.isAdverb then ADVERB else VERB
+          node.category ? VERB
         when 'string', 'number', 'embedded' then NOUN
         when 'index'
           for i in [2...node.length] by 1 when node[i] and visit(node[i]) isnt NOUN then err node, 'Indices must be nouns.'
@@ -231,7 +228,7 @@
             v = node.scopeNode.vars['⍠']
             ly.concat GET, v.scopeDepth, v.slot, lx, DYA
           else err node
-        if node.isAdverb or node.isConjunction then [LAM, f.length + 1].concat f, RET else f
+        if node.category isnt VERB then [LAM, f.length + 1].concat f, RET else f
       when 'string'
         # Strings of length one are scalars, all other strings are vectors:
         #   ⍴⍴''   <=> ,1


Count and display the message parts correctly.

--- src/telegram.coffee
+++ src/telegram.coffee
@@ -85,7 +85,7 @@
         parts = (Math.ceil length / limit) - 1

         @robot.logger.debug "Message length: " + length
-        @robot.logger.debug "Message parts: " + parts
+        @robot.logger.debug "Message parts: " + (parts + 1)

         for part in [0..parts]
             offset = part * limit


Add debug to better identify the issues in #46 and #47

--- src/telegram.coffee
+++ src/telegram.coffee
@@ -171,6 +171,8 @@
     # or poll update.
     ###
     handleUpdate: (update) ->
+
+        @robot.logger.debug update

         message = update.message
         @robot.logger.info "Receiving message_id: " + message.message_id


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -23,5 +23,5 @@
     if value?
       I[name] = options.value
     else
-
+      # Initial
       I[name] = 0 unless I[name]


reset energy on death

reset energy on death

--- src/universe.coffee
+++ src/universe.coffee
@@ -238,6 +238,7 @@
         @self.position.x = Math.random() * constants.universeWidth
         @self.position.y = Math.random() * constants.universeHeight
         @self.flash = 1
+        @self.energy = constants.maxEnergy
         # hacky...  should draw this in the dragon drawing routine?
         # update some kinda scoreboard?


Removed debug log

Removed debug log

--- .metalsmith/metal.coffee
+++ .metalsmith/metal.coffee
@@ -101,7 +101,6 @@
     # Note that the zero index (0) wasn't actually split on the type,
     # so make sure not to add type back.
     if not typeMatch and i != 0 then block = "
type:" + block
-    console.log "[#{i}] Writing block: '#{block}'"
     # Now add our block to the final list
     blocks.push "


Reorder set() and update() functions

Reorder set() and update() functions

--- src/wrangler.coffee
+++ src/wrangler.coffee
@@ -40,6 +40,14 @@
 	wrangler.element = template
 	wrangler.template = template.innerHTML

+	# Set a property and then update
+	wrangler.set = (index, property, value) ->
+		if value?
+			@model[index][property] = value
+		else
+			@model[index] = property
+		@update()
+
 	# Update the DOM
 	wrangler.update = ->
 		if @model instanceof Array
@@ -50,14 +58,6 @@
 		else
 			@element.innerHTML = window.Milk.render(@template, @model)

-	# Set a property and then update
-	wrangler.set = (index, property, value) ->
-		if value?
-			@model[index][property] = value
-		else
-			@model[index] = property
-		@update()
-
 	# Do an inital update of the DOM
 	wrangler.update()


Fix late work acceptance

Fix late work acceptance

--- tutor/src/components/scores/index.cjsx
+++ tutor/src/components/scores/index.cjsx
@@ -13,6 +13,7 @@
 LoadableItem = require '../loadable-item'
 ScoresExport = require './export'
 {CoursePeriodsNavShell} = require '../course-periods-nav'
+BindStoreMixin = require '../bind-store-mixin'

 StudentDataSorter = require './student-data-sorter'

@@ -21,6 +22,10 @@

   contextTypes:
     router: React.PropTypes.func
+
+  mixins: [BindStoreMixin]
+  bindStore: ScoresStore
+  bindUpdate: -> @updateStudentData()

   propTypes:
     courseId: React.PropTypes.string.isRequired


Added guards and default values.

Added guards and default values.

--- SwaggerImporter.coffee
+++ SwaggerImporter.coffee
@@ -10,53 +10,53 @@
           swaggerRequestTitle = swaggerRequestValue.summary
         else
           swaggerRequestTitle = swaggerRequestPath
-
+
         headers = {}
         queries = {}
         formData = {}
         body
-
+
         # Extract contentType from Consumes and add the first one to Headers
         if swaggerRequestValue.consumes
           for contentType in swaggerRequestValue.consumes
             headers["Content-Type"] = contentType
             break
-
+
         # Extract Headers and Query params
         for index, swaggerRequestParamValue of swaggerRequestValue.parameters
-
+
           # Add Queries
           if swaggerRequestParamValue.in == 'query' and swaggerRequestParamValue.type == 'string'
-            queries[swaggerRequestParamValue.name] = swaggerRequestParamValue.name
-
+            queries[swaggerRequestParamValue.name] = swaggerRequestParamValue.name
+
           # Add Headers
           if swaggerRequestParamValue.in == 'header' and swaggerRequestParamValue.type == 'string'
             headers[swaggerRequestParamValue.name] = swaggerRequestParamValue.name
-
-          # Add Url Encoded
+
+          # Add Url Encoded
           if swaggerRequestParamValue.in == 'formData' and swaggerRequestParamValue.type == 'string'
             formData[swaggerRequestParamValue.name] = swaggerRequestParamValue.name
-
+
           # Add Body
           if swaggerRequestParamValue.in == 'body' #Only string
             body = @json_from_definition_schema swaggerCollection, swaggerRequestParamValue.schema
-
+
         swaggerRequestUrl = @createSwaggerRequestUrl swaggerCollection, swaggerRequestPath, queries
         swaggerRequestMethod = swaggerRequestMethod.toUpperCase()
-
+
         # Create Paw request
         pawRequest = context.createRequest swaggerRequestTitle, swaggerRequestMethod, swaggerRequestUrl
-
+
         # Add Headers
         for key, value of headers
           pawRequest.setHeader key, value
-
+
         # Add Basic Auth if required
         pawRequest.setHeader "Authorization", "HTTP Basic Auth (Username/Password)" if @has_basic_auth swaggerCollection, swaggerRequestValue
-
+
         # Set raw body
         pawRequest.body = body if body
-
+
         # Set Form URL-Encoded body
         if Object.keys(formData).length > 0
             # Set Form URL-Encoded body
@@ -65,19 +65,20 @@
             # Set Multipart body
             else if headers['Content-Type'] == "multipart/form-data"
               pawRequest.multipartBody = formData
-
+
         return pawRequest
-
+
     @has_basic_auth = (swaggerCollection, swaggerRequestValue) ->
-      for security in swaggerRequestValue.security
-        for own key, value of security
-          if swaggerCollection.securityDefinitions[key] and swaggerCollection.securityDefinitions[key].type == 'basic'
-            return true
-          break
+      if swaggerRequestValue.security
+        for security in swaggerRequestValue.security
+          for own key, value of security
+            if swaggerCollection.securityDefinitions[key] and swaggerCollection.securityDefinitions[key].type == 'basic'
+              return true
+            break
       return false
-
+
     @json_from_definition_schema = (swaggerCollection, property, indent = 0) ->
-
+
         if property.type == 'string'
             s = "\"string\""
         else if property.type == 'integer'
@@ -87,7 +88,7 @@
         else if typeof(property) == 'object'
             indent_str = Array(indent + 1).join('    ')
             indent_str_children = Array(indent + 2).join('    ')
-
+
             if property.items
               property = property.items
               s = "[\n" +
@@ -96,80 +97,81 @@
             else
               property = swaggerCollection.definitions[property["$ref"].split('/').pop()] if property["$ref"]
               property = property.properties if property.properties # Skip properties
-
+
               s = "{\n" +
                   ("#{indent_str_children}\"#{key}\" : #{@json_from_definition_schema(swaggerCollection, value, indent+1)}" for key, value of property).join(',\n') +
                   "\n#{indent_str}}"

         return s
-
+
     @createSwaggerRequestUrl = (swaggerCollection, swaggerRequestPath, queries) ->
-
+
         # Build swaggerRequestQueries
         if Object.keys(queries).length > 0
           swaggerRequestQueries = []
-
+
         for key, value of queries
           swaggerRequestQueries.push "#{key}=#{value}"
-
-        swaggerRequestUrl = swaggerCollection.schemes[0] + '://' +
-        swaggerCollection.host +
-        swaggerCollection.basePath +
-        swaggerRequestPath
-
+
+        swaggerRequestUrl = (if swaggerCollection.schemes then swaggerCollection.schemes[0] else 'http') +
+          '://' +
+          (swaggerCollection.host or 'echo.luckymarmot.com') +
+          (swaggerCollection.basePath or '') +
+          swaggerRequestPath
+
         if swaggerRequestQueries
           swaggerRequestUrl = swaggerRequestUrl + '?' + swaggerRequestQueries.join('&')
-
+
         return swaggerRequestUrl
-
+
     @createPawGroup = (context, swaggerCollection, swaggerRequestPathName, swaggerRequestPathValue) ->

         # Create Paw group
         pawGroup = context.createRequestGroup swaggerRequestPathName

         for own swaggerRequestMethod, swaggerRequestValue of swaggerRequestPathValue
-
+
             # Create a Paw request
             pawRequest = @createPawRequest context, swaggerCollection, swaggerRequestPathName, swaggerRequestMethod, swaggerRequestValue
-
+
             # Add request to root group
             pawGroup.appendChild pawRequest

         return pawGroup
-
+
     @importString = (context, string) ->
-
+
         try
           # Try JSON parse
           swaggerCollection = JSON.parse string
         catch error
           # Try YAML parse
           swaggerCollection = yaml.load string
-
+
         schema = readFile "schema.json"
         valid = tv4.validate swaggerCollection, JSON.parse(schema)

         if not valid
           throw new Error "Invalid Swagger file (invalid schema or schema version < 2.0)"
-
+
         if swaggerCollection
-
+
           # Define host to localhost if not specified in file
           swaggerCollection.host = if swaggerCollection.host then swaggerCollection.host else 'localhost'
-
+
           # Create a PawGroup
           pawRootGroup = context.createRequestGroup swaggerCollection.info.title
-
+
           # Add Swagger groups
           for own swaggerRequestPathName, swaggerRequestPathValue of swaggerCollection.paths
-
+
             pawGroup = @createPawGroup context, swaggerCollection, swaggerRequestPathName, swaggerRequestPathValue

             # Add group to root
             pawRootGroup.appendChild pawGroup
-
+
           return true
-
+
     return

 SwaggerImporter.identifier = "com.luckymarmot.PawExtensions.SwaggerImporter"


Add detailed error message

Add detailed error message

--- SwaggerImporter.coffee
+++ SwaggerImporter.coffee
@@ -173,7 +173,7 @@
         valid = tv4.validate swaggerCollection, JSON.parse(schema)

         if not valid
-          throw new Error "Invalid Swagger file (invalid schema or schema version < 2.0)"
+          throw new Error "Invalid Swagger file (invalid schema or schema version < 2.0):\n#{ tv4.error }"

         if swaggerCollection


Collect formData and body

Collect formData and body

--- SwaggerImporter.coffee
+++ SwaggerImporter.coffee
@@ -12,6 +12,8 @@

         headers = []
         queries = []
+        formData = []
+        body = []

         # Extract Headers and Query params
         for index, swaggerRequestParamValue of swaggerRequestValue.parameters
@@ -23,6 +25,14 @@
           # Add Headers
           if swaggerRequestParamValue.in == 'header' and swaggerRequestParamValue.type == 'string'
             headers.push swaggerRequestParamValue.name
+
+          # Add Url Encoded
+          if swaggerRequestParamValue.in == 'formData' and swaggerRequestParamValue.type == 'string'
+            body.push swaggerRequestParamValue.name
+
+          # Add Body
+          if swaggerRequestParamValue.in == 'body' #Only string
+            body.push swaggerRequestParamValue.name

         swaggerRequestUrl = @createSwaggerRequestUrl swaggerCollection, swaggerRequestPath, queries
         swaggerRequestMethod = swaggerRequestMethod.toUpperCase()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -18,6 +18,6 @@

   equals obj.I.maxHealth, 150

-test "", ->
+test "should ", ->

 module()


デバッグ用コードを抜き忘れてた

デバッグ用コードを抜き忘れてた

--- cbt/app/scripts/controllers/exam.coffee
+++ cbt/app/scripts/controllers/exam.coffee
@@ -27,7 +27,6 @@
     url = "https://j5odg2vd05.execute-api.ap-northeast-1.amazonaws.com/prod/butaGetJSON?exam=#{$routeParams.examName}&pass=#{$scope.$storage.pass}"

     $http({method: "GET", url: url}).then((data) ->
-      console.log data
       questions = []
       for q in data.data.questions
         $scope.check[q.id] = {}


Update link to CGUs

Update link to CGUs

--- client/app/views/steps/agreement.coffee
+++ client/app/views/steps/agreement.coffee
@@ -39,7 +39,7 @@
                 require '../../assets/sprites/icon-safe.svg'
                 require '../../assets/sprites/icon-user.svg'
             ]
-            cguLink: 'https://cozy.io'
+            cguLink: 'https://files.cozycloud.cc/cgu.pdf'

     onSubmit: (event)->


bugfix : fix resize algorithm for screen size

--- client/app/models/photoprocessor.coffee
+++ client/app/models/photoprocessor.coffee
@@ -18,22 +18,27 @@
         photo.img.onload = ->
             next()

-resize = (photo, MAX_WIDTH, MAX_HEIGHT) ->
-    width = photo.img.width
-    height = photo.img.height
-    if width > height and height > MAX_HEIGHT
-        newWidth = width * MAX_HEIGHT / height
-        newHeight = MAX_HEIGHT
-    else if width > MAX_WIDTH
-        newWidth = MAX_WIDTH
-        newHeight = height * MAX_WIDTH / width
+resize = (photo, MAX_WIDTH, MAX_HEIGHT, fill) ->
+
+    max = width: MAX_WIDTH, height:MAX_HEIGHT
+    if (photo.img.width > photo.img.height) is fill
+        ratiodim = 'height'
+    else
+        ratiodim = 'width'
+
+    ratio = max[ratiodim] / photo.img[ratiodim]
+
+    newdims =
+        height: ratio*photo.img.height
+        width: ratio*photo.img.width
+

     # use canvas to resize the image
     canvas = document.createElement 'canvas'
-    canvas.width = MAX_WIDTH
-    canvas.height = MAX_HEIGHT
+    canvas.width = if fill then MAX_WIDTH else newdims.width
+    canvas.height = if fill then MAX_HEIGHT else newdims.height
     ctx = canvas.getContext '2d'
-    ctx.drawImage photo.img, 0, 0, newWidth, newHeight
+    ctx.drawImage photo.img, 0, 0, newdims.width, newdims.height
     return canvas.toDataURL photo.file.type

 blobify = (dataUrl, type) ->
@@ -47,7 +52,7 @@
 # create photo.thumb_du : a DataURL encoded thumbnail of photo.img
 makeThumbDataURI = (photo, next) ->

-    photo.thumb_du = resize photo, 100, 100
+    photo.thumb_du = resize photo, 100, 100, true
     # let the view update itself
     photo.set 'thumbsrc', photo.thumb_du
     photo.set 'state'   , 'thumbed'
@@ -57,7 +62,7 @@
 # create photo.screen_du : a DataURL encoded thumbnail of photo.img
 makeScreenDataURI = (photo, next) ->

-    photo.screen_du = resize photo, 1200, 800
+    photo.screen_du = resize photo, 1200, 800, false

     next()


utils: use the new generalized utils.extend

--- client/landing/app/MainApp/utils.coffee
+++ client/landing/app/MainApp/utils.coffee
@@ -1,5 +1,4 @@
-
-__utils.extend
+__utils.extend __utils,

   getPaymentMethodTitle: (billing)->
     # for convenience, accept either the payment method, or the billing object


IDEView: Update snapshot on local storage when webterm connected to save terminal session id on storage.

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -148,6 +148,7 @@

     terminalPane.once 'WebtermCreated', =>
       terminalPane.webtermView.on 'click', @bound 'click'
+      @emit 'UpdateWorkspaceSnapshot'

       unless joinUser
         change        =


Removed unused carrier groups from Sankey

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -9,7 +9,7 @@
       {id: 'natural_gas',        column: 0, label: "gas"},
       {id: 'nuclear',       column: 0, label: "nuclear"},
       {id: 'biomass_products',       column: 0, label: "biomass"},
-      {id: 'mixed_flows',       column: 0, label: "mixed flows"},
+      #{id: 'mixed_flows',       column: 0, label: "mixed flows"},
       {id: 'electricity',       column: 0, label: "renewable electricity"},

       {id: 'households',  column: 1},
@@ -36,7 +36,7 @@
       {left: 'natural_gas',      right: 'households', gquery: 'natural_gas_to_households_in_sankey_ufp', color: 'steelblue'},
       {left: 'nuclear',      right: 'households', gquery: 'nuclear_to_households_in_sankey_ufp', color: 'black'},
       {left: 'biomass_products',      right: 'households', gquery: 'biomass_products_to_households_in_sankey_ufp', color: 'green'},
-      {left: 'mixed_flows',      right: 'households', gquery: 'mixed_flows_to_households_in_sankey_ufp', color: 'steelblue'},
+      #{left: 'mixed_flows',      right: 'households', gquery: 'mixed_flows_to_households_in_sankey_ufp', color: 'steelblue'},
       {left: 'electricity',      right: 'households', gquery: 'electricity_to_households_in_sankey_ufp', color: 'green'},
       {left: 'coal_and_derivatives',      right: 'buildings', gquery: 'coal_and_derivatives_to_buildings_in_sankey_ufp', color: 'black'},
       {left: 'oil_and_derivatives',      right: 'buildings', gquery: 'oil_and_derivatives_to_buildings_in_sankey_ufp', color: 'brown'},
@@ -46,7 +46,7 @@
       {left: 'natural_gas',      right: 'buildings', gquery: 'natural_gas_to_buildings_in_sankey_ufp', color: 'steelblue'},
       {left: 'nuclear',      right: 'buildings', gquery: 'nuclear_to_buildings_in_sankey_ufp', color: 'black'},
       {left: 'biomass_products',      right: 'buildings', gquery: 'biomass_products_to_buildings_in_sankey_ufp', color: 'green'},
-      {left: 'mixed_flows',      right: 'buildings', gquery: 'mixed_flows_to_buildings_in_sankey_ufp', color: 'steelblue'},
+      #{left: 'mixed_flows',      right: 'buildings', gquery: 'mixed_flows_to_buildings_in_sankey_ufp', color: 'steelblue'},
       {left: 'electricity',      right: 'buildings', gquery: 'electricity_to_buildings_in_sankey_ufp', color: 'green'},
       {left: 'coal_and_derivatives',      right: 'agriculture', gquery: 'coal_and_derivatives_to_agriculture_in_sankey_ufp', color: 'black'},
       {left: 'oil_and_derivatives',      right: 'agriculture', gquery: 'oil_and_derivatives_to_agriculture_in_sankey_ufp', color: 'brown'},
@@ -56,7 +56,7 @@
       {left: 'natural_gas',      right: 'agriculture', gquery: 'natural_gas_to_agriculture_in_sankey_ufp', color: 'steelblue'},
       {left: 'nuclear',      right: 'agriculture', gquery: 'nuclear_to_agriculture_in_sankey_ufp', color: 'black'},
       {left: 'biomass_products',      right: 'agriculture', gquery: 'biomass_products_to_agriculture_in_sankey_ufp', color: 'green'},
-      {left: 'mixed_flows',      right: 'agriculture', gquery: 'mixed_flows_to_agriculture_in_sankey_ufp', color: 'steelblue'},
+      #{left: 'mixed_flows',      right: 'agriculture', gquery: 'mixed_flows_to_agriculture_in_sankey_ufp', color: 'steelblue'},
       {left: 'electricity',      right: 'agriculture', gquery: 'electricity_to_agriculture_in_sankey_ufp', color: 'green'},
       {left: 'coal_and_derivatives',      right: 'transport', gquery: 'coal_and_derivatives_to_transport_in_sankey_ufp', color: 'black'},
       {left: 'oil_and_derivatives',      right: 'transport', gquery: 'oil_and_derivatives_to_transport_in_sankey_ufp', color: 'brown'},
@@ -66,7 +66,7 @@
       {left: 'natural_gas',      right: 'transport', gquery: 'natural_gas_to_transport_in_sankey_ufp', color: 'steelblue'},
       {left: 'nuclear',      right: 'transport', gquery: 'nuclear_to_transport_in_sankey_ufp', color: 'black'},
       {left: 'biomass_products',      right: 'transport', gquery: 'biomass_products_to_transport_in_sankey_ufp', color: 'green'},
-      {left: 'mixed_flows',      right: 'transport', gquery: 'mixed_flows_to_transport_in_sankey_ufp', color: 'steelblue'},
+      #{left: 'mixed_flows',      right: 'transport', gquery: 'mixed_flows_to_transport_in_sankey_ufp', color: 'steelblue'},
       {left: 'electricity',      right: 'transport', gquery: 'electricity_to_transport_in_sankey_ufp', color: 'green'},
       {left: 'coal_and_derivatives',      right: 'industry', gquery: 'coal_and_derivatives_to_industry_in_sankey_ufp', color: 'black'},
       {left: 'oil_and_derivatives',      right: 'industry', gquery: 'oil_and_derivatives_to_industry_in_sankey_ufp', color: 'brown'},
@@ -76,7 +76,7 @@
       {left: 'natural_gas',      right: 'industry', gquery: 'natural_gas_to_industry_in_sankey_ufp', color: 'steelblue'},
       {left: 'nuclear',      right: 'industry', gquery: 'nuclear_to_industry_in_sankey_ufp', color: 'black'},
       {left: 'biomass_products',      right: 'industry', gquery: 'biomass_products_to_industry_in_sankey_ufp', color: 'green'},
-      {left: 'mixed_flows',      right: 'industry', gquery: 'mixed_flows_to_industry_in_sankey_ufp', color: 'steelblue'},
+      #{left: 'mixed_flows',      right: 'industry', gquery: 'mixed_flows_to_industry_in_sankey_ufp', color: 'steelblue'},
       {left: 'electricity',      right: 'industry', gquery: 'electricity_to_industry_in_sankey_ufp', color: 'green'},
       {left: 'coal_and_derivatives',      right: 'other', gquery: 'coal_and_derivatives_to_other_in_sankey_ufp', color: 'black'},
       {left: 'oil_and_derivatives',      right: 'other', gquery: 'oil_and_derivatives_to_other_in_sankey_ufp', color: 'brown'},
@@ -86,7 +86,7 @@
       {left: 'natural_gas',      right: 'other', gquery: 'natural_gas_to_other_in_sankey_ufp', color: 'steelblue'},
       {left: 'nuclear',      right: 'other', gquery: 'nuclear_to_other_in_sankey_ufp', color: 'black'},
       {left: 'biomass_products',      right: 'other', gquery: 'biomass_products_to_other_in_sankey_ufp', color: 'green'},
-      {left: 'mixed_flows',      right: 'other', gquery: 'mixed_flows_to_other_in_sankey_ufp', color: 'steelblue'},
+      #{left: 'mixed_flows',      right: 'other', gquery: 'mixed_flows_to_other_in_sankey_ufp', color: 'steelblue'},
       {left: 'electricity',      right: 'other', gquery: 'electricity_to_other_in_sankey_ufp', color: 'green'},
       {left: 'households',      right: 'useful_demand_electric', gquery: 'households_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
       {left: 'households',      right: 'useful_demand_heat_cold', gquery: 'households_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},


Only disable every field for current start time

--- app/assets/javascripts/tracks.js.coffee
+++ app/assets/javascripts/tracks.js.coffee
@@ -13,7 +13,8 @@
     parent.find('.every_select').toggle()
     parent.find('.date_select').toggle()
   el.find('.track_start_times_day_of_week select')
-    .on('change', window.selectDayOfWeek)
+    .on('change', (event) ->
+      window.selectDayOfWeek(el))

 window.check_to_hide_or_show_remove_link = ->
   if $('#start_times .nested-fields').length < 2
@@ -21,7 +22,7 @@
   else
     $('#start_times .remove_fields').show()

-window.selectDayOfWeek = ->
-  day = $('.track_start_times_day_of_week select').val()
+window.selectDayOfWeek = (el) ->
+  day = el.find('.track_start_times_day_of_week select').val()
   disabled = day >= 10
-  $('.track_start_times_every select').prop('disabled', disabled)
+  el.find('.track_start_times_every select').prop('disabled', disabled)


Ahora que cambia la forma de enviar la foto, cambia la forma de insertar el texto.

--- app/javascripts/galeria_markitup.coffee
+++ app/javascripts/galeria_markitup.coffee
@@ -1,5 +1,7 @@
 $(document).ready ->
-  $('#fotos a').attr('title', 'Inserta esta imagen en la página').live('click', ->
-    $('textarea').append('!' + this.href + '!').animate({scrollTop: 10000}, 300)
-    false
+  $('#galeria .foto form').ajaxForm(
+    success: (responseText, statusText, xhr, $form) ->
+      textarea = $('textarea')
+      textarea.animate({scrollTop: 10000}, 300).
+      val(textarea.val() + '!' + responseText.replace("\n", '') + '!')
   )


Fixed an error when selecting the scheme to use.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -181,7 +181,8 @@
 	###
 	setupServer: ->
 		# Create a new instance of your server based on the scheme
-		@connection = new require("./scheme/#{@config.scheme}")()
+		scheme = require("./scheme/#{@config.scheme}")
+		@connection = new scheme()
 		@connection.failed.add => return console.log "Connection failed.".bold.red unless @isConnected
 		@connection.connected.add =>
 			@isConnected = true
@@ -198,7 +199,8 @@
 	Create more connections of your server for multiple uploads
 	###
 	setupMultipleServers: ->
-		con = new require("./scheme/#{@config.scheme}")()
+		scheme = require("./scheme/#{@config.scheme}")
+		con = new scheme()
 		con.connected.add =>
 			# Once is connected, check the revision files
 			@connections.push con


Wrap all path.normalize with _constructRemotePath

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -487,7 +487,7 @@
 				return

 			# Create the folder recursively in the server
-			connection.mkdir path.normalize(@config.path.remote + folder), (error) =>
+			connection.mkdir @_constructRemotePath(path.normalize(@config.path.remote + folder)), (error) =>
 				unless @dirCreated[folder]
 					if error
 						# console.log "[ + ]".green, "Fail creating directory: #{folder}:".red
@@ -540,7 +540,7 @@
 		item.started = true

 		# Set the entire remote path
-		remote_path = path.normalize(@config.path.remote + item.remote)
+		remote_path = @_constructRemotePath(path.normalize(@config.path.remote + item.remote))

 		# Delete the file from the server
 		connection.delete remote_path, (error) =>


fixes #40

fixes #40

--- src/format.coffee
+++ src/format.coffee
@@ -58,6 +58,7 @@
     styleString = container.getAttribute('style') or ''
     return _.reduce(styleString.split(';').slice(0, -1), (styles, str) ->
       [name, value] = str.split(':')
+      name = name.slice(1) if name[0] == ' '
       value = value.slice(1) if value[0] == ' '
       styles[name] = value
       return styles
@@ -71,8 +72,10 @@
     return nameArr[0] + capitalNameArr.slice(1).join('')

   constructor: (@root, @keyName, @cssName, @styles, @matchFn) ->
-    @matchFn or= (formatName, cssValue) =>
-      return @styles[formatName] == cssValue
+    @matchFn or= (cssValue) =>
+      for key,value of @styles
+        return key if value == cssValue
+      return false
     super

   clean: (node) ->
@@ -88,10 +91,7 @@
   matchContainer: (container) ->
     return false unless super(container)
     styles = Scribe.Format.Style.getStyleObject(container)
-    if styles[@cssName]?
-      for formatName of @styles
-        return formatName if @matchFn(formatName, styles[@cssName])
-    return false
+    return if styles[@cssName]? then @matchFn(styles[@cssName]) else false

 class Scribe.Format.Bold extends Scribe.Format.Tag
@@ -142,41 +142,40 @@

 class Scribe.Format.Color extends Scribe.Format.Style
   @COLORS: {
-    'black'   : '#000'
-    'red'     : '#F00'
-    'blue'    : '#00F'
-    'lime'    : '#0F0'
-    'teal'    : '#0FF'
-    'magenta' : '#F0F'
-    'yellow'  : '#FF0'
-    'white'   : '#FFF'
+    'black'   : '#000000'
+    'red'     : '#FF0000'
+    'blue'    : '#0000FF'
+    'lime'    : '#00FF00'
+    'teal'    : '#00FFFF'
+    'magenta' : '#FF00FF'
+    'yellow'  : '#FFFF00'
+    'white'   : '#FFFFFF'
   }

   @normalizeColor: (color) ->
     color = Scribe.Format.Color.COLORS[color] if Scribe.Format.Color.COLORS[color]?
     if color[0] == '#'
-      color = color.slice(1)
-      if color.length == 3
-        color = _.map(color, (letter) ->
+      if color.length == 4
+        color = '#' + _.map(color.slice(1), (letter) ->
           letter + letter
         ).join('')
-      return [
-        parseInt(color.slice(0, 2), 16)
-        parseInt(color.slice(2, 4), 16)
-        parseInt(color.slice(4, 6), 16)
-      ]
+      return color
     else if color.indexOf('rgb(') == 0
       color = color.slice(4)
-      return _.map(color.split(',').slice(0, 3), (part) ->
-        parseInt(part, 10)
-      )
+      color = _.reduce(color.split(',').slice(0, 3), (color, part) ->
+        c = parseInt(part, 10).toString(16).toUpperCase()
+        c = '0' + c if c.length == 1
+        return color + c
+      , '#')
+      return color
     else
-      return [0, 0, 0]
-
-  @matchColor: (color1, color2) ->
-    color1 = Scribe.Format.Color.normalizeColor(color1)
-    color2 = Scribe.Format.Color.normalizeColor(color2)
-    return _.isEqual(color1, color2)
+      return '#000000'
+
+  @matchColor: (cssValue) ->
+    color = Scribe.Format.Color.normalizeColor(cssValue)
+    for key,value of @styles
+      return key if value == color
+    return false

   constructor: (@root) ->
     colors = _.clone(Scribe.Format.Color.COLORS)
@@ -189,8 +188,10 @@
     super(@root, 'family', 'font-family', {
       'serif'     : "'Times New Roman', serif"
       'monospace' : "'Courier New', monospace"
-    }, (formatValue, cssValue) =>
-      return cssValue.indexOf(formatValue) >= 0
+    }, (cssValue) =>
+      for key,value of @styles
+        return key if value.indexOf(cssValue) >= 0
+      return false
     )


Edges hidden with nodes

Edges hidden with nodes

--- app/scripts/alchemy/core/filters.coffee
+++ app/scripts/alchemy/core/filters.coffee
@@ -180,7 +180,7 @@
           .html("Toggle Nodes")
           .on("click", ->
             if d3.selectAll(".node.hidden")[0].length == 0
-                d3.selectAll(".node:not(.root)")
+                d3.selectAll(".node:not(.root), .edge")
                   .classed("hidden", true)
             else
                 d3.selectAll(".node.hidden:not(.root), .edge")


Hide headerbar forms when clicking outside them

--- app/scripts/directives/headerbar.coffee
+++ app/scripts/directives/headerbar.coffee
@@ -1,7 +1,7 @@
 'use strict'

 angular.module('holmesApp')
-  .directive('headerbar', () ->
+  .directive 'headerbar', ($window) ->
     templateUrl: 'views/headerbar.html'
     restrict: 'E'
     replace: true,
@@ -12,9 +12,31 @@
       scope.toggleAddPage = ->
         scope.model.addPageFormVisible = !scope.model.addPageFormVisible
         scope.model.searchFormVisible = false
+        if scope.model.addPageFormVisible
+          scope.toggleBar()

       scope.model.searchFormVisible = false
       scope.toggleSearch = ->
         scope.model.addPageFormVisible = false
         scope.model.searchFormVisible = !scope.model.searchFormVisible
-  )
+        if scope.model.searchFormVisible
+          scope.toggleBar()
+
+      scope.toggleBar = ->
+        if scope.model.addPageFormVisible or scope.model.searchFormVisible
+          $window.onclick = (event) ->
+            scope.hideBarIfClickOutside(event, scope.toggleBar)
+        else
+          $window.onclick = null
+          scope.$apply()
+
+      scope.hideBarIfClickOutside = (event, callback) ->
+        if scope.model.addPageFormVisible
+          Ythreshold = Math.abs($('.add-page-form').position().top) + $('.add-page-form').height() - $($window).scrollTop()
+          if event.y > Ythreshold
+            scope.model.addPageFormVisible = false
+        else
+          Ythreshold = Math.abs($('.search-form').position().top) + $('.search-form').height() - $($window).scrollTop()
+          if event.y > Ythreshold
+            scope.model.searchFormVisible = false
+        callback()


Visual/edit modes: change visual-mode start up...

When visual mode launches and there *is* a selection but it's outside of
the viewport, instead of scrolling it into view, ignore it, and start
with a visible caret.

--- content_scripts/mode_visual_edit.coffee
+++ content_scripts/mode_visual_edit.coffee
@@ -424,9 +424,20 @@
       if @options.parentMode and @selection.type == "Caret"
         # We're being called from edit mode, so establish an intial visible selection.
         @extendByOneCharacter(forward) or @extendByOneCharacter backward
-      else if @selection.type in [ "None", "Caret" ]
-        unless @options.oneMovementOnly or options.immediateMovement
-          HUD.showForDuration "No selection, entering caret mode first..", 2500
+      else
+        if @selection.type in [ "Caret", "Range" ]
+          elementWithFocus = DomUtils.getElementWithFocus @selection, @getDirection() == backward
+          if DomUtils.getVisibleClientRect elementWithFocus
+            if @selection.type == "Caret"
+              # Make the selection visible.
+              @extendByOneCharacter(forward) or @extendByOneCharacter backward
+          else
+            # If the selection is outside of the viewport, we clear it.  We guess that the user has moved on,
+            # and is more likely to be interested in visible content.
+            @selection.removeAllRanges()
+
+        if @selection.type != "Range"
+          HUD.showForDuration "Entering caret mode first..", 2500
           @changeMode CaretMode
           return

@@ -530,7 +541,7 @@
           @exit()
           return
       when "Range"
-        @collapseSelectionToFocus()
+        @collapseSelectionToAnchor()

     @selection.modify "extend", forward, character
     @scrollIntoView()


Remove logging.

Remove logging.

--- src/minify.coffee
+++ src/minify.coffee
@@ -46,7 +46,6 @@

 auto = (ast, opts) ->
   minifier = detectMinifier opts.minifier
-  console.log minifier
   minifiers[minifier] ast, opts

 wrapper           = auto


Bugfix: correct shortcoming count for search results.

--- src/models.coffee
+++ src/models.coffee
@@ -355,7 +355,7 @@
                 return Number.MAX_VALUE
             shortcomings = @getTranslatedShortcomings()
             @shortcomingCount = 0
-            for __, group of shortcomings
+            for __, group of shortcomings.results
                 @shortcomingCount += _.values(group).length
             @shortcomingCount


handled some more image context issues

handled some more image context issues

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -189,9 +189,11 @@
 class PuzzlePiece

   constructor: (puzzle_app) ->
+    console.log "PuzzlePiece constructor"
     @puzzle = puzzle_app
     @hex_box = @puzzle.hex_box
     @pattern  = new PiecePattern(this)
+    console.log "    @pattern.img.id = #{@pattern.img.id}"
     @redraw = new PieceRedrawBuffer(this)
     @hexes = []
     @bounding_box = [0,0,0,0,]
@@ -311,6 +313,7 @@
       yy = anchor_y + (bb - anchor_b) * 20
       if aa%2 != anchor_a%2
         if anchor_a%2 == 0 then yy = yy+10 else yy = yy-10
+      @hex_draw.set_context2(@img)
       @hex_draw.fill_hex_xy(xx,yy,"#000000")

@@ -377,11 +380,9 @@
   get_drawing_context: (mode) ->
     switch mode
       when "canvas" then context = @context_canvas
-      when "piece_mask"
-        if @puzzle.piece
-          context = @puzzle.piece.piece_mask.img.getContext('2d')
-        else
-          context = null
+      when "piece-mask"
+        pm_img = document.getElementById("piece-mask")
+        context = pm_img.getContext('2d')
     return context

@@ -431,8 +432,14 @@

   set_context: (mode) ->
+    console.log "CALL set_context()  mode = #{mode}"
     @mode = mode
     @context = @puzzle_view.get_drawing_context(mode)
+
+
+  set_context2: (img) ->
+    console.log "CALL set_context2()  img.id = #{img.id}"
+    @context = img.getContext('2d')

   get_hex_xy: (a,b) ->


chicken out

chicken out

--- src/player.coffee
+++ src/player.coffee
@@ -28,6 +28,8 @@
     if value > 0
       bet = (gameState.small_blind * 2) * 2
       bet = minBet if bet < minBet
+      if value < 0.2 and seriousness > 2
+        return next null, 0
       return next null, bet

     if seriousness > value * 5


add some strategy

add some strategy

--- src/player.coffee
+++ src/player.coffee
@@ -1,4 +1,6 @@
 pkg = require '../package.json'
+
+decodeRank = require './decodeRank'

 module.exports =
   version: ->
@@ -9,6 +11,17 @@
     minBet = gameState.small_blind * 2
     bet = gameState.current_buy_in
     bet = minBet unless bet > minBet
+
+    me = gameState?.players?[gameState?.in_action]
+    if me.hole_cards?.length == 1
+      card = me.hole_cards[0]
+      value = decodeRank card.rank
+      if value < 10
+        return next null, 0
+
+    unless me.hole_cards?.length > 1
+      return next null, bet
+
     next null, bet
   showdown: (gameState, next) ->
     next()


router: RSM usage fix

router: RSM usage fix

--- src/router.coffee
+++ src/router.coffee
@@ -235,7 +235,7 @@
         opts =
             operation: 'retrieve-user-subscriptions'
             jid: server
-        opts.rsm = new RSM.RSM()
+        opts.rsm = new RSM()
         rsmWalk (nextOffset, cb2) =>
             opts.rsm.after = nextOffset
             router.runRemotely opts, (err, results) =>


fixed router

fixed router

--- src/router.coffee
+++ src/router.coffee
@@ -15,13 +15,19 @@
     return

   constructor: (app) ->
-    @default_module  = nodame.config 'module.default'
-    @modules  = nodame.config 'module.items'
-    @hostname = nodame.config 'url.hostname'
+    @default_module  = nodame.config('module.default')
+    @modules  = nodame.config('module.items')
+    @hostname = nodame.config('url.hostname')
     @root = "#{nodame.config('module.root')}"
-
-    unless @root[0] is '/'
-      @root = "/#{@root}"
+    # normalize root path
+    if @root.length > 1
+      unless @root[0] is '/'
+        @root = "/#{@root}"
+      if @root[@root.length - 1] is '/'
+        @root = @root.slice(0, -1)
+    else
+      if @root[0] is '/'
+        @root = ''

     Session   = require('./session')
     app.use(Session.initSession)
@@ -37,7 +43,7 @@
         route   = "#{@root}"

         if mod isnt @default_module then route += "/#{mod}"
-        else defRoute = "#{route}/#{mod}"
+        else default_route = "#{route}/#{mod}"

         # Init middleware
         if config.middleware
@@ -49,7 +55,7 @@
           app.use "#{route}/", handler

           # Enable access to default route using its module name
-          app.use "#{defRoute}/", handler if mod is @default_module
+          app.use "#{default_route}/", handler if mod is @default_module

         # Init xhr
         app.use "#{@root}/ajax/#{mod}/", handler if config.ajax


work on 404 some more, #13

work on 404 some more, #13

--- src/server.coffee
+++ src/server.coffee
@@ -21,6 +21,15 @@

 log = require('./log')(module)

+
+notFoundHandler = (req, res, next) ->
+	res.status 404
+	if Accepts(req).type('text/html')
+		return res.render '404', {reqJSON: StringifySafe(req, null, 2)}
+	else
+		return res.end()
+	next()
+
 errorHandler = (err, req, res, next) ->
 	if typeof err is 'string'
 		err = {'message': err}
@@ -36,14 +45,6 @@
 		return res.render 'error', { err }
 	else
 		res.send StringifySafe err, null, 2
-
-notFoundHandler = (req, res) ->
-	if Accepts(req).type('text/html')
-		return res.render '404', {reqJSON: StringifySafe(req, null, 2)}
-	else
-		res.end()
-
-

 accessLogger = Morgan 'combined', stream: Fs.createWriteStream(__dirname + '/../data/logs/access.log', {flags: 'a'})
 accessLoggerDev = Morgan 'dev'
@@ -107,30 +108,32 @@
 		# Log access
 		@app.use accessLogger
 		@app.use accessLoggerDev
-		for controller in [
-				'header'
-				'restful'
-				'schemo'
-				'upload'
-				'execute'
-				'monitor'
-				'ldf'
-				'swagger'
-				'json-import'
-				'syntax-highlight'
-				'play'
-			]
-			do (controller) =>
-				log.info "Setting up route #{controller}"
-				require("./routes/#{controller}")(@app)
-		# root route
-		@app.get '/', (req, res, next) ->
-			res.status 302
-			res.header 'Location', 'http://infolis.github.io'
-			res.end()
-		# Error handler
-		@app.use errorHandler
-		@app.use notFoundHandler
+		controlers = [
+			'header'
+			'restful'
+			'schemo'
+			'upload'
+			'execute'
+			'monitor'
+			'ldf'
+			'swagger'
+			'json-import'
+			'syntax-highlight'
+			'play'
+		]
+		Async.eachSeries controlers, (controller, done) =>
+			log.info "Setting up route #{controller}"
+			done null, require("./routes/#{controller}")(@app)
+		, (err, next) =>
+			# root route
+			@app.get '/', (req, res, next) ->
+				res.status 302
+				res.header 'Location', 'http://infolis.github.io'
+				res.end()
+			# Error handler
+			@app.use errorHandler
+			# 404 handler
+			@app.use notFoundHandler

 	startServer : () ->
 		log.info "Setting up routes"


Add functionality to convert from a1c to average BG

--- src/diabetes.coffee
+++ src/diabetes.coffee
@@ -29,6 +29,19 @@
 mgdlToIfcc = (n) ->
   return (mgdlToDcct(n)-2.15)*10.929

+dcctToIfcc = (n) ->
+  return (n - 2.15)*10.929
+
+ifccToDcct = (n) ->
+  return (n / 10.929) + 2.15
+
+dcctToMgdl = (n) ->
+  return (n * 28.7) - 46.7
+
+ifccToMgdl = (n) ->
+  return dcctToMgdl((n / 10.929) + 2.5)
+
+
 module.exports = (robot) ->
   range = ///
     (?:^|_)        # anchor to the beginning of the string
@@ -41,24 +54,54 @@

   options =
     threshold: process.env.HUBOT_GLUCOSE_UNIT_THRESHOLD
+    a1cThreshold: process.env.HUBOT_A1C_UNIT_THRESHOLD

   unless options.threshold
     options.threshold = 30

+  unless options.a1cThreshold
+    options.a1cThreshold = 20
+
   robot.respond /estimate a1c (from average )?(.*)/i, (msg) ->
     bg = msg.match[2]
+    mgdl = 0
+    mmol = 0
+
     if bg >= options.threshold
-      reply = 'an average of ' + bg + ' mg/dL or '
-      reply = reply + mgdlToMmol(bg).toFixed(1) + ' mmol/L'
-      reply = reply + ' is approximately equivalent to '
-      reply = reply + mgdlToDcct(bg).toFixed(1) + '% (DCCT) or '
-      reply = reply + mgdlToIfcc(bg).toFixed(1) + ' mmol/mol (IFCC)'
+      mgdl = bg
+      mmol = mgdlToMmol(bg).toFixed(1)
     else
-      reply = 'an average of ' + bg + ' mmol/L or '
-      reply = reply + Math.round(mmolToMgdl(bg)) + ' mg/dL'
-      reply = reply + ' is approximately equivalent to '
-      reply = reply + mgdlToDcct(mmolToMgdl(bg)).toFixed(1) + '% (DCCT) or '
-      reply = reply + mgdlToIfcc(mmolToMgdl(bg)).toFixed(1) + ' mmol/mol (IFCC)'
+      mmol = bg
+      mgdl = mmolToMgdl(bg).toFixed(0)
+
+    dcct = mgdlToDcct(mgdl)
+
+    reply = 'an average of ' + mgdl + ' mg/dL or '
+    reply = reply + mmol + ' mmol/L'
+    reply = reply + ' is approximately equivalent to '
+    reply = reply + dcct.toFixed(1) + '% (DCCT) or '
+    reply = reply + dcctToIfcc(dcct).toFixed(0) + ' mmol/mol (IFCC)'
+    msg.send reply
+
+  robot.respond /estimate average (from a1c)?(.*)/i, (msg) ->
+    a1c = msg.match[2]
+    dcct = 0
+    ifcc = 0
+
+    if a1c >= options.a1cThreshold
+      ifcc = a1c
+      dcct = ifccToDcct(a1c).toFixed(1)
+    else
+      dcct = a1c
+      ifcc = dcctToIfcc(a1c).toFixed(0)
+
+    mgdl = dcctToMgdl(dcct)
+
+    reply = 'an a1c of ' + dcct + '% (DCCT) or '
+    reply = reply + ifcc + ' mmol/mol (IFCC)'
+    reply = reply + ' is approximately equivalent to '
+    reply = reply + mgdl.toFixed(0) + 'mg/dL or '
+    reply = reply + mgdlToMmol(mgdl).toFixed(1) + ' mmol/L'
     msg.send reply

   robot.hear range, (msg) ->


API proxying + relativeUrlRoot

API proxying + relativeUrlRoot

As you can see at a glance, this code is getting SUPER DUPER UGLY.

We need to refactor the heck out of this server task if it's ever going
to change significantly.

Fixes #189 cc/@bdoyle @diminish7

--- tasks/server.coffee
+++ tasks/server.coffee
@@ -51,10 +51,10 @@
       if apiProxyEnabled
         if pushStateEnabled
           grunt.log.writeln("Proxying API requests prefixed with '#{apiProxyPrefix}' to #{apiProxyHost}:#{apiPort}")
-          app.use(prefixMatchingApiProxy(apiProxyPrefix, apiProxyHost, apiPort, apiProxyChangeOrigin, new httpProxy.RoutingProxy()))
+          app.use(prefixMatchingApiProxy(apiProxyPrefix, apiProxyHost, apiPort, apiProxyChangeOrigin, relativeUrlRoot, new httpProxy.RoutingProxy()))
         else
           grunt.log.writeln("Proxying API requests to #{apiProxyHost}:#{apiPort}")
-          app.use(apiProxy(apiProxyHost, apiPort, apiProxyChangeOrigin, new httpProxy.RoutingProxy()))
+          app.use(apiProxy(apiProxyHost, apiPort, apiProxyChangeOrigin, relativeUrlRoot, new httpProxy.RoutingProxy()))

       app.use(express.bodyParser())
       app.use(express.errorHandler())
@@ -89,21 +89,23 @@
     res.write("API Proxying to `#{req.url}` failed with: `#{err.toString()}`")
     res.end()

-  prefixMatchingApiProxy = (prefix, host, port, changeOrigin, proxy) ->
+  prefixMatchingApiProxy = (prefix, host, port, changeOrigin, relativeUrlRoot, proxy) ->
     prefixMatcher = new RegExp(prefix)

     proxy.on "proxyError", handleProxyError

     return (req, res, next) ->
       if prefix and prefixMatcher.exec(req.path)
+        req.url = relativeUrlRoot + req.url
         proxy.proxyRequest(req, res, {host, port, changeOrigin})
       else
         next()

-  apiProxy = (host, port, changeOrigin, proxy) ->
+  apiProxy = (host, port, changeOrigin, relativeUrlRoot, proxy) ->
     proxy.on "proxyError", handleProxyError

     return (req, res, next) ->
+      req.url = relativeUrlRoot + req.url
       proxy.proxyRequest(req, res, {host, port, changeOrigin})

   addBodyParserCallbackToRoutes = (app) ->


Filter out . from \b matches

Filter out . from \b matches

Sometimes @alias.com could get matched when it shouldnt be.

--- src/alias.coffee
+++ src/alias.coffee
@@ -39,7 +39,7 @@
   groups = _.object(groups)

   # Replace aliases with @mentions in the message
-  expand = (message) ->
+  expand = (message, user) ->
     for own k, v of groups
       reg = new RegExp('[:(]+' + k + '[:)]+|@' + k, 'i')
       message = message.replace(reg, v)
@@ -48,6 +48,8 @@
   # Compile RegEx to match only the aliases
   # Note this matches (alias) :alias: and @alias
   aliases = _.keys(groups).join('|')
-  regex = new RegExp('(?:@(' + aliases + ')\\b)|(?:\\(|\\:)(' + aliases + ')(?:\\)|\\:)', 'i')
+  atRE = '(?:@(' + aliases + ')(?:\\b[^.]))'
+  emojiRE = '(?:\\(|\\:)(' + aliases + ')(?:\\)|\\:)'
+  regex = new RegExp(atRE + '|' + emojiRE, 'i')
   robot.hear regex, (msg) ->
     msg.send expand(msg.message.text)


Smarter module.exports

Smarter module.exports

--- src/array.coffee
+++ src/array.coffee
@@ -2,104 +2,102 @@
 curry      = require './curry'
 typeChecks = require './typechecks'

-length = (xs) ->
+array = module.exports
+
+array.length = (xs) ->
   xs.length

-concat = curry (ys, xs) ->
+array.concat = curry (ys, xs) ->
   xs.concat ys

-every = curry (fn, xs) ->
+array.every = curry (fn, xs) ->
   xs.every fn

-filter = curry (fn, xs) ->
+array.filter = curry (fn, xs) ->
   xs.filter fn

-forEach = curry (fn, xs) ->
+array.forEach = curry (fn, xs) ->
   xs.forEach fn

-indexOf = uncurry (x) -> (fromIndex) ->
+array.indexOf = uncurry (x) -> (fromIndex) ->
   if typeChecks.isArray fromIndex
     xs = fromIndex
     xs.indexOf x
   else
     (xs) -> xs.indexOf x, fromIndex

-join = uncurry (separator) -> (xs) ->
+array.join = uncurry (separator) -> (xs) ->
   xs.join separator

-lastIndexOf = uncurry (x) -> (fromIndex) ->
+array.lastIndexOf = uncurry (x) -> (fromIndex) ->
   if typeChecks.isArray fromIndex
     xs = fromIndex
     xs.lastIndexOf x
   else
     (xs) -> xs.lastIndexOf x, fromIndex

-map = curry (fn, xs) ->
+array.map = curry (fn, xs) ->
   xs.map fn

-pop = (xs) ->
-  slice 0, length(xs) - 1, xs
+array.pop = (xs) ->
+  array.slice 0, array.length(xs) - 1, xs

-push = uncurry (x) ->
-  concat [x]
+array.push = uncurry (x) ->
+  array.concat [x]

-reduce = curry (fn, initial, xs) ->
+array.reduce = curry (fn, initial, xs) ->
   if initial is null
     xs.reduce fn
   else
     xs.reduce fn, initial

-reduceRight = curry (fn, initial, xs) ->
+array.reduceRight = curry (fn, initial, xs) ->
   if initial is null
     xs.reduceRight fn
   else
     xs.reduceRight fn, initial

-reverse = (xs) ->
-  do slice(0, xs).reverse
+array.reverse = (xs) ->
+  do array.slice(0, xs).reverse

-slice = uncurry (start) -> (end) ->
+array.slice = uncurry (start) -> (end) ->
   if end instanceof Array
     end.slice start
   else
     (xs) -> xs.slice start, end

-shift = slice 1
+array.shift = array.slice 1

-some = curry (fn, xs) ->
+array.some = curry (fn, xs) ->
   xs.some fn

-sort = uncurry (fn) ->
+array.sort = uncurry (fn) ->
   if typeChecks.isFunction fn
-    (xs) -> slice(0, xs).sort fn
+    (xs) -> array.slice(0, xs).sort fn
   else
     xs = fn
-    do slice(0, xs).sort
+    do array.slice(0, xs).sort

-splice = curry (index, howMany, ys, xs) ->
+array.splice = curry (index, howMany, ys, xs) ->
   ys = switch
     when typeChecks.isArray ys then ys
     when ys is null then []
     else [ys]
-  xs = slice 0, xs
+  xs = array.slice 0, xs
   Array.prototype.splice.apply xs, [index, howMany].concat(ys)
   xs

-toLocaleString = (xs) ->
+array.toLocaleString = (xs) ->
   if typeChecks.isArray xs
     Array.prototype.toLocaleString.call xs
   else
     Object.prototype.toLocaleString.call xs

-toString = (xs) ->
+array.toString = (xs) ->
   if typeChecks.isArray xs
     Array.prototype.toString.call xs
   else
     Object.prototype.toString.call xs

-unshift = curry (x, xs) ->
-  concat xs, [x]
-
-module.exports = { length, concat, every, filter, forEach, indexOf, join,
-  lastIndexOf, map, pop, push, reduce, reduceRight, reverse, shift, slice,
-  some, sort, splice, toLocaleString, toString, unshift }
+array.unshift = curry (x, xs) ->
+  array.concat xs, [x]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -24,7 +24,7 @@
           parseInt(hexString.substr(0, 2), 16)
           parseInt(hexString.substr(2, 2), 16)
           parseInt(hexString.substr(4, 2), 16)
-          if hexString.substr(6, 2).length? then parseInt(hexString.substr(6, 2) / 255.0) else 1.0
+          if hexString.substr(6, 2).length then parseInt(hexString.substr(6, 2) / 255.0) else 1.0
         ]

   parseRGB = (colorString) ->


Fix typo

Fix typo

--- src/color.coffee
+++ src/color.coffee
@@ -50,7 +50,7 @@
         return "rgb(#{r}, #{g}, #{b})"

     rgba = (r, g, b, a) ->
-        return "rgb(#{r}, #{g}, #{b}, #{a})"
+        return "rgba(#{r}, #{g}, #{b}, #{a})"

     service_color = (service) ->
         [r, g, b] = service_colors[service.get('root')]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -82,6 +82,7 @@
     key.toString().toLowerCase().split(' ').join('')

   channelize = (color, alpha) ->
+    return color.channels() if color.channels?
     if Object.isArray color
       if alpha?
         alpha = parseFloat(alpha)