Revert 80LOC change.

Revert 80LOC change.

--- src/docco.coffee
+++ src/docco.coffee
@@ -9,23 +9,20 @@
 #
 #     docco src/*.coffee
 #
-# ...will generate an HTML documentation page for each of the
-# named source files, with a menu linking to the other pages,
-# saving it into a `docs` folder.
-#
-# The [source for Docco](http://github.com/jashkenas/docco)
-# is available on GitHub,
+# ...will generate an HTML documentation page for each of the named source files,
+# with a menu linking to the other pages, saving it into a `docs` folder.
+#
+# The [source for Docco](http://github.com/jashkenas/docco) is available on GitHub,
 # and released under the MIT license.
 #
 # To install Docco, first make sure you have [Node.js](http://nodejs.org/),
 # [Pygments](http://pygments.org/) (install the latest dev version of Pygments
-# from [its Mercurial repo](https://bitbucket.org/birkenfeld/pygments-main)),
-# and [CoffeeScript](http://coffeescript.org/). Then, with NPM:
+# from [its Mercurial repo](https://bitbucket.org/birkenfeld/pygments-main)), and
+# [CoffeeScript](http://coffeescript.org/). Then, with NPM:
 #
 #     sudo npm install -g docco
 #
-# Docco can be used to process
-# CoffeeScript, JavaScript, Ruby, Python, or TeX files.
+# Docco can be used to process CoffeeScript, JavaScript, Ruby, Python, or TeX files.
 # Only single-line comments are processed -- block comments are ignored.
 #
 #### Partners in Crime:
@@ -40,16 +37,14 @@
 # also by Mr. Tomayko.
 #
 # * If Python's more your speed, take a look at
-# [Nick Fitzgerald](http://github.com/fitzgen)'s
-# [Pycco](http://fitzgen.github.com/pycco/).
+# [Nick Fitzgerald](http://github.com/fitzgen)'s [Pycco](http://fitzgen.github.com/pycco/).
 #
 # * For **Clojure** fans, [Fogus](http://blog.fogus.me/)'s
 # [Marginalia](http://fogus.me/fun/marginalia/) is a bit of a departure from
 # "quick-and-dirty", but it'll get the job done.
 #
 # * **Lua** enthusiasts can get their fix with
-# [Robert Gieseke](https://github.com/rgieseke)'s
-# [Locco](http://rgieseke.github.com/locco/).
+# [Robert Gieseke](https://github.com/rgieseke)'s [Locco](http://rgieseke.github.com/locco/).
 #
 # * And if you happen to be a **.NET**
 # aficionado, check out [Don Wilson](https://github.com/dontangg)'s
@@ -58,8 +53,8 @@
 #### Main Documentation Generation Functions

 # Generate the documentation for a source file by reading it in, splitting it
-# up into comment/code sections, highlighting them for
-# the appropriate language, and merging them into an HTML template.
+# up into comment/code sections, highlighting them for the appropriate language,
+# and merging them into an HTML template.
 generateDocumentation = (sources, source, callback) ->
   fs.readFile source, (error, buffer) ->
     throw error if error
@@ -105,10 +100,9 @@
   save docsText, codeText
   sections

-# Highlights a single chunk of CoffeeScript code,
-# using **Pygments** over stdio, and runs the text of
-# its corresponding comment through **Markdown**,
-# using [Showdown.js](http://attacklab.net/showdown/).
+# Highlights a single chunk of CoffeeScript code, using **Pygments** over stdio,
+# and runs the text of its corresponding comment through **Markdown**, using
+# [Showdown.js](http://attacklab.net/showdown/).
 #
 # We process the entire file in a single call to Pygments by inserting little
 # marker comments between each section and then splitting the result string
@@ -146,8 +140,8 @@
     pygments.stdin.end()

 # Once all of the code is finished highlighting, we can generate the HTML file
-# and write out the documentation. Pass the completed sections
-# into the template found in `resources/docco.jst`
+# and write out the documentation. Pass the completed sections into the template
+# found in `resources/docco.jst`
 generateHtml = (sources, source, sections) ->
   title = path.basename source
   dest  = destination source
@@ -196,9 +190,8 @@
 # Get the current language we're documenting, based on the extension.
 getLanguage = (source) -> languages[path.extname(source)]

-# Compute the destination HTML path for an input source file path.
-# If the source is `lib/example.coffee`,
-# the HTML will be at `docs/example.html`
+# Compute the destination HTML path for an input source file path. If the source
+# is `lib/example.coffee`, the HTML will be at `docs/example.html`
 destination = (filepath) ->
   basename = path.basename filepath, path.extname filepath
   "docs/#{basename}.html"


Add constants of tracker statuses to class Frame

--- lib/gazedata.coffee
+++ lib/gazedata.coffee
@@ -2,6 +2,12 @@
 Point2D = require './point2d'

 class Frame
+  @STATE_TRACKING_GAZE = 1
+  @STATE_TRACKING_EYES = 1 << 1
+  @STATE_TRACKING_PRESENCE = 1 << 2
+  @STATE_TRACKING_FAIL = 1 << 3
+  @STATE_TRACKING_LOST = 1 << 4
+
   constructor: (data)->
     @data = data


Set up selection vs. file args.

Set up selection vs. file args.

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -1,24 +1,34 @@
-# Maps Atom Grammar names to the interpreter used by that language
+# Maps Atom Grammar names to the command used by that language
 # As well as any special setup for arguments.
+
+defaultFileArgs = (filename) -> [filename]
+
 module.exports =
   CoffeeScript:
-    interpreter: "coffee"
-    makeargs: (code) -> ['-e', code]
+    command: "coffee"
+    bySelectionArgs: (code) -> ['-e', code]
+    byFileArgs: defaultFileArgs
   JavaScript:
-    interpreter: "node"
-    makeargs: (code) -> ['-e', code]
+    command: "node"
+    selectionArgs: (code) -> ['-e', code]
+    byFileArgs: defaultFileArgs
   Ruby:
-    interpreter: "ruby"
-    makeargs: (code) -> ['-e', code]
+    command: "ruby"
+    selectionArgs: (code) -> ['-e', code]
+    byFileArgs: defaultFileArgs
   Perl:
-    interpreter: "perl"
-    makeargs: (code) -> ['-e', code]
+    command: "perl"
+    selectionArgs: (code) -> ['-e', code]
+    byFileArgs: defaultFileArgs
   PHP:
-    interpreter: "php"
-    makeargs: (code) -> ['-r', code]
+    command: "php"
+    selectionArgs: (code) -> ['-r', code]
+    byFileArgs: defaultFileArgs
   Python:
-    interpreter: "python"
-    makeargs: (code) -> ['-c', code]
+    command: "python"
+    selectionArgs: (code) -> ['-c', code]
+    byFileArgs: defaultFileArgs
   'Shell Script (Bash)':
-    interpreter: "bash"
-    makeargs: (code) -> ['-c', code]
+    command: "bash"
+    selectionArgs: (code) -> ['-c', code]
+    byFileArgs: defaultFileArgs


Update grammars.coffee

Update grammars.coffee

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -141,20 +141,16 @@
       args: (context) -> [context.filepath]

   Java:
-    if GrammarUtils.OperatingSystem.isWindows()
-      "File Based":
-        command: "cmd"
-        args: (context) ->
-          className = context.filename.replace /\.java$/, ""
+    "File Based":
+      command: if GrammarUtils.OperatingSystem.isWindows() then "cmd" else "bash"
+      args: (context) ->
+        className = context.filename.replace /\.java$/, ""
+        args = []
+        if GrammarUtils.OperatingSystem.isWindows()
           args = ["/c javac -Xlint #{context.filename} && start cmd /k java #{className}"]
-          return args
-    else
-      "File Based":
-        command: "bash"
-        args: (context) ->
-          className = context.filename.replace /\.java$/, ""
+        else
           args = ['-c', "javac -d /tmp '#{context.filepath}' && java -cp /tmp #{className}"]
-          return args
+        return args

   JavaScript:
     "Selection Based":


Added windows functionality

Added windows functionality

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -497,11 +497,16 @@
     "File Based":
       command: "powershell"
       args: (context) -> [context.filepath.replace /\ /g, "` "]
-
+
   Processing:
     "File Based":
-      command: "bash"
-      args: (context) -> ['-c', 'processing-java --sketch='+context.filepath.replace("/"+context.filename,"")+' --run']
+      command: if GrammarUtils.OperatingSystem.isWindows() then "cmd" else "bash"
+      args: (context) ->
+        if GrammarUtils.OperatingSystem.isWindows()
+          return ['/c processing-java --sketch='+context.filepath.replace("\\"+context.filename,"")+' --run']
+        else
+          return ['-c', 'processing-java --sketch='+context.filepath.replace("/"+context.filename,"")+' --run']
+

   Prolog:
     "File Based":


:memo:

:memo:

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -15,7 +15,7 @@
   setCursorToStartOfChangeOnUndoRedo:
     order: 1
     type: 'boolean'
-    default: false
+    default: false  # TODO: change 'true' after evaluation.
   useClipboardAsDefaultRegister:
     order: 2
     type: 'boolean'


cleanup

cleanup


only check for wakatime-cli updates once every 24 hours to fix #37

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -37,34 +37,43 @@
     setupConfigs()
     @settingChangedObserver = atom.config.observe 'wakatime', settingChangedHandler

-    isPythonInstalled((installed) ->
-      if not installed
-        if os.type() is 'Windows_NT'
-          atom.confirm
-            message: 'WakaTime requires Python'
-            detailedMessage: 'Let\'s download and install Python now?'
-            buttons:
-              OK: -> installPython(finishActivation)
-              Cancel: -> window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+    lastInit = atom.config.get 'wakatime-hidden.lastInit'
+    currentTime = Math.round (new Date).getTime() / 1000
+
+    hours = 24
+    if not lastInit? or parseInt(lastInit, 10) + 3600 * hours < currentTime
+      atom.config.set 'wakatime-hidden.lastInit', currentTime
+
+      isPythonInstalled((installed) ->
+        if not installed
+          if os.type() is 'Windows_NT'
+            atom.confirm
+              message: 'WakaTime requires Python'
+              detailedMessage: 'Let\'s download and install Python now?'
+              buttons:
+                OK: -> installPython(finishActivation)
+                Cancel: -> window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+          else
+            window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
         else
-          window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
-      else
-        if not isCLIInstalled()
-          installCLI(->
-            log.debug 'Finished installing wakatime cli.'
-            finishActivation()
-          )
-        else
-          isCLILatest((latest) ->
-            if not latest
-              installCLI(->
-                log.debug 'Finished installing wakatime cli.'
+          if not isCLIInstalled()
+            installCLI(->
+              log.debug 'Finished installing wakatime cli.'
+              finishActivation()
+            )
+          else
+            isCLILatest((latest) ->
+              if not latest
+                installCLI(->
+                  log.debug 'Finished installing wakatime cli.'
+                  finishActivation()
+                )
+              else
                 finishActivation()
-              )
-            else
-              finishActivation()
-          )
-    )
+            )
+      )
+    else
+      finishActivation()

   consumeStatusBar: (statusBar) ->
     statusBarTileView = new StatusBarTileView()
@@ -86,6 +95,7 @@
   setupEventHandlers()
   statusBarTileView?.setTitle('WakaTime ready')
   statusBarTileView?.setStatus()
+  log.debug 'Finished initializing WakaTime'

 settingChangedHandler = (settings) ->
   if settings.showStatusBarIcon


prevent trying to update wakatime-cli when offline

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -275,14 +275,22 @@
               if callback?
                 callback(true)
             else
-              log.debug 'Found an updated wakatime-cli v' + latestVersion
-              if callback?
-                callback(false)
+              if latestVersion?
+                log.debug 'Found an updated wakatime-cli v' + latestVersion
+                if callback?
+                  callback(false)
+              else
+                log.debug 'Unable to find latest wakatime-cli version from GitHub.'
+                if callback?
+                  callback(true)
           )
         else
           if callback?
             callback(false)
       )
+    else
+      if callback?
+        callback(false)
   )

 getLatestCliVersion = (callback) ->


Store and recall entrants.

Store and recall entrants.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -11,18 +11,18 @@

 points = {}
 username = {}
-people = {}
 tickets = {}
 raffle = {}
 cost = {}
 entered = {}
+entrants = {}

 flatten = (array) ->
     Array::concat.apply([], array)

 save = (robot) ->
     robot.brain.data.points = points
-    robot.brain.data.tickets = tickets
+    robot.brain.data.entrants = entrants

 module.exports = (robot) ->
     robot.brain.on 'loaded', ->
@@ -40,21 +40,11 @@

         msg.send "Only Masonest can start raffles!"

-    # Close raffle, drawn winner and reset.
-    robot.respond /raffle draw$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            raffle = off
-            entered[username] = false
-            save(robot)
-            msg.send "The raffle is now closed. A winner will be announced in this message once finished."
-            return
-
-        msg.send "Only Masonest can close a raffle."
-
     # Enter an open raffle if you have enough points
     robot.respond /ticket$/i, (msg) ->
         username = "#{msg.envelope.user.name}"
         points[username] ?= 0
+        entrants = flatten([entered[username]])

         if points[username] >= cost and raffle is on and entered[username] is false
             entered[username] = true
@@ -68,3 +58,21 @@
                 msg.send "Test: raffle isn't running."
             if entered[username] is true
                 msg.send "Test: You already entered."
+
+    # Close raffle, drawn winner and reset.
+    robot.respond /raffle draw$/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+            raffle = off
+            entered[username] = false
+            save(robot)
+            msg.send "The raffle is now closed. A winner will be announced in this message once finished."
+            return
+
+        msg.send "Only Masonest can close a raffle."
+
+    # Check entrants. Temp command for testing
+    robot.respond /entrants$/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+            tickets = [entered[username] for username in entrants]
+            msg.send "Entrants for this raffle: " + tickets
+            return


Try new push method.

Try new push method.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -15,7 +15,7 @@
 raffle = {}
 cost = {}
 entered = {}
-entrants = {}
+bought = {}

 Util = require "util"

@@ -24,7 +24,6 @@

 save = (robot) ->
     robot.brain.data.points = points
-    robot.brain.data.entrants = entrants

 module.exports = (robot) ->
     robot.brain.on 'loaded', ->
@@ -52,7 +51,7 @@
         if points[username] >= cost and raffle is on and entered[username] is false
             entered[username] = true
             points[username] -= cost
-            entrants.push(entered[username])
+            bought.push(username)
             save(robot)
             msg.send "Test: raffle entered."
         else
@@ -77,7 +76,7 @@
     # Check entrants. Temp command for testing
     robot.respond /entrants$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin'])
-            tickets = [entered[username] for username in entrants]
+            tickets = [username for username in bought]
             msg.send "Entrants for this raffle: " + tickets
-            msg.send "Inspection: #{Util.inspect(entrants)}"
+            msg.send "Inspection: #{Util.inspect(bought)}"
             return


weather script

weather script

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -17,7 +17,7 @@

     rp(locationOptions)
       .then( (data) ->
-        if data.length > 0
+        if results[0].geometry
           latitude = data.results[0].geometry.location.lat
           longitude = data.results[0].geometry.location.lng
           latlng = latitude + ',' + longitude


weather script

weather script

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -47,6 +47,7 @@
             )
         else
           if location == 'my ass'
+            console.log(location)
             msg.send('It\'s hot and moist today.')
           else
             msg.send('Nope. Sorry. Not happening.')


updated fixtures

updated fixtures

--- server/fixtures.coffee
+++ server/fixtures.coffee
@@ -2,6 +2,9 @@

     now = new Date().getTime()
     hour = 1000 * 60 * 60 # ms * sec * min
+    day = hour * 24
+    week = day * 7
+    year = day * 365
     now = now - 10 * hour

     if Meteor.users.find().count() is 0
@@ -53,14 +56,15 @@
             firstName: "Makena"
             lastName: "Asaph"
             mrn: "1234567890"
+            dateOfBirth: 702280800000
             instancesOfProcurement: [
                 {
                     surgicalPathologyNumber: "0192837465"
-                    date: now - 2 * hour
+                    date: now - (2 * year + 8 * week + 1 * day + 4 * hour)
                 }
                 {
                     otherNumber: "0912873456"
-                    date: now - 26 * hour
+                    date: now - (2 * year + 6 * week + 1 * day + 4 * hour)
                 }
             ]
             shortId: "abcdEF"
@@ -72,14 +76,15 @@
             lastName: "Margarita"
             middleName: "P"
             mrn: "1357902468"
+            dateOfBirth: -487623600000
             instancesOfProcurement: [
                 {
                     surgicalPathologyNumber: "0192867465"
-                    date: now - 2 * hour
+                    date: now - (0 * year + 8 * week + 1 * day + 4 * hour)
                 }
                 {
                     otherNumber: "6912873456"
-                    date: now - 26 * hour
+                    date: now - (8 * year + 8 * week + 1 * day + 4 * hour)
                 }
             ]
             shortId: "EFdsdw"
@@ -91,14 +96,15 @@
             firstName: "Yasmeen"
             lastName: "Capucine"
             mrn: "1234567891"
+            dateOfBirth: 171176400000
             instancesOfProcurement: [
                 {
                     surgicalPathologyNumber: "1094859925"
-                    date: now - 2 * hour
+                    date: now - (2 * year + 5 * week + 1 * day + 4 * hour)
                 }
                 {
                     otherNumber: "1993848458"
-                    date: now - 26 * hour
+                    date: now - (3 * year + 1 * week + 10 * day - 3 * hour)
                 }
             ]
             shortId: "cbd1rC"
@@ -108,4 +114,4 @@

     ]

-    if Patients.find().count() is 0 then Meteor.call "/fixtures/loadpatients"+    if Patients.find().count() is 0 then Meteor.call "/fixtures/loadpatients"


Removed moment and marked from window scope

--- src/index.coffee
+++ src/index.coffee
@@ -1,9 +1,4 @@
 require("font-awesome-webpack");
-
-# FIXME.
-window.marked = require('marked')
-window.moment = require('moment')
-#

 CUI = require('./base/CUI.coffee')


get score

get score

--- src/index.coffee
+++ src/index.coffee
@@ -5,11 +5,22 @@
 ENDPOINTS =
     NEW: "new-game"
     EVENT: "add-events"
+    GET_SCORE: "get-scores"

 serverUrl = null
 requestTimer = null
 game = null
 events = []
+
+getScores = (gameType, cb) ->
+    Request
+        .get "#{serverUrl}/#{ENDPOINTS.GET_SCORE}/#{gameType}"
+        .end (err, response) ->
+            if error = (isRequestError err, response)
+                console.log "Couldn't get score: #{err.toString()}"
+                return cb?(err)
+            answer = _.sortBy(response.body[1], "score")
+            cb answer

 init = (url) ->
     serverUrl = url
@@ -99,3 +110,4 @@
     init: init
     startGame: startGame
     eventHappened: eventHappened
+    getScores: getScores


Reorganize items.coffee

Reorganize items.coffee

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- src/items.coffee
+++ src/items.coffee
@@ -2,246 +2,245 @@
 #<< stats
 #<< item_effects

-lol.items = {}
-lol.items._list = {}
-
-##
-# Represents an item in LoL.
-#
-class lol.items.LoLItem
-  ##
-  # C'tor.
+lol.items =
+  _list: {}
+
+  _queryHandlers:
+    eq:
+      handler: (property, val) -> property is val
+
+    neq:
+      handler: (property, val) -> property isnt val
+
+    gte:
+      propTypes: ["number"]
+      valTypes: ["number"]
+      handler: (property, val) -> property >= val
+
+    gt:
+      propTypes: ["number"]
+      valTypes: ["number"]
+      handler: (property, val) -> property > val
+
+    lte:
+      propTypes: ["number"]
+      valTypes: ["number"]
+      handler: (property, val) -> property <= val
+
+    lt:
+      propTypes: ["number"]
+      valTypes: ["number"]
+      handler: (property, val) -> property < val
+
+    contains:
+      propTypes: ["string"]
+      valTypes: ["string", "number"]
+      handler: (property, val) -> property.search(val.toString()) isnt -1
+
+    matches:
+      propTypes: ["string"]
+      valTypes: ["object"]
+      handler: (property, val) ->
+        throw new Error "Argument must be a regular expression!" unless val instanceof RegExp
+        val.test property
+
+  ##
+  # Defines a new item.
+  #
+  _def: (name, item) ->
+    lol.items._list[name] = new lol.items.LoLItem name, item
+
+  ##
+  # Represents an item in LoL.
+  #
+  LoLItem: class LoLItem
+    ##
+    # C'tor.
+    #
+    constructor: (name, params) ->
+      @name = name
+
+      @code = params.code
+
+      for stat of lol.stats.names
+        this[stat] = params[stat] || 0
+
+      @cost = params.cost
+      @sell = params.sell
+
+      @recipe = params.recipe
+
+      @auras = params.auras
+      @passives = params.passives
+      @active = params.active
+
+      setSources = (effects) => e.source = this for e in effects
+
+      # Set sources of all effects
+      [@auras, @passives].map (el) -> setSources el if el
+      @active.source = this if @active
+
+      # Calculate stats
+      @stats = lol.stats.combine [this]
+
+    ##
+    # Gets the recipe of this item in an array containing the LoLItems of this item.
+    #
+    getRecipe: ->
+      recipe = []
+      for x in @recipe
+        recipe.push lol.items.findOne(x)
+      return recipe
+
+    ##
+    # Gets the items this item builds into.
+    #
+    getBuildsInto: ->
+      parents = []
+      for item in lol.items.find()
+        if item.recipe and item.recipe.indexOf(@name) isnt -1
+          parents.push item
+      return parents
+
+    ##
+    # Gets the cost of this item if no parts of the recipe had been bought yet.
+    #
+    getTotalCost: ->
+      return @cost unless @recipe
+      cost = @cost
+      for r in @getRecipe()
+        cost += r.getTotalCost()
+      return cost
+
+    toString: -> @name
+
+  ##
+  # Finds all items that fulfill a filter.
+  #
+  find: (filter) ->
+    unless filter?
+      filter = (item) -> true # Filterless filter
+
+    type = typeof filter
+
+    if type is "string"
+      return lol.items.find name: filter
+
+    else if type is "object"
+      return lol.items.find lol.items.filters.where filter
+
+    else if type is "function"
+      ret = []
+      for itemName, item of lol.items._list
+        ret.push item if filter item
+
+      return ret
+    else
+        throw new Error "Invalid arguments for find!"
+
+  ##
+  # Finds the first item that fulfills a filter.
   #
-  constructor: (name, params) ->
-    @name = name
-
-    @code = params.code
-
-    for stat of lol.stats.names
-      this[stat] = params[stat] || 0
-
-    @cost = params.cost
-    @sell = params.sell
-
-    @recipe = params.recipe
-
-    @auras = params.auras
-    @passives = params.passives
-    @active = params.active
-
-    setSources = (effects) => e.source = this for e in effects
-
-    # Set sources of all effects
-    [@auras, @passives].map (el) -> setSources el if el
-    @active.source = this if @active
-
-    # Calculate stats
-    @stats = lol.stats.combine [this]
-
-  ##
-  # Gets the recipe of this item in an array containing the LoLItems of this item.
+  findOne: (filter) ->
+    res = lol.items.find filter
+    if res.length is 0 then null else res[0]
+
+  ##
+  # Item filters for use in find().
   #
-  getRecipe: ->
-    recipe = []
-    for x in @recipe
-      recipe.push lol.items.findOne(x)
-    return recipe
-
-  ##
-  # Gets the items this item builds into.
+  filters:
+    ##
+    # Combines all filters in the arguments, returning true if all filters are true.
+    #
+    and: ->
+      args = arguments
+      (item) ->
+        for filter in args
+          return false if typeof filter isnt "function" or filter(item) is false
+        return true
+
+    ##
+    # Combines all filters in the arguments, returning true if one or more filters are true.
+    #
+    or: ->
+      args = arguments
+      (item) ->
+        for filter in args
+          return true if typeof filter is "function" and filter(item) is true
+        return false
+
+    ##
+    # Negates a filter.
+    #
+    not: (filter) -> (item) -> not filter item
+
+    ##
+    # Where -- complex filter that takes in a query object.
+    #
+    where: (params) ->
+      (item) ->
+        for property, matcher of params
+          # Handle passives, active, and auras
+          if ["passives", "active", "auras"].indexOf(property) isnt -1
+            return false # TODO
+
+          # Check for matching a function
+          if typeof matcher is "function"
+            return false unless matcher item[property]
+
+          # Check for equality
+          else if typeof matcher isnt "object"
+            return false if item[property] isnt matcher
+
+          # It's a query!
+          for handlerName, handler of lol.items._queryHandlers
+            if matcher[handlerName]
+              # Check if the property is of the right type
+              if handler.propTypes
+                prop = false
+                for type in handler.propTypes
+                  if typeof item[property] is type
+                    prop = true
+                    break
+
+                unless prop
+                  throw new Error "Invalid type for property '#{item[property]}'! Available types are: #{handler.propTypes.join(", ")}"
+
+              # Check if the handler argument is of the right type for the handler
+              if handler.valTypes
+                val = false
+                for type in handler.valTypes
+                  if typeof matcher[handlerName] is type
+                    val = true
+                    break
+
+                unless val
+                  throw new Error "Invalid type for handler argument '#{matcher[handlerName]}'! Available types are: #{handler.valTypes.join(", ")}"
+
+              # Does not match filter unless the handler is triggered correctly
+              return false unless handler.handler item[property], matcher[handlerName]
+
+        return true
+
+    ##
+    # Checks if the item has the given stats set.
+    #
+    withStats: (statNames) ->
+      (item) -> lol.stats.hasStats item.stats, statNames
+
+  ##
+  # Item sorts (comparators) for use in find().
   #
-  getBuildsInto: ->
-    parents = []
-    for item in lol.items.find()
-      if item.recipe and item.recipe.indexOf(@name) isnt -1
-        parents.push item
-    return parents
-
-  ##
-  # Gets the cost of this item if no parts of the recipe had been bought yet.
-  #
-  getTotalCost: ->
-    return @cost unless @recipe
-    cost = @cost
-    for r in @getRecipe()
-      cost += r.getTotalCost()
-    return cost
-
-  toString: -> @name
-
-##
-# Finds all items that fulfill a filter.
-#
-lol.items.find = (filter) ->
-  unless filter?
-    filter = (item) -> true # Filterless filter
-
-  type = typeof filter
-
-  if type is "string"
-    return lol.items.find name: filter
-
-  else if type is "object"
-    return lol.items.find lol.items.filters.where filter
-
-  else if type is "function"
-    ret = []
-    for itemName, item of lol.items._list
-      ret.push item if filter item
-
-    return ret
-  else
-      throw new Error "Invalid arguments for find!"
-
-##
-# Finds the first item that fulfills a filter.
-#
-lol.items.findOne = (filter) ->
-  res = lol.items.find filter
-  if res.length is 0 then null else res[0]
-
-##
-# Item filters for use in find().
-#
-lol.items.filters =
-  ##
-  # Combines all filters in the arguments, returning true if all filters are true.
-  #
-  and: ->
-    args = arguments
-    (item) ->
-      for filter in args
-        return false if typeof filter isnt "function" or filter(item) is false
-      return true
-
-  ##
-  # Combines all filters in the arguments, returning true if one or more filters are true.
-  #
-  or: ->
-    args = arguments
-    (item) ->
-      for filter in args
-        return true if typeof filter is "function" and filter(item) is true
-      return false
-
-  ##
-  # Negates a filter.
-  #
-  not: (filter) -> (item) -> not filter item
-
-  ##
-  # Where -- complex filter that takes in a query object.
-  #
-  where: (params) ->
-    (item) ->
-      for property, matcher of params
-        # Handle passives, active, and auras
-        if ["passives", "active", "auras"].indexOf(property) isnt -1
-          return false # TODO
-
-        # Check for matching a function
-        if typeof matcher is "function"
-          return false unless matcher item[property]
-
-        # Check for equality
-        else if typeof matcher isnt "object"
-          return false if item[property] isnt matcher
-
-        # It's a query!
-        for handlerName, handler of lol.items._queryHandlers
-          if matcher[handlerName]
-            # Check if the property is of the right type
-            if handler.propTypes
-              prop = false
-              for type in handler.propTypes
-                if typeof item[property] is type
-                  prop = true
-                  break
-
-              unless prop
-                throw new Error "Invalid type for property '#{item[property]}'! Available types are: #{handler.propTypes.join(", ")}"
-
-            # Check if the handler argument is of the right type for the handler
-            if handler.valTypes
-              val = false
-              for type in handler.valTypes
-                if typeof matcher[handlerName] is type
-                  val = true
-                  break
-
-              unless val
-                throw new Error "Invalid type for handler argument '#{matcher[handlerName]}'! Available types are: #{handler.valTypes.join(", ")}"
-
-            # Does not match filter unless the handler is triggered correctly
-            return false unless handler.handler item[property], matcher[handlerName]
-
-      return true
-
-  ##
-  # Checks if the item has the given stats set.
-  #
-  withStats: (statNames) ->
-    (item) -> lol.stats.hasStats item.stats, statNames
-
-##
-# Item sorts (comparators) for use in find().
-#
-lol.items.sorts =
-  ##
-  # Sorts by property.
-  #
-  byProperty: (property, asc = true) ->
-    (a, b) ->
-      pa = a[property] || 0
-      pb = b[property] || 0
-
-      unless typeof pa is "number" and typeof pb is "number"
-        throw new Error "The property must be a number."
-
-      if asc then pa - pb else pb - pa
-
-lol.items._queryHandlers =
-  eq:
-    handler: (property, val) -> property is val
-
-  neq:
-    handler: (property, val) -> property isnt val
-
-  gte:
-    propTypes: ["number"]
-    valTypes: ["number"]
-    handler: (property, val) -> property >= val
-
-  gt:
-    propTypes: ["number"]
-    valTypes: ["number"]
-    handler: (property, val) -> property > val
-
-  lte:
-    propTypes: ["number"]
-    valTypes: ["number"]
-    handler: (property, val) -> property <= val
-
-  lt:
-    propTypes: ["number"]
-    valTypes: ["number"]
-    handler: (property, val) -> property < val
-
-  contains:
-    propTypes: ["string"]
-    valTypes: ["string", "number"]
-    handler: (property, val) -> property.search(val.toString()) isnt -1
-
-  matches:
-    propTypes: ["string"]
-    valTypes: ["object"]
-    handler: (property, val) ->
-      throw new Error "Argument must be a regular expression!" unless val instanceof RegExp
-      val.test property
-
-##
-# Defines a new item.
-#
-lol.items._def = (name, item) ->
-  lol.items._list[name] = new lol.items.LoLItem name, item
-
+  sorts:
+    ##
+    # Sorts by property.
+    #
+    byProperty: (property, asc = true) ->
+      (a, b) ->
+        pa = a[property] || 0
+        pb = b[property] || 0
+
+        unless typeof pa is "number" and typeof pb is "number"
+          throw new Error "The property must be a number."
+
+        if asc then pa - pb else pb - pa


Fixed invalid yaml config upgrading from 0.4.0 to 0.4.1 #37

--- src/jscpd.coffee
+++ src/jscpd.coffee
@@ -18,10 +18,12 @@
   readConfig: (file) ->
     file = path.normalize file
     try
-      doc = yaml.safeLoad(fs.readFileSync(file, 'utf8'));
+      doc = yaml.safeLoad fs.readFileSync(file, 'utf8')
       logger.info "Used config from #{file}"
+      return doc
     catch e
       logger.warn "File #{file} not found in current directory, or it is broken"
+      return false

   run: (options)->
     cwd = options.path


Add some upper case asserts

Add some upper case asserts

--- spec/directory-spec.coffee
+++ spec/directory-spec.coffee
@@ -119,16 +119,19 @@

         expect(directory.relativize(directoryPath.toUpperCase())).toBe ""
         expect(directory.relativize(path.join(directoryPath.toUpperCase(), "b"))).toBe "b"
+        expect(directory.relativize(path.join(directoryPath.toUpperCase(), "B"))).toBe "B"
         expect(directory.relativize(path.join(directoryPath.toUpperCase(), "b/file.coffee"))).toBe "b/file.coffee"
         expect(directory.relativize(path.join(directoryPath.toUpperCase(), "file.coffee"))).toBe "file.coffee"

         expect(directory.relativize(directoryPath.toLowerCase())).toBe ""
         expect(directory.relativize(path.join(directoryPath.toLowerCase(), "b"))).toBe "b"
+        expect(directory.relativize(path.join(directoryPath.toLowerCase(), "B"))).toBe "B"
         expect(directory.relativize(path.join(directoryPath.toLowerCase(), "b/file.coffee"))).toBe "b/file.coffee"
         expect(directory.relativize(path.join(directoryPath.toLowerCase(), "file.coffee"))).toBe "file.coffee"

         expect(directory.relativize(directoryPath)).toBe ""
         expect(directory.relativize(path.join(directoryPath, "b"))).toBe "b"
+        expect(directory.relativize(path.join(directoryPath, "B"))).toBe "B"
         expect(directory.relativize(path.join(directoryPath, "b/file.coffee"))).toBe "b/file.coffee"
         expect(directory.relativize(path.join(directoryPath, "file.coffee"))).toBe "file.coffee"

@@ -161,16 +164,19 @@

       expect(directory.contains(directoryPath.toUpperCase())).toBe false
       expect(directory.contains(path.join(directoryPath.toUpperCase(), "b"))).toBe true
+      expect(directory.contains(path.join(directoryPath.toUpperCase(), "B"))).toBe true
       expect(directory.contains(path.join(directoryPath.toUpperCase(), "b", "file.coffee"))).toBe true
       expect(directory.contains(path.join(directoryPath.toUpperCase(), "file.coffee"))).toBe true

       expect(directory.contains(directoryPath.toLowerCase())).toBe false
       expect(directory.contains(path.join(directoryPath.toLowerCase(), "b"))).toBe true
+      expect(directory.contains(path.join(directoryPath.toLowerCase(), "B"))).toBe true
       expect(directory.contains(path.join(directoryPath.toLowerCase(), "b", "file.coffee"))).toBe true
       expect(directory.contains(path.join(directoryPath.toLowerCase(), "file.coffee"))).toBe true

       expect(directory.contains(directoryPath)).toBe false
       expect(directory.contains(path.join(directoryPath, "b"))).toBe true
+      expect(directory.contains(path.join(directoryPath, "B"))).toBe true
       expect(directory.contains(path.join(directoryPath, "b", "file.coffee"))).toBe true
       expect(directory.contains(path.join(directoryPath, "file.coffee"))).toBe true


Add serialization specs

Add serialization specs

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -4,7 +4,7 @@
 Project = require 'project'
 {View} = require 'space-pen'

-fdescribe 'FindView', ->
+describe 'FindView', ->
   [editor, findView] = []

   beforeEach ->
@@ -38,6 +38,47 @@
       $(document.activeElement).trigger 'core:cancel'
       expect(findResultsView.parent()).not.toExist()

+  describe "serialization", ->
+    it "serializes find and replace history", ->
+      findView.findEditor.setText("items")
+      findView.replaceEditor.setText("cat")
+      findView.replaceAll()
+
+      findView.findEditor.setText("sort")
+      findView.replaceEditor.setText("dog")
+      findView.replaceAll()
+
+      atom.deactivatePackage("find-and-replace")
+      pack = atom.activatePackage("find-and-replace")
+      findView = pack.mainModule.findView
+
+      findView.findEditor.trigger('core:move-up')
+      expect(findView.findEditor.getText()).toBe 'sort'
+
+      findView.replaceEditor.trigger('core:move-up')
+      expect(findView.replaceEditor.getText()).toBe 'dog'
+
+    it "serializes find options ", ->
+      expect(findView.caseSensitiveOptionButton).not.toHaveClass 'enabled'
+      expect(findView.regexOptionButton).not.toHaveClass 'enabled'
+      expect(findView.inSelectionOptionButton).not.toHaveClass 'enabled'
+
+      findView.caseSensitiveOptionButton.click()
+      findView.regexOptionButton.click()
+      findView.inSelectionOptionButton.click()
+
+      expect(findView.caseSensitiveOptionButton).toHaveClass 'enabled'
+      expect(findView.regexOptionButton).toHaveClass 'enabled'
+      expect(findView.inSelectionOptionButton).toHaveClass 'enabled'
+
+      atom.deactivatePackage("find-and-replace")
+      pack = atom.activatePackage("find-and-replace")
+      findView = pack.mainModule.findView
+
+      expect(findView.caseSensitiveOptionButton).toHaveClass 'enabled'
+      expect(findView.regexOptionButton).toHaveClass 'enabled'
+      expect(findView.inSelectionOptionButton).toHaveClass 'enabled'
+
   describe "finding", ->
     beforeEach ->
       editor.setCursorBufferPosition([2,0])


Reduce font size

Reduce font size

--- atom/config.cson
+++ atom/config.cson
@@ -16,6 +16,6 @@
     'userId': '9e4d32b140be1cb4e975a34a65c17fc15d35c6d7'
   'editor':
     'confirmCheckoutHeadRevision': false
-    'fontSize': 15
+    'fontSize': 14
     'fontFamily': 'Menlo'
     'invisibles': {}


Cleanup

Cleanup

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -146,7 +146,6 @@
     # Type cast IDs
     # TODO: https://github.com/pebble/joi-objectid/issues/2#issuecomment-75189638
     query.author_id = ObjectId input.author_id if input.author_id
-    # Unrelated, but aren't we passing in just a single fair id?
     query.fair_id = { $in: _.map(input.fair_ids, ObjectId) } if input.fair_ids
     query.partner_ids = ObjectId input.partner_id if input.partner_id
     query.show_ids = ObjectId input.show_id if input.show_id


Remove map.coffee.

Remove map.coffee.

--- public/js/map.coffee
+++ public/js/map.coffee
@@ -1,50 +0,0 @@
-$(document).ready( () ->
-  param = _getParameters()
-  switch param.type
-    when "search"
-      $.ajax(
-        url: "/navigate/searchmap"
-        data:
-          search: param.search
-        success: (data) ->
-          console.log(param.search)
-          $("#map-wrapper").html(data);
-          $("#map-directions-end").val(param.search);
-      )
-
-    when "directions"
-      $.ajax(
-        url: "/navigate/navmap"
-        data:
-          from: param.from
-          to: param.to
-        success: (data) ->
-          $("#map-directions-start").val(param.from);
-          $("#map-directions-end").val(param.to);
-          $("#map-wrapper").html(data);
-      )
-
-  #get live feed elements
-  $.getJSON('/report/getall',(data) ->
-    $.each(data, (key, val) ->
-      time = val.time
-      type = val.type
-      comment = val.comment
-      $("#live-feed").prepend('

'+'
' + type + '

' + comment+'

—'+ time + '

')
-    )
-  )
-)
-
-###
-  Extracts GET parameters from url
-###
-_getParameters = () ->
-  pl     = /\+/g  #Regex for replacing addition symbol with a space
-  search = /([^&=]+)=?([^&]*)/g
-  decode = (s) -> return decodeURIComponent(s.replace(pl, " "));
-  query  = window.location.search.substring(1);
-
-  urlParams = {};
-  while (match = search.exec(query))
-    urlParams[decode(match[1])] = decode(match[2]);
-  return urlParams;


increase query expire time to 5min

increase query expire time to 5min

--- models/event.coffee
+++ models/event.coffee
@@ -10,7 +10,7 @@

 OS_CPUS = os.cpus().length
 PREFIX = config.REDIS.PREFIX + ':event'
-QUERY_EXPIRE_TIME_SECONDS = 60 # 1 min - release all queries back to pool
+QUERY_EXPIRE_TIME_SECONDS = 5 * 60 # 5 min - release all queries back to pool
 UNCACHEABLE_EXPIRE_TIME_SECONDS = 60 * 60 # 1hr

 timeSuffixToMs = (time, suffix) ->


Queue finished classifications correctly

--- app/lib/classifications.coffee
+++ app/lib/classifications.coffee
@@ -35,7 +35,9 @@
     queue = JSON.parse localStorage.getItem 'classifications'
     queue ?= []

-    @classification.update
+    finished = @classification
+
+    finished.update
       completed: true
       'metadata.finished_at': (new Date).toISOString()
       'metadata.viewport':
@@ -54,11 +56,11 @@
             newQueue.push classificationData
             localStorage.setItem 'classifications', JSON.stringify newQueue

-    @classification.save()
+    finished.save()
       .then (classification) ->
         classification.destroy()
       .catch (e) =>
-        newQueue.push @classification
+        newQueue.push finished
         localStorage.setItem 'classifications', JSON.stringify newQueue
     localStorage.setItem 'classifications', JSON.stringify newQueue


changed to use styl extension only

changed to use styl extension only

--- app/templates/Gruntfile.coffee
+++ app/templates/Gruntfile.coffee
@@ -115,19 +115,18 @@
         conf.src = filepath
         grunt.config 'less.compile.files', [conf]
         'less'
+      styl: (filepath) ->
+        return if /_[^\/]*\.styl$/.test filepath
+        conf = grunt.config('stylus.compile.files')[0]
+        conf.src = filepath
+        grunt.config 'stylus.compile.files', [conf]
+        'stylus'
       options:
         dirs: ['coffee/**', 'jade/**', 'js/**', 'less/**', 'stylus/**']
         livereload:
           enabled: true
-          extensions: ['coffee', 'jade', 'js', 'less', 'styl', 'stylus']
+          extensions: ['coffee', 'jade', 'js', 'less', 'styl']
           port: 35729
-      styl: '<%= esteWatch.stylus %>'
-      stylus: (filepath) ->
-        return if /_[^\/]*\.styl(?:us)?$/.test filepath
-        conf = grunt.config('stylus.compile.files')[0]
-        conf.src = filepath
-        grunt.config 'stylus.compile.files', [conf]
-        'stylus'

   grunt.loadNpmTasks 'grunt-bower-task'
   grunt.loadNpmTasks 'grunt-contrib-clean'


Log some items

Log some items

--- app/utils/connector-rfb.coffee
+++ app/utils/connector-rfb.coffee
@@ -13,6 +13,8 @@
   rfb: null

   constructor: (@canvasEl, @deviceToken) ->
+    console.log "ConnectorRFB - constructor"
+    console.log @canvasEl, @deviceToken
     @rfb = new RFB
       'target': @canvasEl
       'encrypt': ENV.deviceFarmSsl


Use `isBrowser` check for tests

Use `isBrowser` check for tests

--- spec/AllPackets.coffee
+++ spec/AllPackets.coffee
@@ -1,5 +1,5 @@
 noflo = require 'noflo'
-if typeof process is 'object' and process.title is 'node'
+unless noflo.isBrowser()
   chai = require 'chai' unless chai
   AllPackets = require '../components/AllPackets.coffee'
 else


Fixing timeout in test.

Fixing timeout in test.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -108,7 +108,7 @@
             waitsFor ->
                 setTimeout ->
                     editorView.trigger 'jumpy:c'
-                ,150 + 10
+                ,100 + 10 # max default I'd probably use + a buffer
             runs ->
                 expect(editorView.find('.beacon')).not.toExist()


Add spec covering PreviewOpener if Skim is not installed

--- spec/latex-spec.coffee
+++ spec/latex-spec.coffee
@@ -140,3 +140,10 @@
       opener = latex.getOpener()

       expect(opener.constructor.name).toEqual('SkimOpener')
+
+    it "returns PreviewOpener when Skim is not installed on OS X", ->
+      atom.config.set('latex.skimPath', '/foo/Skim.app')
+      helpers.overridePlatform('darwin')
+      opener = latex.getOpener()
+
+      expect(opener.constructor.name).toEqual('PreviewOpener')


$value for events

$value for events

--- src/event.coffee
+++ src/event.coffee
@@ -143,7 +143,17 @@
         (scope, element, expression, env) ->
             fn = scope.$compile expression,
                 no_return: true
-                input: ['$event', '$element']
+                input: ['$event', '$element', '$value']
+
+            if element.type is 'checkbox'
+                getValue = ->
+                    element.checked
+            else if element.type is 'radio'
+                getValue = ->
+                    element.value or element.checked
+            else
+                getValue = ->
+                    element.value

             handler = (event) ->
                 if filterByKey
@@ -163,7 +173,7 @@
                 if stop
                     event.stopPropagation()
                 try
-                    fn scope, event, element
+                    fn scope, event, element, getValue()
                 catch error
                     alight.exceptionHandler error, "Error in event: #{attrArgument} = #{expression}",
                         attr: attrArgument


additional test, just to make sure...

additional test, just to make sure...

--- spec/specs/scan.coffee
+++ spec/specs/scan.coffee
@@ -26,6 +26,10 @@
     expectPropertyEvents(
       -> Bacon.mergeAll(once(1), once(2)).scan(0, (a,b) -> a+b)
       [0,1,3], unstable)
+  describe "works with functions as values", ->
+    expectPropertyEvents(
+      -> series(1, [(-> 1), (-> 2)]).scan((-> 0), (a, b) -> b).map((f) -> f())
+    [0, 1, 2])
   describe "calls accumulator function once per value", ->
     describe "(simple case)", ->
       count = 0


hopefully more reliable show test

hopefully more reliable show test

--- apps/show/test/template.coffee
+++ apps/show/test/template.coffee
@@ -2,6 +2,7 @@
 jade            = require 'jade'
 path            = require 'path'
 fs              = require 'fs'
+sd              = require('sharify').data
 cheerio         = require 'cheerio'
 Backbone        = require 'backbone'
 { fabricate }   = require 'antigravity'
@@ -23,16 +24,13 @@
   describe 'with a public profile', ->

     beforeEach ->
-      @sd =
-        ARTSY_URL : 'http://localhost:5000'
-        ASSET_PATH: 'http://localhost:5000'
       @show = new PartnerShow fabricate('show')
       @profile = new Profile fabricate 'partner_profile'
       @html = render('template')({
         fair    : @show.fair()
         location: @show.location()
         partner : @show.partner()
-        sd      : @sd
+        sd      : sd
         show    : @show
         profile : @profile
       })
@@ -45,7 +43,7 @@
           fair    : @show.fair()
           location: @show.location()
           partner : @show.partner()
-          sd      : @sd
+          sd      : sd
           show    : @show
           profile : @profile
         })
@@ -58,7 +56,7 @@
           fair    : @show.fair()
           location: @show.location()
           partner : @show.partner()
-          sd      : @sd
+          sd      : sd
           show    : @show
           profile : @profile
           context : 'fair'
@@ -74,7 +72,7 @@
           fair    : @show.fair()
           location: @show.location()
           partner : @show.partner()
-          sd      : @sd
+          sd      : sd
           show    : @show
           profile : @profile
           context : 'fair'
@@ -111,7 +109,7 @@
   describe 'with a private profile', ->

     beforeEach ->
-      @sd =
+      sd =
         ARTSY_URL : 'http://localhost:5000'
         ASSET_PATH: 'http://localhost:5000'
       @show = new PartnerShow fabricate('show')
@@ -120,7 +118,7 @@
         fair    : @show.fair()
         location: @show.location()
         partner : @show.partner()
-        sd      : @sd
+        sd      : sd
         show    : @show
       })

@@ -132,7 +130,7 @@
           fair    : @show.fair()
           location: @show.location()
           partner : @show.partner()
-          sd      : @sd
+          sd      : sd
           show    : @show
           profile : @profile
         })


pinned prop should pass through for single parts form parent

--- src/components/exercise/index.cjsx
+++ src/components/exercise/index.cjsx
@@ -53,7 +53,6 @@

     partProps =
       footer: footer
-      pinned: true
       focus: true
       includeGroup: true


Sliderbar Component: Bugfix and new features

Dragging changed to use draggable.
Handle creation method changed.
Change by click feature added.

--- client/landing/app/Applications/Demos.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Demos.kdapplication/AppView.coffee
@@ -1,10 +1,14 @@
 class DemosMainView extends KDScrollView

   viewAppended:->
-    @addSubView new KDSliderBarView
-      minValue  : 40
-      maxValue  : 100
-      interval  : 20
-    , handles   :
-        handle1 :
-          value : 40+    @addSubView example = new KDSliderBarView
+      cssClass   : 'my-cute-slider'
+      minValue   : 0
+      maxValue   : 100
+      interval   : 10
+      width 	 : 500
+      snap 	     : yes
+      drawBar 	 : yes
+      showLabels : [0, 25, 50, 75, 100]
+      handles    : [60]
+


removing old ajax call

removing old ajax call

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -234,20 +234,6 @@
         if response.hide_reset
           @reset_button.hide()

-  get_last_response: (@answer_area) =>
-    @submit_button.hide()
-    @answer_area.attr("disabled", true)
-    @gentle_alert "Checking for a previous response..."
-    data = {}
-    $.postWithPrefix "#{@ajax_url}/get_last_response", data, (response) =>
-      if response.success && response.response != ""
-        @answer_area.html(response.response)
-        @gentle_alert "You have already answered this question."
-      else
-        @submit_button.show()
-        @answer_area.attr("disabled", false)
-        @el.find('.open-ended-alert').animate(opacity: 0, 700)
-
   message_post: (event)=>
     external_grader_message=$(event.target).parent().parent().parent()
     evaluation_scoring = $(event.target).parent()
@@ -311,7 +297,6 @@
       @submit_button.prop('value', 'Submit')
       @submit_button.click @save_answer
       @setup_file_upload()
-      @get_last_response(@answer_area)
     else if @child_state == 'assessing'
       @answer_area.attr("disabled", true)
       @replace_text_inputs()


Load language-test package before activating snippets in all tests

When the snippets package activates, it reads snippets from all of the
loaded packages in an async code block after loading bundled snippets.

Previously, tests were loading the `language-test` package after
activating snippets, which created a race between the async code block
that read the loaded packages and the loading of this package. Most of
the time, the jasmine waitsFor block would load the package before the
async code in the snippets package scanned the loaded package.
Sometimes, however, it would not. Hopefully this addresses this
unreliability by always activating the language package before the
snippets package to avoid the race.

--- spec/installed-package-view-spec.coffee
+++ spec/installed-package-view-spec.coffee
@@ -45,15 +45,15 @@
     shouldRunScopeTest = parseFloat(atom.getVersion()) >= 1.33

     waitsForPromise ->
+      atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
+
+    waitsForPromise ->
       atom.packages.activatePackage('snippets').then (p) ->
         snippetsModule = p.mainModule
         return unless snippetsModule.provideSnippets().getUnparsedSnippets?

         SnippetsProvider =
           getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
-
-    waitsForPromise ->
-      atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))

     waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()

@@ -82,15 +82,15 @@
       snippetsModule = null

       waitsForPromise ->
+        atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
+
+      waitsForPromise ->
         atom.packages.activatePackage('snippets').then (p) ->
           snippetsModule = p.mainModule
           return unless snippetsModule.provideSnippets().getUnparsedSnippets?

           SnippetsProvider =
             getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
-
-      waitsForPromise ->
-        atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))

       waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()

@@ -119,6 +119,9 @@

       beforeEach ->
         waitsForPromise ->
+          atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
+
+        waitsForPromise ->
           atom.packages.activatePackage('snippets').then (p) ->
             snippetsModule = p.mainModule
             return unless snippetsModule.provideSnippets().getUnparsedSnippets?
@@ -126,9 +129,6 @@
             SnippetsProvider =
               getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
               getUserSnippetsPath: snippetsModule.getUserSnippetsPath()
-
-        waitsForPromise ->
-          atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))

         waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()

@@ -168,6 +168,10 @@
     it "sets the packagesWithSnippetsDisabled config to include the package name", ->
       [pack, card] = []
       snippetsModule = []
+
+      waitsForPromise ->
+        atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
+
       waitsForPromise ->
         atom.packages.activatePackage('snippets').then (p) ->
           snippetsModule = p.mainModule
@@ -175,9 +179,6 @@

           SnippetsProvider =
             getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
-
-      waitsForPromise ->
-        atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))

       waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()


fixes get script to exit properly on missing key

--- script/get.coffee
+++ script/get.coffee
@@ -52,6 +52,7 @@
       sources key
     else
       console.log '<missing>'
+      r.end()

 sources = (key) ->
   r.smembers "sources:#{key}", (err, arr) ->


activity: clear state when unmounting new public channel modal

--- client/activity/lib/components/createpublicchannelmodal/index.coffee
+++ client/activity/lib/components/createpublicchannelmodal/index.coffee
@@ -35,6 +35,13 @@
       invalidName         : no
       invalidParticipants : no
       placeholder         : 'type a @username and hit enter'
+
+
+  componentWillUnmount: ->
+
+    CreateChannelFlux.actions.user.resetSelectedIndex()
+    CreateChannelFlux.actions.user.unsetInputQuery()
+    CreateChannelFlux.actions.channel.removeAllParticipants()

   getDataBindings: ->


Pesky log!

Pesky log!

--- src/cli.coffee
+++ src/cli.coffee
@@ -19,8 +19,6 @@
   cmd += " -d " + o.threshold if o.threshold
   cmd += " -n " + o.ngram if o.ngram
   cmd += " -a " + o.algorithm if o.algorithm
-
-  log.info cmd

   res = shell.exec cmd


add request for getting valid cookie

add request for getting valid cookie

--- src/cli.coffee
+++ src/cli.coffee
@@ -22,6 +22,14 @@
       screenId: '0102001'
       loginRouteId: '0102001'

+fetchForCookie = (cookieStore) ->
+  request
+    jar: cookieStore
+    url: 'https://my.jcb.co.jp/iss-pc/member/details_inquiry/detail.html'
+    qs:
+      detailMonth: 1
+      output: 'web'
+
 fetch = ->
   # get cookie store
   jar = request.jar()
@@ -30,6 +38,9 @@
   .then ->
     console.log 'authorize'
     authorize jar
+  .then ->
+    console.log 'fetch cookie'
+    fetchForCookie jar
   .catch (e) ->
     console.error e


Unsubscribe EditorComponent before unmounting

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -78,6 +78,7 @@
     @requestUpdate()

   componentWillUnmount: ->
+    @unsubscribe()
     @getDOMNode().removeEventListener 'mousewheel', @onMouseWheel

   componentWillUpdate: ->


HistoryManager: Added setToken method.

HistoryManager: Added setToken method.

--- src/lib/core/HistoryManager.coffee
+++ src/lib/core/HistoryManager.coffee
@@ -43,3 +43,13 @@
       @emit 'Navigated', e.token

     @history.setEnabled yes
+
+
+  ###*
+    Set token method to update history token and change the page url.
+
+    @export
+    @param {!string} token Token to change page url.
+  ###
+  setToken: (token) ->
+    @history.setToken token


Never merge nodes to the left of splices

--- src/patch.coffee
+++ src/patch.coffee
@@ -25,14 +25,6 @@
       @children.splice(childIndex, 1, splitChildren...)
       childIndex += splitChildren.indexOf(spliceChild)
       rightMergeIndex += splitChildren.length - 1
-
-    if leftNeighbor = @children[leftMergeIndex - 1]
-      leftNeighbor.merge(@children[leftMergeIndex])
-      if isEmpty(@children[leftMergeIndex])
-        @children[leftMergeIndex].assign(leftNeighbor)
-        @children.splice(leftMergeIndex - 1, 1)
-        childIndex--
-        rightMergeIndex--

     if rightNeighbor = @children[rightMergeIndex + 1]
       @children[rightMergeIndex].merge(rightNeighbor)
@@ -67,8 +59,6 @@
       rightNeighbor.outputExtent = rightNeighbor.outputExtent.traversalFrom(childMerge.outputExtent)
       childMerge

-  assign: ({@inputExtent, @outputExtent, @children}) -> this
-
   hasBoundary: (minInputPosition, maxInputPosition=Point.infinity()) ->
     inputEnd = Point.zero()
     for child in @children
@@ -125,7 +115,7 @@

     else if @content instanceof Boundary
       splitNodes = [
-        new Leaf().assign(this),
+        new Leaf(Point.zero(), Point.zero(), @content)
         this
       ]
       @inputExtent = newInputExtent
@@ -162,8 +152,6 @@
       rightNeighbor.inputExtent = rightNeighbor.outputExtent = Point.zero()
       rightNeighbor.content = null
       result
-
-  assign: ({@inputExtent, @outputExtent, @content}) -> this

   hasBoundary: (minInputPosition) ->
     @content instanceof Boundary


Replacing Odometer name bu Textroller

Replacing Odometer name bu Textroller

--- odometer.coffee
+++ odometer.coffee
@@ -96,14 +96,14 @@
 # In case jQuery is brought in after this file
 setTimeout wrapJQuery, 0

-class Odometer
+class TextRoller
   constructor: (@options) ->
     @el = @options.el
     return @el.odometer if @el.odometer?

     @el.odometer = @

-    for k, v in Odometer.options
+    for k, v in TextRoller.options
       if not @options[k]?
         @options[k] = v

@@ -210,7 +210,7 @@

     parsed = FORMAT_PARSER.exec format
     if not parsed
-      throw new Error "Odometer: Unparsable digit format"
+      throw new Error "TextRoller: Unparsable digit format"

     [repeating, radix, fractional] = parsed[1..3]

@@ -539,38 +539,38 @@
     if fractionalCount
       @addSpacer @format.radix, @digits[fractionalCount - 1], 'odometer-radix-mark'

-Odometer.options = window.odometerOptions ? {}
+TextRoller.options = window.odometerOptions ? {}

 setTimeout ->
   # We do this in a seperate pass to allow people to set
   # window.odometerOptions after bringing the file in.
   if window.odometerOptions
     for k, v of window.odometerOptions
-      Odometer.options[k] ?= v
+      TextRoller.options[k] ?= v
 , 0

-Odometer.init = ->
+TextRoller.init = ->
   if not document.querySelectorAll?
     # IE 7 or 8 in Quirksmode
     return

-  elements = document.querySelectorAll (Odometer.options.selector or '.odometer')
+  elements = document.querySelectorAll (TextRoller.options.selector or '.odometer')

   for el in elements
-    el.odometer = new Odometer {el, value: el.innerText}
+    el.odometer = new TextRoller {el, value: el.innerText}

 if document.documentElement?.doScroll? and document.createEventObject?
   # IE < 9
   _old = document.onreadystatechange
   document.onreadystatechange = ->
-    if document.readyState is 'complete' and Odometer.options.auto isnt false
-      Odometer.init()
+    if document.readyState is 'complete' and TextRoller.options.auto isnt false
+      TextRoller.init()

     _old?.apply this, arguments
 else
   document.addEventListener 'DOMContentLoaded', ->
-    if Odometer.options.auto isnt false
-      Odometer.init()
+    if TextRoller.options.auto isnt false
+      TextRoller.init()
   , false

-window.Odometer = Odometer
+window.TextRoller = TextRoller


change display of unhandled text to colored text + fat blocks to allow for line breaks

--- src/mkts-typesetter-interim.coffee
+++ src/mkts-typesetter-interim.coffee
@@ -586,10 +586,18 @@
     else if MKTS.isa event, '.', 'text'
       send event
     else unless event[ 3 ][ 'processed' ]
-      event_tex = MKTS.fix_typography_for_tex ( rpr event ), @options
-      # send [ 'tex', "{\\color{magenta}unhandled event: #{event_tex}}" ]
-      # send [ 'tex', "\\colorbox{red}{\\color{yellow}unhandled event: #{event_tex}}" ]
-      send [ 'tex', "\\mktsErrorbox{unhandled event: #{event_tex}}" ]
+      [ type, name, text, meta, ] = event
+      if text?
+        if ( CND.isa_pod text )
+          if ( Object.keys text ).length is 0
+            text = ''
+          else
+            text = rpr text
+      else
+        text = ''
+      event_txt = type + name + text
+      event_tex = MKTS.fix_typography_for_tex event_txt, @options
+      send [ 'tex', "{\\mktsStyleBold\\color{violet}{\\mktsStyleSymbol█}#{event_tex}{\\mktsStyleSymbol█}}" ]
       send event
     else
       send event


:memo: Convert Point docs

:memo: Convert Point docs

--- src/point.coffee
+++ src/point.coffee
@@ -6,20 +6,17 @@
 #
 # ```coffee
 # new Point(1, 2)
-# [1, 2]
+# [1, 2] # Point compatible Array
 # ```
 module.exports =
 class Point
   # Public: Convert any point-compatible object to a {Point}.
   #
-  # * object:
-  #     This can be an object that's already a {Point}, in which case it's
-  #     simply returned, or an array containing two {Number}s representing the
-  #     row and column.
-  #
-  # * copy:
-  #     An optional boolean indicating whether to force the copying of objects
-  #     that are already points.
+  # * `object` This can be an object that's already a {Point}, in which case it's
+  #   simply returned, or an array containing two {Number}s representing the
+  #   row and column.
+  # * `copy` An optional boolean indicating whether to force the copying of objects
+  #   that are already points.
   #
   # Returns: A {Point} based on the given object.
   @fromObject: (object, copy) ->
@@ -33,7 +30,10 @@

       new Point(row, column)

-  # Public: Returns the given point that is earlier in the buffer.
+  # Public: Returns the given {Point} that is earlier in the buffer.
+  #
+  # * `point1` {Point}
+  # * `point2` {Point}
   @min: (point1, point2) ->
     point1 = @fromObject(point1)
     point2 = @fromObject(point2)
@@ -42,6 +42,10 @@
     else
       point2

+  # Public: Construct a {Point} object
+  #
+  # * `row` {Number} row
+  # * `column` {Number} column
   constructor: (@row=0, @column=0) ->

   # Public: Returns a new {Point} with the same row and column.
@@ -49,11 +53,15 @@
     new Point(@row, @column)

   # Public: Makes this point immutable and returns itself.
+  #
+  # Returns an immutable version of this {Point}
   freeze: ->
     Object.freeze(this)

   # Public: Return a new {Point} based on shifting this point by the given delta,
   # which is represented by another {Point}.
+  #
+  # * `delta` {Point} to shift by
   translate: (delta) ->
     {row, column} = Point.fromObject(delta)
     new Point(@row + row, @column + column)
@@ -78,12 +86,11 @@

   # Public:
   #
-  # * other: A {Point} or point-compatible {Array}.
+  # * `other` A {Point} or point-compatible {Array}.
   #
-  # Returns:
-  #  * -1 if this point precedes the argument.
-  #  * 0 if this point is equivalent to the argument.
-  #  * 1 if this point follows the argument.
+  # Returns `-1` if this point precedes the argument.
+  # Returns `0` if this point is equivalent to the argument.
+  # Returns `1` if this point follows the argument.
   compare: (other) ->
     if @row > other.row
       1
@@ -99,6 +106,8 @@

   # Public: Returns a {Boolean} indicating whether this point has the same row
   # and column as the given {Point} or point-compatible {Array}.
+  #
+  # * `other` A {Point} or point-compatible {Array}.
   isEqual: (other) ->
     return false unless other
     other = Point.fromObject(other)
@@ -106,21 +115,29 @@

   # Public: Returns a {Boolean} indicating whether this point precedes the given
   # {Point} or point-compatible {Array}.
+  #
+  # * `other` A {Point} or point-compatible {Array}.
   isLessThan: (other) ->
     @compare(other) < 0

   # Public: Returns a {Boolean} indicating whether this point precedes or is
   # equal to the given {Point} or point-compatible {Array}.
+  #
+  # * `other` A {Point} or point-compatible {Array}.
   isLessThanOrEqual: (other) ->
     @compare(other) <= 0

   # Public: Returns a {Boolean} indicating whether this point follows the given
   # {Point} or point-compatible {Array}.
+  #
+  # * `other` A {Point} or point-compatible {Array}.
   isGreaterThan: (other) ->
     @compare(other) > 0

   # Public: Returns a {Boolean} indicating whether this point follows or is
   # equal to the given {Point} or point-compatible {Array}.
+  #
+  # * `other` A {Point} or point-compatible {Array}.
   isGreaterThanOrEqual: (other) ->
     @compare(other) >= 0


point

point

--- src/point.coffee
+++ src/point.coffee
@@ -391,7 +391,6 @@

     @name equal
     @methodOf Point#
-
     @param {Point} other The point to check for equality.
     @returns {Boolean} true if the other point has the same x, y coordinates, false otherwise.
     ###
@@ -402,10 +401,10 @@
     Computed the length of this point as though it were a vector from (0,0) to (x,y).

-       point = Point(5, 7)
-
-       point.length()
-    => 8.602325267042627
+    point = Point(5, 7)
+
+    point.length()
+    # => 8.602325267042627

     @name length


Reintroduce removed Point methods because they're needed

--- src/point.coffee
+++ src/point.coffee
@@ -54,6 +54,24 @@
   translate: (delta) ->
     {row, column} = Point.fromObject(delta)
     new Point(@row + row, @column + column)
+
+  add: (other) ->
+    other = Point.fromObject(other)
+    row = @row + other.row
+    if other.row == 0
+      column = @column + other.column
+    else
+      column = other.column
+
+    new Point(row, column)
+
+  splitAt: (column) ->
+    if @row == 0
+      rightColumn = @column - column
+    else
+      rightColumn = @column
+
+    [new Point(0, column), new Point(@row, rightColumn)]

   # Public:
   #


Add function to keep track of state

Add function to keep track of state

--- src/proxy.coffee
+++ src/proxy.coffee
@@ -43,29 +43,22 @@
         @port = null
         @process = null
         @stopTimeout = null
-        @state = @states.stopped
         @readyCallbacks = []
-
-    states:
-        stopped: 0
-        starting: 1
-        running: 2

     start: (readyCallback) =>
         if @stopTimeout?
             clearTimeout @stopTimeout
         @stopTimeout = setTimeout @stop, @serviceManager.config.timeout

-        if @state == @states.running
+        if @state() == 'running'
             readyCallback()
             return
-        if @state == @states.starting
-            @readyCallbacks.push readyCallback
+
+        @readyCallbacks.push readyCallback
+        if @state() == 'starting'
             return

-        @state = @states.starting
-
-
+        @state 'starting'
         freeport (err, port) =>
             @port = port
             processName = "#{@serviceManager.config.dir}/#{@name}"
@@ -89,7 +82,7 @@
                 console.log "#{@name}: stopped, code #{code}, signal #{signal}"
                 @port = null
                 @process = null
-                @state = @states.stopped
+                @state 'stopped'

             # wait until it starts responding to TCP connections before connecting
             tester = =>
@@ -98,11 +91,7 @@
                         testClient.end()
                     testClient.on 'end', () =>
                         # ready to run
-                        @state = @states.running
-                        readyCallback()
-                        for c in @readyCallbacks
-                            c()
-                        @readyCallbacks = []
+                        @state 'running'
                     testClient.on 'error', (e) ->
                         if e.code in ['ECONNREFUSED']
                             setTimeout tester, 1000
@@ -122,6 +111,24 @@
                     process.kill(child.PID)
                 @process.kill()

+    state: (newState) =>
+        # Keeps track of state and runs certain state-transition tasks
+        oldState = @__state
+        if not newState?
+            console.log "oldState requested"
+            return oldState
+        if newState not in ['stopped', 'starting', 'running']
+            throw new Error("Invalid state #{newState}")
+        console.log "#{oldState} -> #{newState}"
+        @__state = newState
+
+        # state-transition tasks
+        if oldState == 'starting'
+            if newState == 'running'
+                for callback in @readyCallbacks
+                    callback()
+            @readyCallbacks = []
+
 module.exports =
     ServiceManager: ServiceManager
     Service: Service


Wait for the process to be ready to accept connections before starting proxying

--- src/proxy.coffee
+++ src/proxy.coffee
@@ -5,6 +5,7 @@
 http = require 'http'
 path = require 'path'
 fs = require 'fs'
+net = require 'net'

 HOME = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE

@@ -76,10 +77,22 @@
                 @port = null
                 @process = null

-            # TODO: Wait until the server is ready to accept connections
-            # before running the callback
+            # wait until it starts responding to TCP connections before connecting
+            tester = =>
+                if @process # don't keep trying to connect if the process crashes
+                    console.log "testing", @name
+                    testClient = net.createConnection @port, () ->
+                        testClient.end()
+                        console.log 'connection successful'
+                    testClient.on 'end', () ->
+                        console.log 'ready'
+                        readyCallback()
+                    testClient.on 'error', (e) ->
+                        console.log e
+                        if e.code in ['ECONNREFUSED']
+                            setTimeout tester, 1000
+            tester()

-            readyCallback()

     stop: =>
         # Kills the server process, and all its children (such as those spawned


Need to check for length

Need to check for length

--- src/quest.coffee
+++ src/quest.coffee
@@ -196,8 +196,7 @@
   # ```
   table: (sqlResult, {trimWhitespace}={}) ->
     trimWhitespace ?= true
-
-    if sqlResult.rows
+    if sqlResult.rows.length > 0
       columns = (field.name for field in sqlResult.fields)
       opts = head: columns
       table = new Table(opts)
@@ -213,6 +212,8 @@

       table.push.apply table, values
       console.log table.toString()
+    else
+      console.log "Nothing to output.".gray

   # Public: Run sql code in a string or file, fulfilling mustache templates.
   #


Get channel name via slack adapter.

Get channel name via slack adapter.

--- src/retro.coffee
+++ src/retro.coffee
@@ -49,7 +49,7 @@
     while start_date <= end_date
       day_list = null
       try
-        day_list = @robot.brain.data.retro[get_channel(msg)][start_date.getFullYear()][start_date.getMonth()][start_date.getDate()] || []
+        day_list = @robot.brain.data.retro[get_channel_id(msg)][start_date.getFullYear()][start_date.getMonth()][start_date.getDate()] || []
         # add any left over items from before we started recording the channel
         day_list = day_list.concat(@robot.brain.data.retro[start_date.getFullYear()][start_date.getMonth()][start_date.getDate()] || [])
       catch
@@ -74,8 +74,11 @@
 get_items_string = (items) ->
   "#{item.user}:#{item.message} #{if item.channel then '' else '(channel unknown)'}" for item in items

+get_channel_name = (response, robot) ->
+  channel_id = get_channel_id(response)
+  robot.adapter.client.rtm.dataStore.getChannelById(room).name

-get_channel = (response) ->
+get_channel_id = (response) ->
   if response.message.room == response.message.user.name
     "@#{response.message.room}"
   else
@@ -118,16 +121,18 @@
 handle_comment = (msg) ->
   type = msg.match[0]
   date = new Date()
+  channel_id = get_channel_id(msg)
+  channel_name = @robot.adapter.client.rtm.dataStore.getChannelById(get_channel_id(msg)).name

   @robot.brain.data.retro ||= {}
-  @robot.brain.data.retro[get_channel(msg)] ||= {}
-  @robot.brain.data.retro[get_channel(msg)][date.getFullYear()] ||= {}
-  @robot.brain.data.retro[get_channel(msg)][date.getFullYear()][date.getMonth()] ||= {}
-  @robot.brain.data.retro[get_channel(msg)][date.getFullYear()][date.getMonth()][date.getDate()] ||= []
-  @robot.brain.data.retro[get_channel(msg)][date.getFullYear()][date.getMonth()][date.getDate()].push
+  @robot.brain.data.retro[channel_id] ||= {}
+  @robot.brain.data.retro[channel_id][date.getFullYear()] ||= {}
+  @robot.brain.data.retro[channel_id][date.getFullYear()][date.getMonth()] ||= {}
+  @robot.brain.data.retro[channel_id][date.getFullYear()][date.getMonth()][date.getDate()] ||= []
+  @robot.brain.data.retro[channel_id][date.getFullYear()][date.getMonth()][date.getDate()].push
     user: msg.message.user.name
     type: msg.match[1]
     message: msg.match[2]
-    channel: get_channel(msg)
+    channel: channel_name

-  msg.send "Noted, #{msg.message.user.name} in #{get_channel(msg)}, I'll remember that for retro!"
+  msg.send "foo Noted, #{msg.message.user.name} in #{channel_name}, I'll remember that for retro!"


fix Scope.$getValue

fix Scope.$getValue

--- src/scope.coffee
+++ src/scope.coffee
@@ -62,9 +62,9 @@
     cd.setValue name, value
     return

-Scope::$getValue = (name, value) ->
+Scope::$getValue = (name) ->
     cd = @.$rootChangeDetector
-    cd.getValue name, value
+    cd.getValue name

 Scope::$eval = (exp) ->
     cd = @.$rootChangeDetector


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/score.coffee
+++ src/score.coffee
@@ -1,3 +1,12 @@
+###*
+Creates an oscillator function with the given parameters.
+
+@name Oscillator
+@constructor
+@param {Number} amplitude How much to scale the oscillator function value
+@param {Number} period How fast the osciallator function repeats
+@param {Number} offset How much to offset the created osciallator function. Useful for translating between sin and cosine functions.
+###s
 Score = (I={}, self) ->
   Object.reverseMerge I,
     score: 0


not needed

not needed

--- src/eddsa.iced
+++ src/eddsa.iced
@@ -1,124 +0,0 @@
-
-nacl_js = require 'tweetnacl/nacl-fast'
-nacl_c = null
-
-try
-  nacl_c = require('sodium').api
-catch e
-  # noop
-
-exports.publicKeyLength = nacl_js.publicKeyLength
-exports.secretKeyLength = nacl_js.secretKeyLength
-exports.b2u = b2u = (b) -> new Uint8Array(b)
-exports.u2b = u2b = (u) -> new Buffer u
-
-#---------------------------------------------------------
-# Constant-time buffer comparison
-#
-bufeq_secure = (x,y) ->
-  ret = if not x? and not y? then true
-  else if not x? or not y? then false
-  else if x.length isnt y.length then false
-  else
-    check = 0
-    for i in [0...x.length]
-      check |= (x.readUInt8(i) ^ y.readUInt8(i))
-    (check is 0)
-  return ret
-
-#================================================================
-
-exports.alloc = ({publicKey, secretKey, force_js}) ->
-  if force_js or not nacl_c? then new TweetNaCl { publicKey, secretKey }
-  else Sodium { publicKey, secretKey }
-
-#================================================================
-
-#
-# @class Base
-#
-# Base class for Sodium and TweetNaCl implementations.
-#
-class Base
-
-  #---------------
-  #
-  #
-  # @param {Buffer} publicKey The public key for this instance.
-  # @param {Buffer} secretKey The secret key for this instance.
-  #
-  constructor : ({@publicKey, @secretKey}) ->
-
-  #---------------
-  #
-  # @method genFromSeed
-  #
-  # Generate an EdDSA keypair from a deterministic seed.
-  #
-  # @param {Buffer} seed The seed
-  # @return {Object} Contained `publicKey`, `secretKey` buffers
-  #
-  #
-  genFromSeed : ({seed}) ->
-
-    # As of sodium@1.0.13, there is no wrapper for crypto_sign_seed_keypair,
-    # so use TweetNaCl's for all.
-    tmp = nalc_js.sign.keyPair.fromSeed b2u seed
-
-    # Note that the tweetnacl library deals with Uint8Arrays,
-    # and internally, we like node-style Buffers.
-    @secretKey = u2b tmp.secretKey
-    @publicKey = u2b tmp.publicKey
-
-    return { @secretKey, @publicKey }
-
-#================================================================
-
-#
-# @class TweetNaCl
-#
-#  A pure-JS implemenation using the TweetNaCl library.
-#
-class TweetNaCl extends Base
-
-  #
-  # verify
-  #
-  # Verify a signature, given a public key, the signature, and the payload
-  # (if it's not alread attached).
-  #
-  # @param {Bool} detached If this is a detached signature or not.
-  # @param {Buffer} payload The payload to verify. Optional, might be attached.
-  # @param {Buffer} sig The signature to verify.
-  # @param {Buffer} publicKey The public key to verify with.
-  # @return {List<Error,Buffer>} error on a failure, or nil on success. On sucess,
-  #   also return the payload from the buffer.
-  #
-  verify_js : ({payload, sig, detached, publicKey}) ->
-    # "Attached" signatures in NaCl are just a concatenation of the signature
-    # in front of the message.
-    err = null
-    if detached
-      payload = new Buffer [] if not payload?
-      if not nacl_js.sign.detached.verify b2u(payload), b2u(sig), b2u(publicKey)
-        err = new Error "signature didn't verify"
-    else if not (r_payload = nacl_js.sign.open b2u(sig), b2u(publicKey))?
-      err = new Error "signature didn't verify"
-    else if not (r_payload = u2b r_payload)?
-      err = new Error "failed to convert from a Uint8Array to a buffer"
-    else if payload? and not bufeq_secure(r_payload, payload)
-      err = new Error "got unexpected payload"
-    else
-      payload = r_payload
-    return [ err, payload ]
-
-  #----------------------------
-
-  verify_c = ({payload, sig, detached, publicKey}) ->
-    if detached and not payload?
-      err = new Error "in detached mode, you must supply a payload"
-      return [ err, null ]
-    msg = if detached then Buffer.concat [ sig, payload ] else sig
-  err = nalc_c.cyrpto_sign_open msg, publicKey
-
-#================================================================


Correctly @-mention users when using msg.reply

--- src/slack.coffee
+++ src/slack.coffee
@@ -246,7 +246,7 @@

     for msg in messages
       # TODO: Don't prefix username if replying in DM
-      @send envelope, "#{envelope.user.name}: #{msg}"
+      @send envelope, "<@#{envelope.user.id}>: #{msg}"

   topic: (envelope, strings...) ->
     channel = @client.getChannelGroupOrDMByName envelope.room


fix some words

fix some words

--- src/slack.coffee
+++ src/slack.coffee
@@ -79,8 +79,8 @@
       email_address: user.profile.email
       slack: {}
     for key, value of user
-      # user contains an of the SlackClient, which and contains references to the all the data types (users, channels) plus things like the token, s
-      # so, don't bother storing it
+      # don't store the SlackClient, because it'd cause a circular reference
+      # (it contains users and channels), and because it has sensitive information like the token
       continue if value instanceof SlackClient
       newUser.slack[key] = value


fix: NaNms duration (#376)

fix: NaNms duration (#376)

--- src/reporters/base-reporter.coffee
+++ src/reporters/base-reporter.coffee
@@ -27,6 +27,8 @@
     emitter.on 'test pass', (test) =>
       @stats.passes += 1
       test['end'] = new Date()
+      if typeof test['start'] is 'string'
+        test['start'] = new Date(test['start'])
       test['duration'] = test.end - test.start

     emitter.on 'test skip', (test) =>
@@ -35,11 +37,15 @@
     emitter.on 'test fail', (test) =>
       @stats.failures += 1
       test['end'] = new Date()
+      if typeof test['start'] is 'string'
+        test['start'] = new Date(test['start'])
       test['duration'] = test.end - test.start

     emitter.on 'test error', (error, test) =>
       @stats.errors += 1
       test['end'] = new Date()
+      if typeof test['start'] is 'string'
+        test['start'] = new Date(test['start'])
       test['duration'] = test.end - test.start


added support for running commands with a job.  need to work on data race for multiple servers listing jobs

--- src/jenkins-enhanced.coffee
+++ src/jenkins-enhanced.coffee
@@ -78,6 +78,12 @@
   hasInitialized: ->
     @_hasListed

+  getJobs: =>
+    @_rootFolder.getJobs()
+
+  hasJobs: =>
+    @getJobs().length > 0
+
   setFolder: (folder) =>
     @_hasListed = true
     @_rootFolder = folder
@@ -123,11 +129,15 @@
     @_hasListed = true
     @_jobs.push job if not @hasJobByName(job.name, false)

-  getJobs: =>
-    @_jobs
+  getJobs: (recursive=true) =>
+    res = @_jobs
+    if (recursive)
+      for subFolder in @getFolders(false)
+        res = res.concat(subFolder.getJobs())
+    return res

   hasJobs: =>
-    @_jobs.length > 0
+    @getJobs().length > 0

   getJobByName: (jobName, recursive=true) =>
     jobName = @_querystring.unescape(jobName).trim()
@@ -137,7 +147,7 @@
     if (recursive)
       for folder in @_folders
         job = folder.getJobByName(jobName)
-        return job if job.length > 0
+        return job if job and job.length > 0
     null

   hasJobByName: (jobName, recursive=true) =>
@@ -147,11 +157,15 @@
     @_hasListed = true
     @_folders.push folder if not @hasFolderByName(folder.name, false)

-  getFolders: =>
-    @_folders.sort((a,b) => true if (a.name > b.name))
+  getFolders: (recursive=true) =>
+    res = @_folders.sort((a,b) => true if (a.name > b.name))
+    if (recursive)
+      for subFolder in @getFolders(false)
+        res = res.concat(subFolder.getFolders())
+    return res

   hasFolders: =>
-    @_folders.length > 0
+    @getFolders().length > 0

   getFolderByName: (folderName, recursive=true) =>
     folderName = @_querystring.unescape(folderName).trim()
@@ -160,7 +174,7 @@
     if (recursive)
       for folder in @_folders
         folder = folder.getFolderByName(folderName)
-        return folder if folder.length > 0
+        return folder if folder and folder.length > 0
     null

   hasFolderByName: (folderName, recursive=true) =>
@@ -187,8 +201,7 @@
   getServerByJobName: (jobName) =>
     @send "ERROR: Make sure to run a 'list' to update the job cache" if not @serversHaveJobs()
     for server in @_servers
-      for folder in server.getFolders()
-        return server if folder.hasJobByName(jobName)
+      return server if server.getFolder().hasJobByName(jobName)
     null

   hasInitialized: =>
@@ -201,8 +214,7 @@

   serversHaveJobs: =>
     for server in @_servers
-      for folder in server.getFolders()
-        return true if folder.hasJobs()
+      return true if server.hasJobs()
     false

   servers: =>
@@ -281,11 +293,11 @@
   build: (buildWithEmptyParameters) =>
     return if not @_init(@build)
     job = @_getJob()
-    server = @_serverManager.getServerByJobName(job)
+    server = @_serverManager.getServerByJobName(job.name)
     command = if buildWithEmptyParameters then "buildWithParameters" else "build"
-    path = if @_params then "job/#{job}/buildWithParameters?#{@_params}" else "job/#{job}/#{command}"
+    path = if @_params then "#{job.path}/buildWithParameters?#{@_params}" else "#{job.path}/#{command}"
     if !server
-      @msg.send "I couldn't find any servers with a job called #{@_getJob()}.  Try `jenkins servers` to get a list."
+      @msg.send "I couldn't find any servers with a job called #{job.name}.  Try `jenkins servers` to get a list."
       return
     @_requestFactorySingle server, null, path, @_handleBuild, "post"

@@ -301,10 +313,9 @@
   describe: =>
     return if not @_init(@describe)
     job = @_getJob()
-    console.log(job)
-    server = @_serverManager.getServerByJobName(job)
+    server = @_serverManager.getServerByJobName(job.name)
     if !server
-      @msg.send "I couldn't find any servers with a job called #{@_getJob()}.  Try `jenkins servers` to get a list."
+      @msg.send "I couldn't find any servers with a job called #{job.name}.  Try `jenkins servers` to get a list."
       return
     @_requestFactorySingle server, null, "#{job.path}/api/json", @_handleDescribe

@@ -326,16 +337,15 @@
   last: =>
     return if not @_init(@last)
     job = @_getJob()
-    server = @_serverManager.getServerByJobName(job)
-    path = "job/#{job}/lastBuild/api/json"
+    server = @_serverManager.getServerByJobName(job.name)
+    path = "#{job.path}/lastBuild/api/json"
     if !server
-      @msg.send "I couldn't find any servers with a job called #{@_getJob()}.  Try `jenkins servers` to get a list."
+      @msg.send "I couldn't find any servers with a job called #{job.name}.  Try `jenkins servers` to get a list."
       return
     @_requestFactorySingle server, null, path, @_handleLast

   list: (isInit = false) =>
     @_requestsRunning = 0
-    @_jobList = []
     @_requestFactory "api/json", if isInit then @_handleListInit else @_handleList

   listAliases: =>
@@ -394,12 +404,12 @@
     for item in items
       itemType = item._class
       if (itemType == 'com.cloudbees.hudson.plugins.folder.Folder')
-        newFolder = new JenkinsFolder(item.name, folder.path+"/job/"+@_querystring.escape(item.name), folder.depth+1)
+        newFolder = new JenkinsFolder(item.name, "#{folder.path}/job/#{@_querystring.escape(item.name)}", folder.depth+1)
         folder.addFolder(newFolder)
         @_requestsRunning++
         @_requestFactorySingle server, newFolder, "#{newFolder.path}/api/json", @_handleNewFolder
       else
-        newJob = new JenkinsJob(item.name, folder.path+""+@_querystring.escape(item.name), if item.color == "red" then "FAIL" else "PASS")
+        newJob = new JenkinsJob(item.name, "#{folder.path}/job/#{@_querystring.escape(item.name)}", if item.color == "red" then "FAIL" else "PASS")
         folder.addJob(newJob)
     # count our current folder as processed
     @_requestsRunning--
@@ -410,14 +420,15 @@
     if isRoot
       response += "Server: #{server.url}\n"
     # go through each folder and list jobs first then other folders, sorted alphabetically for consistency
-    for job in folder.getJobs()
+    for job in folder.getJobs(false)
       @_jobList.push(job) if @_jobList.indexOf(job) == -1
       response += "\t".repeat(folder.depth+2)+"[#{@_jobList.indexOf(job) + 1}] #{job.state} #{job.name}\n"
-    for subFolder in folder.getFolders()
+    for subFolder in folder.getFolders(false)
       response += "\t".repeat(subFolder.depth+1)+"Folder: #{subFolder.name}\n" if subFolder.name != ""
       response += @_listJobs(server, subFolder)
     if isRoot and @_outputStatus
       @send response
+      @_initComplete() if @_serverManager.hasInitialized()
     else
       return response

@@ -461,13 +472,27 @@
     "HEALTH: #{result}\n"

   _getJob: (escape = false) =>
-    job = @msg.match[1].trim()
+    # check if its a job we already stored
+    if (typeof(@msg.match[1]) == "object")
+      return @msg.match[1]
+    # otherwise search for it with what the user gave us
+    jobName = @msg.match[1].trim()

     # if the provided name is an alias, provide it's mapped job name
     aliases = @_getSavedAliases()
-    job     = aliases[job] if aliases[job]
-
-    if escape then @_querystring.escape(job) else job
+    jobName     = aliases[jobName] if aliases[jobName]
+
+    jobName = if escape then @_querystring.escape(jobName) else jobName
+
+    jobs = []
+    # perform lookup
+    for server in @_serverManager.listServers()
+      job = server.getFolder().getJobByName(jobName)
+      if job and job.length > 0
+        jobs = jobs.concat(job)
+    if jobs.length > 1
+      @send "There are multiple jobs with that name, please use an id instead."
+    jobs[0] if jobs.length == 1

   # Switch the index with the job name
   _getJobById: =>
@@ -480,8 +505,8 @@

   _lastBuildStatus: (lastBuild) =>
     job = @_getJob()
-    server = @_serverManager.getServerByJobName(job)
-    path = "job/#{job}/#{lastBuild.number}/api/json"
+    server = @_serverManager.getServerByJobName(job.name)
+    path = "#{job.path}/#{lastBuild.number}/api/json"
     @_requestFactorySingle server, null, path, @_handleLastBuildStatus

   _requestFactorySingle: (server, folder, endpoint, callback, method = "get") =>
@@ -512,7 +537,7 @@
     try
       content = JSON.parse(body)
       @_addJobsToFoldersList content.jobs, server, folder
-      @_initComplete() if @_serverManager.hasInitialized()
+      #@_initComplete() if @_serverManager.hasInitialized()
     catch error
       @send error

@@ -589,7 +614,7 @@
       content = JSON.parse(body)
       @_outputStatus = print
       @_makeRootFolderForServer content.jobs, server
-      @_initComplete() if @_serverManager.hasInitialized()
+      #@_initComplete() if @_serverManager.hasInitialized()
     catch error
       @send error


Reuse instance variable.

Reuse instance variable.

--- src/spec/exportmapping.spec.coffee
+++ src/spec/exportmapping.spec.coffee
@@ -10,7 +10,7 @@

   describe '#constructor', ->
     it 'should initialize', ->
-      expect(-> new ExportMapping()).toBeDefined()
+      expect(@exportMapping).toBeDefined()

   describe '#mapPrices', ->
     it '', ->


Use Login.getTokenOrLogin helper

Use Login.getTokenOrLogin helper

--- src/stars.coffee
+++ src/stars.coffee
@@ -1,7 +1,6 @@
 _ = require 'underscore-plus'
 optimist = require 'optimist'

-auth = require './auth'
 Command = require './command'
 config = require './config'
 Install = require './install'
@@ -38,7 +37,7 @@
       @requestStarredPackages(requestSettings)
     else
       requestSettings.url = "#{config.getAtomApiUrl()}/stars"
-      @getToken (error, token) =>
+      Login.getTokenOrLogin (error, token) =>
         if token?
           requestSettings.headers = authorization: token
           @requestStarredPackages(requestSettings, callback)
@@ -57,13 +56,6 @@
       else
         message = body.message ? body.error ? body
         callback("Requesting packages failed: #{message}")
-
-  getToken: (callback) ->
-    auth.getToken (error, token) ->
-      if error?
-        new Login().run({callback, commandArgs: []})
-      else
-        callback(null, token)

   run: (options) ->
     {callback} = options


Adding d to certExpire

Adding d to certExpire

--- src/irc.coffee
+++ src/irc.coffee
@@ -106,7 +106,7 @@
       stripColors: true
       secure: options.usessl
       selfSigned: options.fakessl
-      certExpired: options.certExpire
+      certExpired: options.certExpired
       floodProtection: options.unflood

     client_options['channels'] = options.rooms unless options.nickpass


#27 IE doesn't like \ufff0, escape it also.

--- src/utils.coffee
+++ src/utils.coffee
@@ -90,7 +90,7 @@
     ee.addListener(event, new_handler)

-escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u2000-\u20ff]/g
+escapable = /[\x00-\x1f\ud800-\udfff\u2000-\u20ff\ufff0-\uffff]/g

 unroll_lookup = (escapable) ->
     unrolled = {}


Python parseDammit

Python parseDammit

--- src/languages/python.coffee
+++ src/languages/python.coffee
@@ -1,10 +1,13 @@
-Language = require './language'
+_ = window?._ ? self?._ ? global?._ ? require 'lodash'  # rely on lodash existing, since it busts CodeCombat to browserify it--TODO

 parser = require 'filbert'
+parser_loose = require 'filbert/filbert_loose'

 estraverse = require 'estraverse'
 escodegen = require 'escodegen'
 esprima = require 'esprima'
+
+Language = require './language'

 module.exports = class Python extends Language
   name: 'Python'
@@ -13,8 +16,17 @@
   runtimeGlobals:
     __pythonRuntime: parser.pythonRuntime

-  hasChangedSignificantly: (a, b) ->
-    true
+  hasChangedASTs: (a, b) ->
+    try
+      [aAST, bAST] = [null, null]
+      options = {locations: false, ranges: false}
+      aAST = parser_loose.parse_dammit a, options
+      bAST = parser_loose.parse_dammit b, options
+      unless aAST and bAST
+        return true
+      return not _.isEqual(aAST, bAST)
+    catch error
+      return true

   # Wrap the user code in a function. Store @wrappedCodePrefix and @wrappedCodeSuffix.
   wrap: (rawCode, aether) ->
@@ -30,10 +42,21 @@

   # Using a third-party parser, produce an AST in the standardized Mozilla format.
   parse: (code, aether) ->
-    ast = parser.parse code, {locations: true, ranges: true}
+    ast = parser.parse code, {locations: false, ranges: true}
+    selfToThis ast
+    ast

-    # 'this' is not a keyword in Python, so it does not parse to a ThisExpression
-    # Instead, we expect the variable 'self', and map it to a ThisExpression here.
-    ast.body[0].body.body.unshift {"type": "VariableDeclaration","declarations": [{ "type": "VariableDeclarator", "id": {"type": "Identifier", "name": "self" },"init": {"type": "ThisExpression"} }],"kind": "var"}
+  parseDammit: (code, aether) ->
+    try
+      ast = parser_loose.parse_dammit code, {locations: false, ranges: true}
+      selfToThis ast
+    catch error
+      ast = {type: "Program", body:[{"type": "EmptyStatement"}]}
+    ast

-    ast
+
+# 'this' is not a keyword in Python, so it does not parse to a ThisExpression
+# Instead, we expect the variable 'self', and map it to a ThisExpression
+selfToThis = (ast) ->
+  ast.body[0].body.body.unshift {"type": "VariableDeclaration","declarations": [{ "type": "VariableDeclarator", "id": {"type": "Identifier", "name": "self" },"init": {"type": "ThisExpression"} }],"kind": "var"}
+  ast


Extract `handleURL` from `app.locals`

Extract `handleURL` from `app.locals`

`app.locals` are only needed for views. In this case, a simple
function suffices.

--- app._coffee
+++ app._coffee
@@ -70,8 +70,8 @@

 ## ROUTES:

-# Helper for the two different routes for URLs
-app.locals.handleUrl = (res, url, _) ->
+# Helper for the two different routes for URLs:
+handleURL = (res, url, _) ->
   if not url?
       res.json 400, error:
         message: 'Please give us the full URL, including the "http://" or "https://".'
@@ -114,10 +114,10 @@

 # For compatibility with zoom.it
 app.get '/v1/content/:url?', (req, res, _) ->
-  app.locals.handleUrl res, req.query.url, _
+  handleURL res, req.query.url, _

 app.get /^\/https?:\/\/.+/, (req, res, _) ->
-  app.locals.handleUrl res, req.url[1..], _
+  handleURL res, req.url[1..], _

 app.get '/:id.:ext', (req, res, _) ->
   ext = req.params.ext


Make SP details marker use real service colors

Closes #99.

--- src/views.coffee
+++ src/views.coffee
@@ -334,8 +334,7 @@
             marker_canvas = @$el.find('#details-marker-canvas').get(0)
             context = marker_canvas.getContext('2d')
             size = 40
-            # TODO: get the color based on the service id.
-            color = 'rgb(0, 0, 0)'
+            color = app.color_matcher.unit_color(@model) or 'rgb(0, 0, 0)'
             id = 0
             rotation = 90


Check for a number in expires_in

Check for a number in expires_in

--- src/clientonlytoken.coffee
+++ src/clientonlytoken.coffee
@@ -1,5 +1,6 @@
 # ClientOnlyToken.coffee

+_ = require 'lodash'
 {DatabankObject} = require 'databank'
 web = require 'fuzzy.io-web'

@@ -84,8 +85,8 @@
               cot = new ClientOnlyToken({clientID: clientID})
             cot.token = results.access_token
             # XXX: expiration in seconds or milliseconds?
-            if !results.expires_in?
-              msg = "Expected results '#{body}' to include 'expires_in'"
+            if !_.isNumber results.expires_in
+              msg = "Expected results '#{body}' to include number 'expires_in'"
               callback new Error(msg)
             else
               expiresAt = new Date(Date.now() + (results.expires_in * 1000))


Don't require expiration on client tokens

--- src/clientonlytoken.coffee
+++ src/clientonlytoken.coffee
@@ -25,9 +25,6 @@
   if !props.token
     return callback new Error("No token")

-  if !props.expiresAt
-    return callback new Error("No expiresAt")
-
   props.createdAt = props.updatedAt = (new Date()).toISOString()

   callback null, props
@@ -47,6 +44,9 @@

   callback null

+ClientOnlyToken::expired = ->
+  !@expiresAt? or (Date.parse(@expiresAt) > (Date.now() + ONE_HOUR))
+
 ONE_HOUR = 1000 * 60 * 60

 ClientOnlyToken.ensure = (clientID, clientSecret, callback) ->
@@ -55,7 +55,7 @@
     if err && err.name != "NoSuchThingError"
       callback err
     else
-      if cot && Date.parse(cot.expiresAt) > (Date.now() + ONE_HOUR)
+      if cot && !cot.expired()
         callback null, cot.token
       else
         params =
@@ -85,17 +85,14 @@
               cot = new ClientOnlyToken({clientID: clientID})
             cot.token = results.access_token
             # XXX: expiration in seconds or milliseconds?
-            if !_.isNumber results.expires_in
-              msg = "Expected results '#{body}' to include number 'expires_in'"
-              callback new Error(msg)
-            else
+            if _.isNumber results.expires_in
               expiresAt = new Date(Date.now() + (results.expires_in * 1000))
               cot.expiresAt = expiresAt.toISOString()

-              cot.save (err) ->
-                if err
-                  callback err
-                else
-                  callback null, cot.token
+            cot.save (err) ->
+              if err
+                callback err
+              else
+                callback null, cot.token

 module.exports = ClientOnlyToken