Add acceptance test for the project-list and project-entities endpoints

--- services/web/test/acceptance/coffee/LinkedFilesTests.coffee
+++ services/web/test/acceptance/coffee/LinkedFilesTests.coffee
@@ -56,7 +56,44 @@
 						(error, doc_id) =>
 							@source_doc_id = doc_id
 							cb(error)
+				(cb) =>
+					@owner.createDocInProject @project_two_id,
+						@project_two_root_folder_id,
+						'some-harmless-doc.txt',
+						(error, doc_id) =>
+							cb(error)
 			], done
+
+		it 'should produce a list of the users projects', (done) ->
+			@owner.request.get {
+				url: "/user/projects",
+				json: true
+			}, (err, response, body) =>
+				expect(err).to.not.exist
+				expect(body).to.deep.equal {
+					projects: [
+						{ _id: @project_one_id, name: 'plf-test-one', accessLevel: 'owner' },
+						{ _id: @project_two_id, name: 'plf-test-two', accessLevel: 'owner' }
+					]
+				}
+				done()
+
+		it 'should produce a list of entities in the project', (done) ->
+			@owner.request.get {
+				url: "/project/#{@project_two_id}/entities",
+				json: true
+			}, (err, response, body) =>
+				expect(err).to.not.exist
+				expect(body).to.deep.equal {
+					project_id: @project_two_id,
+					entities: [
+						{ path: '/main.tex',              type: 'doc' },
+						{ path: '/some-harmless-doc.txt', type: 'doc' },
+						{ path: '/test.txt',              type: 'doc' }
+					]
+				}
+				done()
+

 		it 'should import a file from the source project', (done) ->
 			@owner.request.post {


Corrected KDHelp Typo

Corrected KDHelp Typo

--- bin/kdhelp.coffee
+++ bin/kdhelp.coffee
@@ -127,7 +127,7 @@
   vm        = hostname.split('.')[0]

   # Return the data object
-  username    : process.env.HOME
+  home        : process.env.HOME
   username    : username
   hostname    : hostname
   vm          : vm


Mocha 3 compat

Mocha 3 compat

--- spec/ComponentLoader.coffee
+++ spec/ComponentLoader.coffee
@@ -30,6 +30,7 @@
                     chai.expect(names).to.have.length 1
                     chai.expect(names[0]).to.equal compName
                     done()
+        return
     it 'should be a RemoteSubGraph', ->
         component = loader.load compName, (err, instance) ->
             chai.expect(err).to.equal null


:poop: Reset fs.write mock before test.

:poop: Reset fs.write mock before test.

--- spec/app/config-spec.coffee
+++ spec/app/config-spec.coffee
@@ -28,6 +28,7 @@
       config.set("x.y.z", 3)
       config.setDefaults("a.b", e: 4, f: 5)

+      fs.write.reset()
       config.save()

       writtenConfig = JSON.parse(fs.write.argsForCall[0][1])


Remove unused spec file.

Remove unused spec file.

--- spec/build-view-spec.coffee
+++ spec/build-view-spec.coffee
@@ -1,6 +0,0 @@
-BuildView = require '../lib/build-view'
-{WorkspaceView} = require 'atom'
-
-describe "BuildView", ->
-  it "has one valid test", ->
-    expect("life").toBe "easy"


Don't be chatty when getting factoids without an interrogative

--- lib/logic.coffee
+++ lib/logic.coffee
@@ -165,7 +165,7 @@
     return

   # Getting regular factoids
-  else if shouldReply and ((/^wh?at\s+(is|are)\s+/i).test(msg) or /\?+$/.test(msg))
+  else if shouldReply and (/^wh?at\s+(is|are)\s+/i).test(msg)
     key = msg.replace(/^wh?at\s+(is|are)\s+/i, '').replace(/\?+$/, '')
     return if key.toLowerCase() in IGNORED_FACTOIDS
     storage.getFactoid team, key, (err, current) ->
@@ -173,6 +173,15 @@
         reply oneOf I_DONT_KNOW
         return
       parseAndReply key, current
+    return
+
+  # Getting factoids without an interrogative requires addressing
+  else if shouldReply and /\?+$/.test(msg)
+    key = msg.replace(/\?+$/, '')
+    return if key.toLowerCase() in IGNORED_FACTOIDS
+    storage.getFactoid team, key, (err, current) ->
+      if current?
+        parseAndReply key, current
     return

   # Updating factoids
@@ -209,6 +218,7 @@
         reply oneOf "I already know that.", "I've already got it as that."

       else if current
+        current = current.replace(/^(is|are)\s+/i, '')
         reply "But #{key} #{verb} already #{current}"

       else


basic expression engine

basic expression engine

--- ink.coffee
+++ ink.coffee
@@ -4,13 +4,15 @@
 ansiRegex = require 'ansi-regex'
 supportsColor = require 'supports-color'

-SHADE_AMT = 0.1
+ADJUST_AMT = 0.02
+
+clamp = (v, l, u)-> Math.min u, Math.max l, v

 applyDomain = (val, lb, ub, tlb, tub)->
   Math.round ((val - lb) / (ub - lb)) * (tub - tlb)

 InkRender = (args...)->
-  console.log 'RENDER', @
+  console.log require('util').inspect @, colors: true, depth: null

 # thanks to Mohsen at http://stackoverflow.com/a/9493060/510036
 rgbToHsl = (r, g, b)->
@@ -58,6 +60,7 @@
       fg: {}
       bg: {}
     @side = 'fg'
+    @lastProp = null

   reset:-> @codes = {}

@@ -67,14 +70,15 @@
   fg:-> @side = 'fg'
   bg:-> @side = 'bg'

-  bright:-> # TODO
-  light:-> @codes[@side].mult =     2.0
-  dim:-> @codes[@side].mult =       1.0
-  lighter:->
-    @codes[@side].mult +=           SHADE_AMT
-    (amt)=>
-      --amt
-      @codes[@side].mult +=         amt * SHADE_AMT
+  by:-> (amt)->
+    # _lastcall is set by the wrappers
+    return if @_lastcall is 'by' or @_lastcall[0] is '_'
+    amt -= 1
+    @[@_lastcall] amt
+
+  lighter: (amt = 1)->
+    amt *= ADJUST_AMT
+    @_addLum amt

   black:-> @codes[@side].color =    [0,   0,   0  ]
   red:-> @codes[@side].color =      [127, 0,   0  ]
@@ -84,6 +88,11 @@
   magenta:-> @codes[@side].color =  [127, 0,   127]
   cyan:-> @codes[@side].color =     [0,   127, 127]
   white:-> @codes[@side].color =    [127, 127, 127]
+
+  _addLum: (lum)->
+    hsl = rgbToHsl.apply null, @codes[@side].color
+    hsl[2] = clamp hsl[2] + lum, 0, 1
+    @codes[@side].color = hslToRgb.apply null, hsl

 # ... and a teaspoon of Javascript black voodoo magic ...
 module.exports = {}
@@ -97,17 +106,26 @@
     get: -> fn.call thisArg
   return obj

-mkGetter = (ink, fn, getter)->->
-  fn.call ink
+mkGetter = (ink, name, fn, getter, bind = no)->->
+  if bind
+    res = fn.apply ink, arguments
+  else
+    res = fn.call ink
+  if res instanceof Function
+    res = mkGetter ink, name, res, getter, yes
+    return res
+  else
+    ink._lastcall = name
   return getter

 mkExport = (name, fn)->->
   ink = new Ink
   getter = InkRender.bind ink
-  for name, gfn of Ink.prototype
-    defineFnProp getter, name, mkGetter ink, gfn, getter
+  for name, gfn of Ink.prototype when name[0] isnt '_'
+    defineFnProp getter, name, mkGetter ink, name, gfn, getter
   fgbg = fn.call ink
+  ink._lastcall = name
   return getter

-for name, fn of Ink.prototype
+for name, fn of Ink.prototype when name[0] isnt '_'
   defineFnProp module.exports, name, mkExport name, fn


Start testing the brain.

Start testing the brain.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -106,18 +106,24 @@
                 points[username] += 5
     ), 60000

-    robot.respond /top (\d*)$/i, (msg) ->
+    # robot.respond /top (\d*)$/i, (msg) ->
+    #     if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+    #         pointcount = msg.match[1]
+    #
+    #         savednames = [robot.brain.get "people.username"]
+    #         savedpoints = [robot.brain.get "people.username.points"]
+    #
+    #         score = ["#{savednames} has #{savedpoints[savednames]}"]
+    #
+    #         topscore = score.sort((a, b) ->
+    #             b.savedpoints[savednames] - a.savedpoints[savednames]
+    #         ).slice(0, pointcount)
+    #
+    #         msg.send "The top #{pointcount} users with the most points are: #{topscore}"
+
+    # Testing brain.get functionality
+    robot.respond /allnames$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
-            pointcount = msg.match[1]

-            # Currently only seems to get results from in-chat users at time of command
-            savednames = [robot.brain.get "people.username"]
-            savedpoints = [robot.brain.get "people.username.points"]
-
-            score = ["#{savednames} has #{savedpoints[savednames]}"]
-
-            topscore = score.sort((a, b) ->
-                b.savedpoints[savednames] - a.savedpoints[savednames]
-            ).slice(0, pointcount)
-
-            msg.send "The top #{pointcount} users with the most points are: #{topscore}"
+            allnames = [robot.brain.get "people.username"]
+            msg.send "Stored users: #{allnames}"


keyboard shortcut for enter/exit (F)

keyboard shortcut for enter/exit (F)

--- game/assets/js/keyboard_shortcuts.coffee
+++ game/assets/js/keyboard_shortcuts.coffee
@@ -10,6 +10,7 @@
     when 83 then $('.movebutton[data-direction=s]').trigger 'click'
     when 88 then $('.movebutton[data-direction=s]').trigger 'click'
     when 67 then $('.movebutton[data-direction=se]').trigger 'click'
+    when 70 then $('.movebutton[data-direction=enterexit]').trigger 'click'
     when 80 #P
       if event.shiftKey
         $('[data-action-focus=paint]').focus()


myteam: remove duplicate value from resendInvitationModal

--- client/home/lib/myteam/components/hometeamsendinvites/resendinvitationconfirmmodal.coffee
+++ client/home/lib/myteam/components/hometeamsendinvites/resendinvitationconfirmmodal.coffee
@@ -32,6 +32,5 @@
                   planDetails :
                     cssClass  : 'content'
                     itemClass : kd.View
-                    partial   : options.partial

     super options, data


Check accounts length since its the data

--- client/landing/app/Applications/Chat.kdapplication/Views/conversationlistitemtitle.coffee
+++ client/landing/app/Applications/Chat.kdapplication/Views/conversationlistitemtitle.coffee
@@ -30,7 +30,7 @@
     @participants = switch @accounts.length
       when 1 then @getName 0
       when 2 then "#{@getName(0)} and #{@getName(1)}"
-      else "#{@getName(0)}, #{@getName(1)} and #{data.length - 2} more."
+      else "#{@getName(0)}, #{@getName(1)} and #{@accounts.length - 2} more."

     """


move method

move method

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -3,9 +3,9 @@
 #
 # Commands:
 #   hubot よろしくお願いします！ - hubot に挨拶しよう！
+#   hubot 後輩の {nickname} - hubot に面通ししよう！
 #   hubot 誰知ってるんですか? - hubot が知ってる人を教えてくれるよ！
 #   hubot {アダ名 or nickname} って誰ですか？ - 誰かよくわからない場合は hubot に聞いてみよう！
-#   hubot 後輩の {nickname} - hubot に面通ししよう！
 #   hubot {nickname} のアダ名何ですか?  - ものしりな hubot にアダ名を教えてもらおう！
 #   hubot {アダ名} は {nickname} のアダ名です  - hubot にアダ名を教えるよ！
 #   hubot {アダ名} は {nickname} のアダ名じゃないです  - hubot にアダ名が間違いだったことをを教えるよ！
@@ -112,6 +112,17 @@
     checkKeigo robot, msg
 # }}}

+  robot.respond /後輩の[ ]*([^ ]+)/, (msg) -># {{{
+    name = msg.match[1]
+    name = trimKeigo name
+    if existsUser robot, msg, name
+      msg.send "知ってるしw"
+      checkKeigo robot, msg
+      return
+    robot.brain.data.usersInfo[name] = {}
+    msg.send "#{name} か。よろしくな"
+# }}}
+
   robot.respond /誰知って/, (msg) -># {{{
     users = robot.brain.data.usersInfo ||= {}
     ret = []
@@ -136,17 +147,6 @@
     checkKeigo robot, msg
 # }}}

-  robot.respond /後輩の[ ]*([^ ]+)/, (msg) -># {{{
-    name = msg.match[1]
-    name = trimKeigo name
-    if existsUser robot, msg, name
-      msg.send "知ってるしw"
-      checkKeigo robot, msg
-      return
-    robot.brain.data.usersInfo[name] = {}
-    msg.send "#{name} か。よろしくな"
-# }}}
-
   robot.respond /([^ ]+)[ ]*のアダ名(何|なん|なに)/, (msg) -># {{{
     realname = msg.match[1]
     unless existsUser robot, msg, realname


Handle errors

Handle errors

--- scripts/themis.coffee
+++ scripts/themis.coffee
@@ -78,6 +78,8 @@
                 $form.ajaxSubmit
                     success: (responseText, textStatus, jqXHR) ->
                         console.log responseText
+                    error: (jqXHR, textStatus, errorThrown) ->
+                        console.log "#{textStatus}: #{errorThrown}"

     SigninPageView

@@ -92,6 +94,8 @@
                 $form.ajaxSubmit
                     success: (responseText, textStatus, jqXHR) ->
                         console.log responseText
+                    error: (jqXHR, textStatus, errorThrown) ->
+                        console.log "#{textStatus}: #{errorThrown}"

     SignupPageView


fix bug missing baseSenka

fix bug missing baseSenka

--- index.cjsx
+++ index.cjsx
@@ -234,7 +234,7 @@
             {__ 'Please click the stats to update rankings'}
           </Alert>
         {
-          {data, baseDetail, nickname, timeUp, accounted, isUpdated, exp} = @state
+          {data, baseDetail, nickname, timeUp, accounted, isUpdated, exp, baseSenka} = @state
           <div style={getStatusStyle timeUp}>
             <Detail data={data}
                     baseDetail={baseDetail}
@@ -253,6 +253,7 @@
                          exp={exp}
                          accounted={accounted}
                          timeUp={timeUp}
+                         baseSenka={baseSenka}
                          setPresumedSenka={@setPresumedSenka} />
             <RankList  baseDetail={baseDetail}
                        isUpdated={isUpdated}


fix getresult

fix getresult

--- index.cjsx
+++ index.cjsx
@@ -90,7 +90,8 @@
   sortieCnt = enemyCnt = 0
   sortieDmg = enemyDmg = 0.0
   sortieTot = enemyTot = 0.0
-  console.log sortieHp, enemyHp, combinedHp
+  console.log sortieHp.dmg, sortieHp.now
+  console.log enemyHp.dmg, sortieHp.now
   for i in [0..5]
     if sortieHp.now[i] + sortieHp.dmg[i] > 0
       sortieCnt += 1
@@ -116,19 +117,35 @@
         sortieDmg += (enemyHp.now[i] - leastHp)
         enemyDrop += 1
         enemyHp.now[i] = 0
-  console.log sortieHp, enemyHp, combinedHp
-  console.log enemyDmg / sortieTot, sortieDmg / enemyTot
-  result = 'D'
-  if enemyDrop == enemyCnt
-    result = 'S'
-  else if enemyDrop >= dropCount[enemyCnt]
-    result = 'A'
-  else if sortieDmg != 0 && (enemyHp.now[0] <= 0 || (sortieDmg * sortieTot >= 2.5 * enemyDmg * enemyTot) || (sortieDmg >= 0.01 * enemyTot))
-    result = 'B'
-  else if sortieDmg != 0 && (sortieDmg * sortieTot >= 1.0 * enemyDmg * enemyTot)
-    result = 'C'
-  console.log result
-  result
+  sortieRate = sortieDmg / enemyTot
+  enemyRate = enemyDmg / sortieTot
+  sortieRate = Math.floor(sortieRate * 100)
+  enemyRate = Math.floor(enemyRate * 100)
+  equalOrMore = sortieRate > 0.9 * enemyRate
+  resultB = sortieRate > 0 && sortieRate > 2.5 * enemyRate
+  console.log sortieHp.dmg, sortieHp.now
+  console.log enemyHp.dmg, enemyHp.now
+  console.log sortieRate, enemyRate
+  if sortieDrop == 0
+    if enemyDrop == enemyCnt
+      return 'S'
+    else if enemyDrop >= dropCount[enemyCnt]
+      return 'A'
+    else if enemyHp.now[0] <= 0 || resultB
+      return 'B'
+  else
+    if enemyDrop == enemyCnt
+      return 'S'
+    else if (enemyHp.now[0] <= 0 && sortieDrop < enemyDrop) || resultB
+      return 'B'
+    else if enemyHp.now[0] <= 0
+      return 'C'
+  if enemyRate > 0
+    if equalOrMore
+      return 'C'
+  if sortieDrop > 0 && (sortieCnt == sortieDrop + 1)
+    return 'E'
+  return 'D'

 checkRepair = (shipId) ->
   {_ships} = window
@@ -309,7 +326,6 @@
       raigekiAttack combinedHp, enemyHp, body.api_raigeki, combinedInfo
     else
       raigekiAttack sortieHp, enemyHp, body.api_raigeki, sortieInfo
-
   getResult sortieHp, enemyHp, combinedHp, leastHp

 escapeId = -1


make ModelProps#entityDic private

make ModelProps#entityDic private

--- spec/lib/model-props.coffee
+++ spec/lib/model-props.coffee
@@ -49,10 +49,6 @@
     it 'has models collecting MODELs (ValueObject and Entities, Collections)', ->
         expect(@modelProps.models).to.eql ['b', 'c', 'bs', 'cs']

-    it 'has entityDic collecting all entities', ->
-
-        expect(Object.keys @modelProps.entityDic).to.eql ['b']
-

     describe 'isEntity', ->


Remove debug-related logging

Remove debug-related logging

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -385,12 +385,9 @@

     it "preserves indentation", ->
       indentFixturesFolder = path.join(__dirname, "fixtures", "indents")
-      console.log(indentFixturesFolder)

       fixtures = for fixture in fs.readdirSync(indentFixturesFolder)
         fs.readFileSync(path.join(indentFixturesFolder, fixture), 'utf8')

-      console.log(fixtures)
-
       for fixture in fixtures
         expectPreservedIndentation fixture


Sigh, fix this

Sigh, fix this

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -62,7 +62,7 @@
     lookup: ['headers.x-forwarded-for']
     total: REQUEST_LIMIT
     expire: REQUEST_EXPIRE_MS
-    onRateLimited: -> (req, res, next)
+    onRateLimited: (req, res, next) ->
       console.log 'Rate limit exceeded for', req.headers['x-forwarded-for']
       next()


Pass MOBILE_URL to cors setup

Pass MOBILE_URL to cors setup

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -7,7 +7,7 @@
 {
   API_URL, APP_URL, NODE_ENV, ARTSY_ID, ARTSY_SECRET, SESSION_SECRET,
   SESSION_COOKIE_MAX_AGE, MICROGRAVITY_URL, DEFAULT_CACHE_TIME, COOKIE_DOMAIN, AUTO_GRAVITY_LOGIN,
-  SESSION_COOKIE_KEY, SENTRY_DSN, API_REQUEST_TIMEOUT, FUSION_URL, IP_BLACKLIST,
+  SESSION_COOKIE_KEY, SENTRY_DSN, API_REQUEST_TIMEOUT, FUSION_URL, IP_BLACKLIST, MOBILE_URL,
   LOGGER_FORMAT
 } = config = require "../config"
 { parse, format } = require 'url'
@@ -158,7 +158,7 @@
   # We want the user to be able to log-in to force via the microgravity subdomain
   # the initial use case being the professional buyer application
   # (this is specific to responsive pages that require log-in)
-  app.use cors origin: [APP_URL, MICROGRAVITY_URL]
+  app.use cors origin: [APP_URL, MOBILE_URL]

   # Mount apps


Add description and proper label to all the settings

--- lib/tablr.coffee
+++ lib/tablr.coffee
@@ -8,44 +8,70 @@
       type: 'object'
       properties:
         undefinedDisplay:
+          title: 'Undefined Value Display'
+          description: 'How to render undefined values in a cell. Leave the field blank to display an empty cell.'
           type: 'string'
           default: ''
         pageMoveRowAmount:
+          title: 'Page Move Row Amount'
+          description: 'The number of rows to jump when using the `core:page-up` and `core:page-down` commands.'
           type: 'integer'
           default: 20
         pageMoveColumnAmount:
+          title: 'Page Move Column Amount'
+          description: 'The number of columns to jump when using the `tablr:page-left` and `tablr:page-right` commands.'
           type: 'integer'
           default: 5
         scrollSpeedDuringDrag:
+          title: 'Scroll Speed During Drag'
+          description: 'The speed of the scrolling motion during a drag gesture, in pixels.'
           type: 'integer'
           default: 20
         scrollPastEnd:
+          title: 'Scroll Past End'
+          description: 'When enabled, the table can scroll past the end of the table both vertically and horizontally to let manipulate rows and columns more easily.'
           type: 'boolean'
           default: false

         rowHeight:
+          title: 'Row Height'
+          description: 'The default row height in pixels.'
           type: 'integer'
           default: 24
         rowOverdraw:
+          description: 'The number of rows to render outside the bounds of the visible area to smooth the scrolling motion.'
+          title: 'Row Overdraw'
           type: 'integer'
           default: 3
         minimumRowHeight:
+          title: 'Minimum Row Height'
+          description: 'The minimum height of a row in pixels.'
           type: 'integer'
           default: 16
         rowHeightIncrement:
+          title: 'Row Height Increment'
+          description: 'The amount of pixels to add or remove to a row when using the row resizing commands.'
           type: 'integer'
           default: 20

         columnWidth:
+          title: 'Column Width'
+          description: 'The default column width in pixels.'
           type: 'integer'
           default: 120
         columnOverdraw:
+          title: 'Column Overdraw'
+          description: 'The number of columns to render outside the bounds of the visible area to smooth the scrolling motion.'
           type: 'integer'
           default: 2
         minimumColumnWidth:
+          title: 'Minimum Column Width'
+          description: 'The minimum column width in pixels.'
           type: 'integer'
           default: 40
         columnWidthIncrement:
+          title: 'Column Width Increment'
+          description: 'The amount of pixels to add or remove to a column when using the column resizing commands.'
           type: 'integer'
           default: 20

@@ -53,18 +79,45 @@
       type: 'object'
       properties:
         flattenBufferMultiSelectionOnPaste:
+          title: 'Flatten Buffer Multi Selection On Paste'
           type: 'boolean'
           default: false
           description: 'If the clipboard content comes from a multiple selection copy in a text editor, the whole clipboard text will be pasted in each cell of the table selection.'
         distributeBufferMultiSelectionOnPaste:
+          title: 'Distribute Buffer Multi Selection On Paste'
           type: 'string'
           default: 'vertically'
           enum: ['horizontally', 'vertically']
           description: 'If the clipboard content comes from a multiple selection copy in a text editor, each selection will be considered as part of the same column (`vertically`) or of the same row (`horizontally`).'
         treatEachCellAsASelectionWhenPastingToABuffer:
+          title: 'Treat Each Cell As A Selection When Pasting To A Buffer'
           type: 'boolean'
           default: true
           description: 'When copying from a table to paste the content in a text editor this setting will make each cell appear as if they were created from different selections.'
+
+    csvEditor:
+      type: 'object'
+      properties:
+        columnDelimiter:
+          title: 'Default Column Delimiter'
+          type: 'string'
+          default: ','
+        rowDelimiter:
+          title: 'Default Row Delimiter'
+          type: 'string'
+          default: 'auto'
+        quotes:
+          title: 'Default Quote Character'
+          type: 'string'
+          default: '"'
+        escape:
+          title: 'Default Espace Character'
+          type: 'string'
+          default: '"'
+        comments:
+          title: 'Default Comment Character'
+          type: 'string'
+          default: '#'

     supportedCsvExtensions:
       type: 'array'
@@ -75,25 +128,6 @@
       default: 'alphabetic'
       enum: ['alphabetic', 'numeric', 'numericZeroBased']
       description: 'When file has no header, select the default naming method for the columns. `alphabetic` means use A, B,…, Z, AA, AB… `numeric` is for simple numbers, ie 1, 2… `numericZeroBased` is similar to `numeric`, except that it starts numbering from 0 instead of 1'
-
-    csvEditor:
-      type: 'object'
-      properties:
-        columnDelimiter:
-          type: 'string'
-          default: ','
-        rowDelimiter:
-          type: 'string'
-          default: 'auto'
-        quotes:
-          type: 'string'
-          default: '"'
-        escape:
-          type: 'string'
-          default: '"'
-        comments:
-          type: 'string'
-          default: '#'

   activate: ({csvConfig}) ->


Clean models.coffee

Clean models.coffee

--- admin/static/coffee/models.coffee
+++ admin/static/coffee/models.coffee
@@ -78,474 +78,6 @@

 class Log extends Backbone.Model

-class Namespace extends Backbone.Model
-    initialize: ->
-        # Add a computed shards property for convenience and metadata
-        @compute_shards()
-    compute_shards: =>
-        @.set 'computed_shards', new DataUtils.Shards [],@
-
-    regex:
-        string: /^S/
-        number: /^N.*%23(\-?[0-9]*(\.)?[0-9]*$)/
-        date: /^(PTIME).*%23\-?([0-9]*(\.)?[0-9]*$)/
-    transform_key: (a) ->
-        if a is null
-            a_new = a
-        else if a is ""
-            a_new = -Infinity
-        else if typeof a is "string"
-            if @regex.string.test(a) is true
-                a_new = a.slice(1)
-            else if @regex.number.test(a) is true
-                s =
-                    a.slice(a.indexOf("%23")+3)
-                if _.isNaN(parseFloat(s)) is false
-                    a_new = parseFloat(s)
-            else if @regex.date.test(a) is true
-                a_new = new Date(parseFloat(a.slice(a.indexOf("%23")+3)))
-        return a_new
-
-
-    compare_keys: (a, b) =>
-        #return 1 if a > b
-        a_new = @transform_key(a)
-        b_new = @transform_key(b)
-
-        if typeof a_new is 'number' and typeof b_new is 'number'
-            return a_new-b_new
-        else if (typeof a_new is 'string' and typeof b_new is 'string') or (a_new instanceof Date and b_new instanceof Date)
-            if a_new > b_new
-                return 1
-            else if a_new < b_new
-                return -1
-            else
-                return 0
-        else if typeof a_new isnt typeof b_new
-            if typeof a_new is 'number' and typeof b_new is 'string'
-                return -1
-            else if typeof a_new is 'number' and b_new instanceof Date
-                return -1
-            else if a_new instanceof Date and typeof b_new is 'string'
-                return -1
-            else if typeof a_new is 'string' and typeof b_new is 'number'
-                return 1
-            else if a_new instanceof Date and typeof b_new is 'number'
-                return 1
-            else if typeof a_new is 'string' and b_new instanceof Date
-                return 1
-
-            else if a_new is null and b_new isnt null
-                return 1
-            else if b_new is null and a_new isnt null
-                return -1
-        return 0
-
-
-    load_key_distr: =>
-        $.ajax
-            processData: false
-            url: "ajax/distribution?namespace=#{@get('id')}&depth=2"
-            type: 'GET'
-            contentType: 'application/json'
-            success: (distr_data) =>
-                # Cache the data
-                # Sort the keys and cache that too
-                distr_keys = []
-                for key, count of distr_data
-                    distr_keys.push(key)
-                distr_keys.sort(@compare_keys)
-
-                @set('key_distr_sorted', distr_keys)
-                @set('key_distr', distr_data)
-                @timeout = setTimeout @load_key_distr, 5000
-            error: =>
-                if namespaces.get(@get('id'))?
-                    @timeout = setTimeout @load_key_distr, 1000
-
-    clear_timeout: =>
-        if @timeout?
-            clearTimeout @timeout
-
-    # Some shard helpers
-    compute_shard_rows_approximation: (shard) =>
-        # first see if we can grab the distr data
-        shard = $.parseJSON(shard)
-        if not @get('key_distr')? or not @get('key_distr_sorted')?
-            return null
-
-        # some basic initialization
-        start_key = shard[0]
-        end_key = shard[1]
-
-        # TODO: we should interpolate once we will have decided how to order different type of keys
-
-        # find the first key greater than the beginning of our shard
-        # and keep summing until we get past our shard boundary.
-        count = 0
-
-        for key in @get('key_distr_sorted')
-            # TODO Might be unsafe when comparing string and integers. Need to be checked when the back end will have decided what to do.
-            if @compare_keys(key, start_key) >= 0 or start_key is ''
-                if @compare_keys(key, end_key) < 0 or end_key is null
-                    if @get('key_distr')[key]?
-                        count += @get('key_distr')[key]
-
-        return count.toString() # Return string since [] == 0 return true (for Handlebars)
-
-    # Is x between the lower and upper splitpoints (the open interval) for the given index?
-    splitpoint_between: (shard_index, sp) =>
-        all_sps = @.get('splitpoints')
-        return (shard_index == 0 || all_sps[shard_index - 1] < sp) && (shard_index == all_sps.length || sp < all_sps[shard_index])
-
-    # Computing btree stats based on machine stats
-    get_stats: =>
-        __s =
-            keys_read: 0
-            keys_set: 0
-        _.each DataUtils.get_namespace_machines(@get('id')), (mid) =>
-            _m = machines.get(mid)
-            if _m?
-                _s = _m.get_stats()[@get('id')]
-            if _s?.serializers?
-                keys_read = 0
-                keys_set = 0
-                for serializer_id of _s.serializers
-                    serializer = _s.serializers[serializer_id]
-                    if serializer['btree-primary']?
-                        keys_read = parseFloat(serializer['btree-primary'].keys_read)
-                        keys_set = parseFloat(serializer['btree-primary'].keys_set)
-                        if not isNaN(keys_read)
-                            __s.keys_read += keys_read
-                        if not isNaN(keys_set)
-                            __s.keys_set += keys_set
-        return __s
-
-    get_stats_for_performance: =>
-        # Ops/sec stats
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: 0
-        _s = this.get_stats()
-        if _s?
-            __s.keys_read += _s.keys_read
-            __s.keys_set += _s.keys_set
-        # CPU, mem, disk
-        num_machines_in_namespace = 0
-        for machine in machines.models
-            if machine.get('stats')? and @get('id') of machine.get('stats') and machine.is_reachable
-                num_machines_in_namespace++
-                __s.global_disk_space += machine.get_used_disk_space()
-        return __s
-
-    get_durability: =>
-        if @get('hard_durability')?
-            return @get('hard_durability')
-        else
-            for dc, ack of @get('ack_expectations')
-                @set('hard_durability', ack.hard_durability)
-                return @get('hard_durability')
-            @set('hard_durability', true)
-            return @get('hard_durability')
-
-class Datacenter extends Backbone.Model
-    # Compute the number of machines not used by other datacenters for one namespace
-    compute_num_machines_not_used_by_other_datacenters: (namespace) =>
-        max_machines = machines.length
-        for datacenter_id of namespace.get('replica_affinities')
-            # The second condition is to make sure that the datacenter does exist (and was not deleted)
-            if datacenter_id isnt @get('id') and datacenters.get(datacenter_id)?
-                max_machines -= namespace.get('replica_affinities')[datacenter_id]
-        if namespace.get('primary_uuid') isnt @get('id')
-            max_machines -= 1
-
-        return max_machines
-
-
-    get_machines: =>
-        machines.filter (machine) => machine.get('datacenter_uuid') is @get('id')
-    get_stats: =>
-        stats =
-            dc_disk_space: 0
-        for machine in machines.models
-            if machine.get('datacenter_uuid') is @get('id')
-                stats.dc_disk_space += machine.get_used_disk_space()
-        return stats
-
-    get_stats_for_performance: =>
-        # Ops/sec stats
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: 0
-        for namespace in namespaces.models
-            if not namespace.get('blueprint')? or not namespace.get('blueprint').peers_roles?
-                continue
-            for machine_id of namespace.get('blueprint').peers_roles
-                machine = machines.get(machine_id)
-                if not machine?
-                    continue
-                has_data = false
-                if machine.get('datacenter_uuid') is @get('id') and namespace.get('blueprint').peers_roles[machine_id]?
-                    for shard_key of namespace.get('blueprint').peers_roles[machine_id]
-                        if namespace.get('blueprint').peers_roles[machine_id][shard_key] isnt 'role_nothing'
-                            has_data = true
-                            break
-                    if has_data is true
-                        break
-            if has_data is true
-                _s = namespace.get_stats()
-                if not _s?
-                    continue
-                __s.keys_read += _s.keys_read
-                __s.keys_set += _s.keys_set
-
-        # CPU, mem, disk
-        num_machines_in_datacenter = 0
-        for machine in machines.models
-            if machine.get('datacenter_uuid') is @get('id') and machine.is_reachable
-                num_machines_in_datacenter++
-                __s.global_disk_space += machine.get_used_disk_space()
-        return __s
-
-
-class Machine extends Backbone.Model
-    get_stats: =>
-        stats = @get('stats')
-        if not stats?
-            stats = {}
-        if not stats.proc?
-            stats.proc = {}
-        return stats
-
-    get_used_disk_space: =>
-        machine_disk_space = 0
-        for nid, value of @get_stats()
-            # Check if nid is actually a namespace id
-            if namespaces.get(nid)?
-                stats = value.cache
-                if stats?
-                    machine_disk_space += parseInt(stats.block_size) * parseInt(stats.blocks_total)
-        return machine_disk_space
-
-    get_stats_for_performance: =>
-        stats_full = @get_stats()
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: parseInt(@get_used_disk_space())
-
-        for namespace in namespaces.models
-            _s = namespace.get_stats()
-            if not _s?
-                continue
-
-            if namespace.get('id') of stats_full
-                __s.keys_read += _s.keys_read
-                __s.keys_set += _s.keys_set
-        # CPU, mem, disk
-        return __s
-
-    is_reachable: =>
-        reachable = directory.get(@get('id'))?
-        return reachable
-
-    # Returns a filtered list of namespaces whose shards use this server as master
-    is_master_for_namespaces: =>
-        namespaces.filter (namespace) =>
-            for machine_uuid, peer_roles of namespace.get('blueprint').peers_roles
-                if machine_uuid is @get('id')
-                    for shard, role of peer_roles
-                        if role is 'role_primary'
-                            return true
-
-    # Return an object with warnings if a server cannot be moved
-    get_data_for_moving: =>
-        data = {}
-        dc_uuid = @get('datacenter_uuid')
-        # If the server is in Universe, we can move it since it's always safe
-        if dc_uuid isnt universe_datacenter.get('id')
-            # Count the number of servers in the same datacenter as this server
-            num_machines_in_dc = 0
-            machines.each (machine) => num_machines_in_dc++ if dc_uuid is machine.get('datacenter_uuid')
-
-            # Looking for critical issues (long term loss of availability)
-            # (look for namespaces where the datacenter is primary and with just one machine)
-            namespaces_with_critical_issue = []
-            # Find the tables that won't have sufficient replicas if we unassign this server
-            namespaces_with_unsatisfiable_goals = []
-            for namespace in namespaces.models
-                # Does the datacenter of this server have responsibilities for the namespace?
-                if dc_uuid of namespace.get('replica_affinities')
-                    num_replicas = namespace.get('replica_affinities')[dc_uuid]
-                    # If the datacenter acts as primary for the namespace, bump the replica count by one
-                    num_replicas++ if namespace.get('primary_uuid') is dc_uuid
-
-                # There will be a unsatisfiable goals if we unassign the machine
-                # We take for granted that acks < num_replicas. We might want to increase the safety.
-                if num_machines_in_dc <= num_replicas
-                    namespaces_with_unsatisfiable_goals.push
-                        id: namespace.get('id')
-                        name: namespace.get('name')
-
-                # The last machine in the datacenter is primary so no new master can be elected if the master is moved
-                if num_machines_in_dc is 1 and dc_uuid is namespace.get('primary_uuid')
-                    namespaces_with_critical_issue.push
-                        id: namespace.get('id')
-                        name: namespace.get('name')
-
-
-                # That's all, if the machine that's going to replace universe is already working for universe,
-                # it will start working for the datacenter and the unassigned one will start working for universe
-            if namespaces_with_unsatisfiable_goals.length > 0
-                data = _.extend data,
-                    has_warning: true
-                    namespaces_with_unsatisfiable_goals: namespaces_with_unsatisfiable_goals
-                    num_namespaces_with_unsatisfiable_goals: namespaces_with_unsatisfiable_goals.length
-            if namespaces_with_critical_issue.length > 0
-                data = _.extend data,
-                    namespaces_with_critical_issue: namespaces_with_critical_issue
-                    num_namespaces_with_critical_issue: namespaces_with_critical_issue.length
-
-            if namespaces_with_unsatisfiable_goals.length > 0 or namespaces_with_critical_issue.length > 0
-                data = _.extend data,
-                    has_warning: true
-                    datacenter_id: @get('datacenter_uuid')
-                    datacenter_name: datacenters.get(@get('datacenter_uuid')).get('name')
-                    machine_id: @get('id')
-                    machine_name: @get('name')
-        return data
-
-class LogEntry extends Backbone.Model
-    get_iso_8601_timestamp: => new Date(@.get('timestamp') * 1000)
-    get_formatted_message: =>
-        msg = @.get('message')
-
-
-class IssueRedundancy extends Backbone.Model
-
-class Progress extends Backbone.Model
-
-# this is a hook into the directory
-class MachineAttributes extends Backbone.Model
-
-class ConnectionStatus extends Backbone.Model
-
-# This is a computed model for the cluster (mainly for stats right
-# now)
-class ComputedCluster extends Backbone.Model
-    get_stats: =>
-        # Ops/sec stats
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: 0
-        for namespace in namespaces.models
-            _s = namespace.get_stats()
-            if not _s?
-                continue
-            __s.keys_read += _s.keys_read
-            __s.keys_set += _s.keys_set
-        # CPU, mem, disk
-        for m in machines.models
-            __s.global_disk_space += m.get_used_disk_space()
-
-        return __s
-
-
-
-
-# Collections for Backbone.js
-
-class Namespaces extends Backbone.Collection
-    model: Namespace
-    name: 'Namespaces'
-
-class Datacenters extends Backbone.Collection
-    model: Datacenter
-    name: 'Datacenters'
-
-class Machines extends Backbone.Collection
-    model: Machine
-    name: 'Machines'
-
-
-# We compare the directory and the blueprints to detect redundancy problems
-class IssuesRedundancy extends Backbone.Collection
-    model: IssueRedundancy
-    num_replicas : 0
-    initialize: ->
-        directory.on 'all', @compute_redundancy_errors
-        namespaces.on 'all', @compute_redundancy_errors
-
-
-
-    convert_activity: (role) ->
-        switch role
-            when 'role_secondary' then return 'secondary_up_to_date'
-            when 'role_nothing' then return 'nothing'
-            when 'role_primary' then return 'primary'
-
-    compute_redundancy_errors: =>
-        issues_redundancy_new = []
-
-        num_replicas = 0
-
-
-        directory_by_namespaces = DataUtils.get_directory_activities_by_namespaces()
-        for namespace in namespaces.models
-            namespace_id = namespace.get('id')
-            blueprint = namespace.get('blueprint').peers_roles
-            for machine_id of blueprint
-                if machines.get(machine_id)? and machines.get(machine_id).get('name')?
-                    machine_name = machines.get(machine_id).get('name')
-                else # can happen if machines is not loaded yet
-                    machine_name = machine_id
-
-                for key of blueprint[machine_id]
-                    value = blueprint[machine_id][key]
-                    if value is "role_primary" or value is "role_secondary"
-                        num_replicas++
-
-                        if !(directory_by_namespaces?) or !(directory_by_namespaces[namespace_id]?) or !(directory_by_namespaces[namespace_id][machine_id]?)
-                            issue_redundancy_param =
-                                machine_id: machine_id
-                                machine_name: machine_name
-                                namespace_uuid: namespace_id
-                                namespace_name: namespace.get('name')
-                                shard: key
-                                blueprint: value
-                                directory: 'not_found'
-                            issue_redundancy = new IssueRedundancy issue_redundancy_param
-                            issues_redundancy_new.push issue_redundancy
-                        else if directory_by_namespaces[namespace_id][machine_id][key] != @convert_activity(value)
-                            issue_redundancy_param =
-                                machine_id: machine_id
-                                machine_name: machine_name
-                                namespace_uuid: namespace_id
-                                namespace_name: namespace.get('name')
-                                shard: key
-                                blueprint: value
-                                directory: directory_by_namespaces[namespace_id][machine_id][key]
-                            issues_redundancy_new.push new IssueRedundancy issue_redundancy_param
-
-
-        if issues_redundancy_new.length > 0 or issues_redundancy_new.length isnt @.length
-            @.reset(issues_redundancy_new)
-
-        if num_replicas isnt @num_replicas
-            @num_replicas = num_replicas
-            @.trigger 'reset'
-
-class ProgressList extends Backbone.Collection
-    model: Progress
-    url: 'ajax/progress'
-
-# hook into directory
-class Directory extends Backbone.Collection
-    model: MachineAttributes
-    url: 'ajax/directory'

 # This module contains utility functions that compute and massage
 # commonly used data.
@@ -555,525 +87,12 @@
     # convenience, it's useful to pick data from several of these models: these
     # are computed models (and computed collections).

-    # Computed shard for a particular namespace: computed for convenience and for extra metadata
-    # Arguments:
-    #   shard: element of the namespace shards list: Namespace.get('shards')
-    #   namespace: namespace that this shard belongs to
-    # Computed attributes of a shard:
-    #   shard_boundaries: JSON string representing the shard boundaries
-    #   primary_uuid: machine uuid that is the master for this shard
-    #   secondary_uuids: list of machine_uuids that are the secondaries for this shard
-    class @Shard extends Backbone.Model
-        get_primary_uuid: => DataUtils.get_shard_primary_uuid @namespace.get('id'), @shard
-        get_secondary_uuids: => DataUtils.get_shard_secondary_uuids @namespace.get('id'), @shard
-
-        initialize: (shard, namespace) =>
-            @shard = shard
-            @namespace = namespace
-
-            @.set 'shard_boundaries', @shard
-            @.set 'primary_uuid', @get_primary_uuid()
-            @.set 'secondary_uuids',  @get_secondary_uuids()
-
-            namespace.on 'change:blueprint', @set_uuids
-
-        set_uuids: =>
-            new_primary_uuid = @get_primary_uuid()
-            new_secondary_uuids = @get_secondary_uuids()
-            @.set 'primary_uuid', new_primary_uuid if @.get('primary_uuid') isnt new_primary_uuid
-            @.set 'secondary_uuids', new_secondary_uuids if not _.isEqual @.get('secondary_uuids'), new_secondary_uuids
-
-        destroy: =>
-            namespace.on 'change:blueprint', @set_uuids
-
-    # The Shards collection maintains the computed shards for a given namespace.
-    # It will observe for any changes in the sharding scheme (or if the
-    # namespace has just been added) and maintains the list of computed shards,
-    # rebuilding if necessary.
-    class @Shards extends Backbone.Collection
-        model: DataUtils.Shard
-
-        initialize: (models, namespace) =>
-            @namespace = namespace
-            @namespace.on 'change:shards', @compute_shards_without_args
-            @namespace.on 'add', @compute_shards_without_args
-
-        compute_shards_without_args: =>
-            @compute_shards @namespace.get('shards')
-
-        compute_shards: (shards) =>
-            new_shards = []
-            for shard in shards
-                new_shards.push new DataUtils.Shard shard, @namespace
-            @.reset new_shards
-
-        destroy: =>
-            @namespace.off 'change:shards', @compute_shards_without_args
-            @namespace.off 'add', @compute_shards_without_args
-
-
     @stripslashes = (str) ->
         str=str.replace(/\\'/g,'\'')
         str=str.replace(/\\"/g,'"')
         str=str.replace(/\\0/g,"\x00")
         str=str.replace(/\\\\/g,'\\')
         return str
-
-    @get_machine_reachability = (machine_uuid) ->
-        reachable = directory.get(machine_uuid)?
-        if not reachable
-            _m = machines.get(machine_uuid)
-            if _m?
-                last_seen = _m.get('last_seen')
-            else
-                last_seen = null
-            if last_seen
-                last_seen = $.timeago(new Date(parseInt(last_seen) * 1000))
-        json =
-            reachable: reachable
-            last_seen: last_seen
-        return json
-
-    @get_datacenter_reachability = (datacenter_uuid) ->
-        total = (_.filter machines.models, (m) => m.get('datacenter_uuid') is datacenter_uuid).length
-        reachable = (_.filter directory.models, (m) =>
-            _m = machines.get(m.get('id'))
-            if _m?
-                _m.get('datacenter_uuid') is datacenter_uuid
-            else
-                return false
-            ).length
-
-        if reachable == 0 and total > 0
-            for machine in machines.models
-                if machine.get('datacenter_uuid') is datacenter_uuid
-                    _last_seen = machine.get('last_seen')
-                    if last_seen
-                        if _last_seen > last_seen
-                            last_seen = _last_seen
-                    else
-                        last_seen = _last_seen
-            last_seen = $.timeago(new Date(parseInt(last_seen) * 1000))
-
-        json =
-            total: total
-            reachable: reachable
-            last_seen: last_seen
-
-        return json
-
-    @get_shard_primary_uuid = (namespace_uuid, shard) ->
-        for machine_uuid, peers_roles of namespaces.get(namespace_uuid).get('blueprint').peers_roles
-            for _shard, role of peers_roles
-                if shard.toString() is _shard.toString() and role is 'role_primary'
-                    return machine_uuid
-        return null
-
-    @get_shard_secondary_uuids = (namespace_uuid, shard) ->
-        # We're organizing secondaries per datacenter
-        secondaries = {}
-        for machine_uuid, peers_roles of namespaces.get(namespace_uuid).get('blueprint').peers_roles
-            if !machines.get(machine_uuid)? # In case the machine is dead
-                continue
-
-            datacenter_uuid = machines.get(machine_uuid).get('datacenter_uuid')
-            for _shard, role of peers_roles
-                if shard.toString() is _shard.toString() and role is 'role_secondary'
-                    if not secondaries[datacenter_uuid]?
-                        secondaries[datacenter_uuid] = []
-                    secondaries[datacenter_uuid][secondaries[datacenter_uuid].length] = machine_uuid
-        return secondaries
-
-    @get_ack_expectations = (namespace_uuid, datacenter_uuid) ->
-        namespace = namespaces.get(namespace_uuid)
-        acks = namespace?.get('ack_expectations')?[datacenter_uuid]?.expectation
-        if acks?
-            return acks
-        else
-            return 0
-
-    @get_replica_affinities = (namespace_uuid, datacenter_uuid) ->
-        namespace = namespaces.get(namespace_uuid)
-        if datacenter_uuid is universe_datacenter.get('id')
-            datacenter = universe_datacenter
-        else
-            datacenter = datacenters.get(datacenter_uuid)
-        affs = namespace?.get('replica_affinities')?[datacenter?.get('id')]
-        if affs?
-            return affs
-        else
-            return 0
-
-    @get_datacenter_machines = (datacenter_uuid) ->
-        return _.filter(machines.models, (m) -> m.get('datacenter_uuid') is datacenter_uuid)
-
-    # Return a list of machines relevant to a namespace
-    @get_namespace_machines = (namespace_uuid) ->
-        mids = []
-        _n = namespaces.get(namespace_uuid)
-        if not _n
-            return []
-        _n = _n.get('blueprint').peers_roles
-        for mid, roles of _n
-            mids.push(mid)
-        return mids
-
-
-    # Cache the value of the refactored directory_activities
-    @directory_activities = null
-    @directory_activities_by_namespaces = null
-    @reset_directory_activities = ->
-        @directory_activities = null
-        @directory_activities_by_namespaces = null
-
-    # Organizes the directory as a map of activity ids
-    @get_directory_activities = ->
-        if not @directory_activities?
-            @directory_activities = {}
-            for machine in directory.models
-                bcards = machine.get('rdb_namespaces')['reactor_bcards']
-                for namespace_id, activity_map of bcards
-                    activity_map = activity_map['activity_map']
-                    for activity_id, activity of activity_map
-                        @directory_activities[activity_id] =
-                            value: activity
-                            machine_id: machine.get('id')
-                            namespace_id: namespace_id
-        return @directory_activities
-
-
-    @get_directory_activities_by_namespaces = ->
-        #TODO We report the status of the "last" internal hash shard as being the status of the whole shard
-        #We should eventually report the status of all internal shards
-        if not @directory_activities_by_namespaces?
-            @directory_activities_by_namespaces = {}
-            for machine in directory.models
-                bcards = machine.get('rdb_namespaces')['reactor_bcards']
-                for namespace_id, activity_map of bcards
-                    if not @directory_activities_by_namespaces[namespace_id]?
-                        @directory_activities_by_namespaces[namespace_id] = {}
-
-                    activity_map = activity_map['activity_map']
-                    for activity_id, activity of activity_map
-                        if not @directory_activities_by_namespaces[namespace_id][machine.get('id')]?
-                            @directory_activities_by_namespaces[namespace_id][machine.get('id')] = {}
-                        @directory_activities_by_namespaces[namespace_id][machine.get('id')][activity[0]] = activity[1]['type']
-
-        return @directory_activities_by_namespaces
-
-    # Computes backfill progress for a given (namespace, shard,
-    # machine) tripple. All arguments correspond to the objects that
-    # are *receiving* data.
-    @get_backfill_progress = (namespace_uuid, shard, machine_uuid) ->
-        activity_map = @get_directory_activities()
-        _output_json = []
-        if typeof(shard) isnt 'string'
-            shard = JSON.stringify(shard)
-
-        # grab the right machine from the progress list
-        progress = progress_list.get(machine_uuid)
-        if not progress?
-            return null
-
-        # next level of organization is by namespaces, grab the
-        # one we need
-        progress = progress.get(namespace_uuid)
-        if not progress?
-            return null
-
-        # The level of organization after that is by activity
-        # ids. Each activity corresponds to the shard we're
-        # backfilling into.
-        for activity_uuid, shard_map of progress
-            # Grab the activity from the activity map and break it up
-            # into some useful information.
-            activity = activity_map[activity_uuid]
-            if not activity?
-                # This probably means directory data hasn't caught up
-                # with the progress data on the client yet.
-                return null
-            activity_type = activity.value[1].type
-            into_shard = activity.value[0]
-
-            # Check that we're on the right shard (our C++ and JS JSON
-            # stringifiers have different policies with respect to
-            # spaces, so removing spaces here. We really need a
-            # standard way to compare shards - TODO here and in many
-            # many other places)
-            if into_shard.replace(/\s/g, '') isnt shard.replace(/\s/g, '')
-                continue
-
-            # Collect activity info about all the shards we're
-            # replicating from
-            senders_activity_id = []
-            if activity_type is 'secondary_backfilling'
-                senders_activity_id.push(activity.value[1].backfiller.activity_id)
-            else if activity_type is 'primary_when_safe'
-                for backfiller in activity.value[1].backfillers
-                    senders_activity_id.push(backfiller.activity_id)
-
-
-            # We now have a map from one shard (union of all shards
-            # we're replicating from), to an array of progress
-            # info. Grab these.
-            union_shard = null
-            progress_info_arr = null
-            for _us, _pia of shard_map
-                union_shard = _us
-                progress_info_arr = _pia
-                break
-
-            # We now have two arrays: senders and progress infos
-            # (which should theoretically be the same size). Combine
-            # them into data points, push each datapoint onto
-            # _output_json. We'll aggregate after the loop is done.
-            for i in [0..(progress_info_arr.length - 1)]
-                progress_info = progress_info_arr[i]
-                sender = activity_map[senders_activity_id[i]]
-                ratio_available = typeof(progress_info) isnt 'string'
-                json =
-                    replicated_blocks:    if ratio_available then progress_info[0] else null
-                    total_blocks:         if ratio_available then progress_info[1] else null
-                    block_info_available: ratio_available and progress_info[0] isnt -1 and progress_info[1] isnt -1
-                    ratio_available:      ratio_available
-                    machine_id:           sender.machine_id #TODO can fail
-                _output_json.push json
-
-        # Make sure there is stuff to report
-        if _output_json.length is 0
-            return null
-
-        # Now we can aggregate the results for this shard by summing
-        # progress from every shard it's replicating from
-        agg_start =
-            total_blocks: -1
-            replicated_blocks: -1
-            block_info_available: false
-            ratio_available: false
-            backfiller_machines: []
-        agg_json = _.reduce(_output_json, ((agg, val) ->
-            if val.block_info_available
-                agg.total_blocks      = 0 if agg.total_blocks is -1
-                agg.replicated_blocks = 0 if agg.replicated_blocks is -1
-                agg.total_blocks      += val.total_blocks
-                agg.replicated_blocks += val.replicated_blocks
-                agg.block_info_available = true
-            if val.ratio_available
-                agg.ratio_available = true
-            if typeof(val.machine_id) is 'string'
-                agg.backfiller_machines.push
-                    machine_id:   val.machine_id
-                    machine_name: machines.get(val.machine_id).get('name')
-            return agg
-            ), agg_start)
-        # Phew, final output
-        output_json =
-            ratio:             agg_json.replicated_blocks / agg_json.total_blocks
-            percentage:        Math.round(agg_json.replicated_blocks / agg_json.total_blocks * 100)
-        output_json = _.extend output_json, agg_json
-
-        return output_json
-
-    # Computes aggregate backfill progress for a namespace. The
-    # datacenter argument can optionally limit the aggreggate values
-    # to the datacenter.
-    @get_backfill_progress_agg = (namespace_uuid, datacenter_uuid) ->
-        # Find a list of machines we care about
-        namespace_machines = @get_namespace_machines(namespace_uuid)
-        if datacenter_uuid?
-            datacenter_machines = _.map @get_datacenter_machines(datacenter_uuid), (m) -> m.get('id')
-        else
-            datacenter_machines = namespace_machines
-        machines = _.intersection namespace_machines, datacenter_machines
-        # Collect backfill progress for each machine
-        backfills = []
-        for machine_uuid in machines
-            for shard in namespaces.get(namespace_uuid).get('shards')
-                backfills.push(@get_backfill_progress(namespace_uuid, shard, machine_uuid))
-        backfills = _.without backfills, null
-        if backfills.length is 0
-            return null
-        # Aggregate backfill results
-        agg_start =
-            total_blocks: -1
-            replicated_blocks: -1
-            block_info_available: false
-            ratio_available: false
-        agg_json = _.reduce(backfills, ((agg, val) ->
-            if not val?
-                return agg
-            if val.block_info_available
-                agg.total_blocks      = 0 if agg.total_blocks is -1
-                agg.replicated_blocks = 0 if agg.replicated_blocks is -1
-                agg.total_blocks      += val.total_blocks
-                agg.replicated_blocks += val.replicated_blocks
-                agg.block_info_available = true
-            if val.ratio_available
-                agg.ratio_available = true
-            return agg
-            ), agg_start)
-        # Phew, final output
-        output_json =
-            ratio:             agg_json.replicated_blocks / agg_json.total_blocks
-            percentage:        Math.round(agg_json.replicated_blocks / agg_json.total_blocks * 100)
-        output_json = _.extend output_json, agg_json
-
-        return output_json
-
-    # datacenter_uuid is optional and limits the information to a specific datacenter
-    # If datacenter is not defined, we get the status for all datacenters
-    # TODO We should clean this function. We don't need so much data most of the time
-    @get_namespace_status = (namespace_uuid, datacenter_uuid) ->
-        namespace = namespaces.get(namespace_uuid)
-        json =
-            nshards: 0
-            nreplicas: 0
-            nashards: 0 # Number of available shards
-            nareplicas: 0 # Number of available replicas
-            reachability: 'Live' # 'Live' if we can write on the table, 'Down' if we can't
-
-        # If we can't see the namespace...
-        if not namespace?
-            return null
-
-        # machine and datacenter counts
-        _machines = []
-        _datacenters = []
-
-        for machine_uuid, role of namespace.get('blueprint').peers_roles
-            if !machines.get(machine_uuid)? # If the machine is dead
-                continue
-            # We filter machines. If
-            if datacenter_uuid? and machines.get(machine_uuid)?.get('datacenter_uuid') isnt datacenter_uuid
-                continue
-            peer_accessible = directory.get(machine_uuid)
-            machine_active_for_namespace = false
-            for shard, role_name of role
-                if role_name is 'role_primary'
-                    machine_active_for_namespace = true
-                    json.nshards += 1
-                    json.nreplicas += 1
-                    if peer_accessible?
-                        json.nashards += 1
-                        json.nareplicas += 1
-                if role_name is 'role_secondary'
-                    machine_active_for_namespace = true
-                    json.nreplicas += 1
-                    if peer_accessible?
-                        json.nareplicas += 1
-            if machine_active_for_namespace
-                _machines.push machine_uuid
-                if not datacenter_uuid? or datacenter_uuid isnt universe_datacenter.get('id') # If datacenter_uuid is defined, we don't want to count universe
-                    _datacenters.push machines.get(machine_uuid).get('datacenter_uuid')
-
-        json.nmachines = _.uniq(_machines).length
-        json.ndatacenters = _.uniq(_datacenters).length
-
-        # Getting the directoy per namespace
-        directory_by_namespaces = DataUtils.get_directory_activities_by_namespaces()
-
-        # We now want to know if the table is available or not
-        # The table is available only if these two conditions are met:
-        # - A write can succeed, which means that for each shard, # the number of available
-        #   replicas in each datacenter should be at least the number of acks required in this datacenter
-        # - The master for this shard is available
-
-        # This is an object that will store the number of acks for each shard and datacenter
-        # Ex: _shard_required = {
-        #   ['', 'foo']: {
-        #       universe_id: 0,
-        #       datacenter_1: 3,
-        #       datacenter_2: 1
-        #   }
-        #   ['foo', null]: {
-        #       universe_id: 0,
-        #       datacenter_1: 3,
-        #       datacenter_2: 1
-        #  }
-        # }
-        _shard_required = {}
-
-        # An object that will store for each shard a boolean that represents whether its master is available or not
-        # Ex: _shard_has_master = {
-        #   ['', 'foo']: Boolean
-        #   ['foo', null]: Boolean
-        # }
-        _shard_has_master = {}
-
-
-        # Initialize all the two previous variable
-        for shard in namespace.get('shards')
-            _shard_required[shard] = {} # We will add the requirement per datacenter in the next loop
-            _shard_has_master[shard] = false # We haven't seen the master yet, so we consider it not available
-
-        # For each shard, we save the number of write required (per datacenter) to have a write acknowledge (in this datacenter)
-        for datacenter, value of namespace.get('ack_expectations')
-            for shard of _shard_required
-                _shard_required[shard][datacenter] = value.expectation
-
-        # Checking blueprint (goals) vs directory (real state of the cluster)
-        # For each shard assignments, we are going to
-        #     - Make sure there is a master
-        #     - Decrement the number of remaining ack we need to be available (per shard)
-        blueprint = namespace.get('blueprint').peers_roles
-        for machine_id of blueprint
-            if json.reachability isnt 'Live'
-                break
-
-            machine = machines.get(machine_id)
-            if not machine?
-                continue
-
-            datacenter_id = machine.get('datacenter_uuid')
-            machine_name = machine.get('name')
-
-            for shard, role of blueprint[machine_id]
-                if json.reachability isnt 'Live'
-                    break
-
-                if role is "role_primary" # This machine is the master for the current shard
-                    if directory_by_namespaces?[namespace.get('id')]?[machine_id]?[shard] is "primary"
-                        _shard_has_master[shard] = true
-
-                        if not _shard_required[shard]?[datacenter_id]?
-                            _shard_required[shard]?[datacenter_id] = 0
-                        _shard_required[shard]?[datacenter_id] -= 1
-                    else
-                        json.reachability = 'Down'
-                else if role is "role_secondary" # This machine is a secondary for the current shard
-                    if directory_by_namespaces?[namespace.get('id')]?[machine_id]?[shard] is "secondary_up_to_date"
-                        # The shard can be defined in shards but not in the blueprint if there were not yet reprinted
-                        if not _shard_required[shard]?[datacenter_id]?
-                            _shard_required[shard]?[datacenter_id] = 0
-                        _shard_required[shard]?[datacenter_id] -= 1
-
-        # By default we consider a machine to hold a replica because of a per datacenter requirement
-        # If a datacenter has more responsabilities than required, it's because some of these machines
-        # are working for the whole cluster (universe), so we move them to universe
-        for shard of _shard_required
-            for datacenter of _shard_required[shard]
-                if _shard_required[shard][datacenter] < 0
-                    _shard_required[shard][universe_datacenter.get('id')] += _shard_required[shard][datacenter]
-                    _shard_required[shard][datacenter] = 0
-
-
-        # Make sure that all shards have a master
-        if json.reachability = 'Live'
-            for shard of _shard_has_master
-                if _shard_has_master[shard] is false
-                    json.reachability = 'Down'
-                    break
-
-        # Make sure that all shards have enough replica to satisfy a write
-        if json.reachability = 'Live'
-            for shard of _shard_required
-                for datacenter of _shard_required[shard]
-                    if _shard_required[shard][datacenter] > 0
-                        json.reachability = 'Down'
-                    break
-
-        return json
-

     @is_integer = (data) ->
         return data.search(/^\d+$/) isnt -1


Fixes multiple game over messages

Fixes multiple game over messages

--- coffee/game/ride_engine.coffee
+++ coffee/game/ride_engine.coffee
@@ -56,11 +56,11 @@

   stop: () ->
     clearInterval(@generator)
-    for e in @pickupZones
-      e.hide()
-    for e in @dropZones
-      e.hide()
-
+    for id, zone of @pickupZones
+      zone.hide()
+    for id, wrapper of @dropZones
+      wrapper.zone.hide()
+
   onCarMove: (e) ->
     pickupZoneToRemove = []
     dropZoneToRemove = []


Fix for windows template paths.

Fix for windows template paths.

--- lib/Watchers/Template.coffee
+++ lib/Watchers/Template.coffee
@@ -15,7 +15,7 @@
         .replace('/template', '')

     getModuleName: (shortPath)->
-        module = shortPath.replace(/\//g, '.') + '.template'
+        module = shortPath.replace(/[\/\\]/g, '.') + '.template'
         if moduleRoot = @getModuleRoot()
             module = "#{moduleRoot}.#{module}"
         module


Don't cancel if plugin is inactive

Don't cancel if plugin is inactive

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -18,7 +18,6 @@

   initialize: (@editorView) ->
     super
-
     @addClass('autocomplete popover-list')
     {@editor} = @editorView

@@ -69,7 +68,7 @@
     # Close the overlay when the cursor moved without
     # changing any text
     @editor.on 'cursor-moved', (data) =>
-      unless data.textChanged
+      if not data.textChanged and @active
         @cancel()

   ###


fix keep-high/keep-low functionality

fix keep-high/keep-low functionality

--- src/DiceInterpretter.coffee
+++ src/DiceInterpretter.coffee
@@ -87,7 +87,7 @@
         result

     result = (_roll(sides, options.above, options.below) for i in [1..dice])
-    result = result[-options.keep_low...options.keep_high]
+    result = result.sort()[-options.keep_low...options.keep_high]
       .reduce (t, s) -> t + s
     @dice_rolled.push
       dice:   dice


fix the number of dots displayed when user joins after the game starts

--- src/Game.coffee
+++ src/Game.coffee
@@ -67,13 +67,18 @@

     @socket.emit 'new player', playerName

-    @socket.on 'user joined', (identifier) =>
-      console.log identifier.uuid
+    @socket.on 'Player ID', (identifier) =>
       @player.id = identifier.uuid

+    @socket.on 'userJoin', (data)=>
+      player = data.identifier
+      if player.uuid isnt @player.id
+        @players[player.uuid] = new Player(@viz.two,player.name,player.uuid)

     @socket.on 'game started', (data) =>
       #start client timer here
+      console.log @player.id
+      console.log data.players
       if data.elapsed
         $("#timeContainer").html('
TIME:
')
         @timer = new Timer('#time', parseInt(data.elapsed/1000))
@@ -96,6 +101,7 @@

       # create player objects and their dots
       for player in data.players
+        console.log player.uuid
         if player.uuid isnt @player.id
           @players[player.uuid] = new Player(@viz.two, player.name, player.uuid)


Fixing some whitespace

Fixing some whitespace

--- src/List.coffee
+++ src/List.coffee
@@ -76,9 +76,9 @@
   # Consider two lists. A is the argument passed to the method append
   # of list T. R is the resulting list. x2 is a copy of x1 etc.
   #
-  # A: a -> b -> c -> Nil
-  # T: x -> y -> z -> Nil
-  # R: x2 -> y2 -> z2 -> A
+  # A: a -> b -> c -> Nil
+  # T: x -> y -> z -> Nil
+  # R: x2 -> y2 -> z2 -> A
   #
   # Thus all of the elements of A are reused.
   appendList: (list) ->
@@ -90,9 +90,9 @@
   # Consider two lists. A is the argument passed to the method prepend
   # of list T. R is the resulting list. a2 is a copy of a1 etc.
   #
-  # A: a -> b -> c -> Nil
-  # T: x -> y -> z -> Nil
-  # R: a2 -> b2 -> c3 -> T
+  # A: a -> b -> c -> Nil
+  # T: x -> y -> z -> Nil
+  # R: a2 -> b2 -> c3 -> T
   #
   # Thus all of the elements of T are reused
   prependList: (list) ->


Added a new Rule, isoDate

Added a new Rule, isoDate

--- server/src/Component/Builtin/Rules.coffee
+++ server/src/Component/Builtin/Rules.coffee
@@ -125,6 +125,8 @@
         return @regex(value, /^\d{2}\:\d{2}\:\d{2}$/) and moment(value, formats).isValid()
     datetime: (value, formats = ['YYYY-MM-DDTHH:mm:ss']) ->
         return @regex(value, /^\d{4}\-\d{2}\-\d{2}[T]\d{2}\:\d{2}\:\d{2}$/) and moment(value, formats).isValid()
+    isoDate: (value) ->
+        return @regex(value, /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})[+-](\d{2})\:(\d{2})/)  and moment(value, ['YYYY', moment.ISO_8601]).isValid()

     # Test if a value will pass a set of validation rules specified in the rules parameter
     # @value The value to be validated


Pump: allow any thenable to be used as return value of .process

--- src/Pump.coffee
+++ src/Pump.coffee
@@ -122,9 +122,11 @@
       .then (data) =>
         @currentRead = null
         @_processing = @_process data, @
-        if not (@_processing instanceof Promise)
+        if not (@_processing?.then instanceof Function)
           @_processing = undefined
           throw new Error ".process() did not return a Promise"
+
+        @_processing = Promise.resolve @_processing
         return @_processing.cancellable()
       .catch(Promise.CancellationError, ->)
       .catch (err) => @writeError err


Be more precise what methods return.

Be more precise what methods return.

--- lib.coffee
+++ lib.coffee
@@ -20,6 +20,9 @@

     @components[componentName] = componentClass

+    # To allow chaining.
+    @
+
   @getComponent: (componentName) ->
     @components[componentName] or null

@@ -30,7 +33,10 @@
   # componentName class method manually on the new class of this new component.
   @componentName: (componentName) ->
     # Setter.
-    @_componentName = componentName if componentName
+    if componentName
+      @_componentName = componentName
+      # To allow chaining.
+      return @

     # Getter.
     @_componentName or null


Provide a helper function for vanilla JavaScript.

--- lib.coffee
+++ lib.coffee
@@ -173,3 +173,26 @@

   renderComponent: (componentParent) ->
     throw new Error "Not implemented"
+
+  @extendComponent: (constructor, methods) ->
+    currentClass = @
+
+    unless _.isFunction constructor
+      methods = constructor
+      constructor = ->
+        constructor.__super__.constructor.apply @, arguments
+
+    constructor:: = Object.create currentClass::
+    constructor::constructor = constructor
+
+    # We use "own" here because this is how CoffeeScript extends the class.
+    for own property, value of currentClass
+      constructor[property] = value
+    constructor.__super__ = currentClass::
+
+    # We expect the plain object of methods here, but if something
+    # else is passed, we use only "own" properties.
+    for own property, value of methods or {}
+      constructor::[property] = value
+
+    constructor


subscription updating

subscription updating

--- server/methods.coffee
+++ server/methods.coffee
@@ -5,18 +5,18 @@
   #
   # Creates stripe customer then updates the user document with the stripe customerId and cardId
   #
-  createCustomer: (userId, card) ->
-    Stripe = StripeAPI(Billing.settings.secretKey)
+  createCustomer: (userId, card) ->
+    console.log 'Creating customer for', userId
     user = BillingUser.first(_id: userId)
     unless user then throw new Meteor.Error 404, "User not found.  Customer cannot be created."

     future = new Future()
+    Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.customers.create
       email: user.emails[0].address
       card: card.id
     , Meteor.bindEnvironment (error, customer) =>
-        if error
-          throw new Meteor.Error 500, "Error creating customer."
+        if error then throw new Meteor.Error 500, "Error creating customer."
         else
           Meteor.users.update
             _id: user._id
@@ -24,35 +24,36 @@
             'profile.customerId': customer.id
             'profile.cardId': customer.default_card
           , (error) ->
-            if error
-              throw new Meteor.Error 500, 'Error updating customer information.'
-            else
-              future.return()
-      , (error) ->
-        future.throw error
+            if error then throw new Meteor.Error 500, 'Error updating customer information.'
+            else future.return()
+      , (error) ->
+        future.throw new Meteor.Error error.error, error.message
     future.wait()

-  updateSubscription = (userId, quantity) ->
+  updateSubscription: (userId, plan, quantity) ->
+    console.log 'Updating subscription for', userId
     user = BillingUser.first(_id: userId)
     if user then customerId = user.profile.customerId
     unless user and customerId then new Meteor.Error 404, "User not found.  Subscription cannot be updated."
     if user.profile.waiveFees or user.profile.admin then return

     future = new Future()
+    Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.customers.updateSubscription customerId,
-      plan: Meteor.user().profile.plan
+      plan: plan
       prorate: true
       quantity: quantity
     , Meteor.bindEnvironment (error, subscription) =>
+        if error
+          console.log error
+          throw new Meteor.Error 500, "Error updating subscription."
         Meteor.users.update
-          _id: Meteor.userId()
+          _id: userId
         , $set:
           'profile.subscriptionId': subscription.id
         , (error) ->
-          if error
-            throw new Meteor.Error 500, "Error updating customer information."
-          else
-            future.return()
+          if error then throw new Meteor.Error 500, "Error updating customer information."
+          else future.return()
       , (error) ->
         future.throw error
     future.wait()
@@ -67,12 +68,11 @@

     future = new Future()
     console.log 'Canceling subscription for', user.emails[0].address
+    Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.customers.cancelSubscription customerId,
       Meteor.bindEnvironment (error, confirmation) =>
-        if error
-          throw new Meteor.Error 500, error.message
-        else
-          future.return()
+        if error then throw new Meteor.Error 500, error.message
+        else future.return()
       , (error) ->
         future.throw error
     future.wait()
@@ -85,6 +85,7 @@
     unless user then new Meteor.Error 404, "User not found.  Subscription cannot be deleted."

     future = new Future()
+    Stripe = StripeAPI(Billing.settings.secretKey)
     console.log 'Disabling account for ', user.emails[0].address
     # Mark the profile as disabled and delete the default card
     user.update('profile.subscriptionId': null)
@@ -110,6 +111,7 @@
     unless user and customerId then new Meteor.Error 404, "User not found.  Subscription cannot be restarted."

     future = new Future()
+    Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.customers.createCard customerId, card: card.id,
       Meteor.bindEnvironment (error, card) =>
         if error
@@ -131,6 +133,7 @@
   #
   getInvoices: ->
     future = new Future()
+    Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.invoices.list customer: Meteor.user().profile.customerId, (error, invoices) ->
       if error then future.throw error
       else future.return invoices
@@ -142,6 +145,7 @@
   #
   getUpcomingInvoice: ->
     future = new Future()
+    Stripe = StripeAPI(Billing.settings.secretKey)
     Stripe.invoices.retrieveUpcoming customer: Meteor.user().profile.customerId, (error, upcoming) ->
       if error then future.throw error
       else future.return upcoming


Fix angles calculation.

Fix angles calculation.

--- 09-delaunay/src/delaunay.coffee
+++ 09-delaunay/src/delaunay.coffee
@@ -83,8 +83,8 @@
       leftAtan2 = rightVertex.atan2(leftVertex)
       rightAtan2 = leftVertex.atan2(rightVertex)

-      leftAdjacent = _.sortBy leftAdjacent, (v) -> v.atan2(leftVertex) - leftAtan2
-      rightAdjacent = _.sortBy rightAdjacent, (v) -> rightAtan2 - v.atan2(rightVertex)
+      leftAdjacent = _.sortBy leftAdjacent, (v) -> (v.atan2(leftVertex) - leftAtan2 + 360) % 360
+      rightAdjacent = _.sortBy rightAdjacent, (v) -> (rightAtan2 - v.atan2(rightVertex) + 360) % 360

       newLeft = this.selectNext(leftTriangulation, leftVertex, leftAdjacent, leftVertex, rightVertex)
       newRight = this.selectNext(rightTriangulation, rightVertex, rightAdjacent, leftVertex, rightVertex)


Tank: removed change event + code cleanup

Change event was removed for performance reasons. Listen to both fill and
release events instead.

Code was refactored to match coffee coding style.

--- src/Tank.coffee
+++ src/Tank.coffee
@@ -39,9 +39,7 @@
     throw new Error('Tank is full') if @isFull()
     @content.push data
     @emit 'fill'
-    @emit 'change'
-    if @isFull()
-      @emit 'full'
+    @emit 'full' if @isFull()

     if @drain?
         @drain(@content[0])
@@ -59,9 +57,7 @@
     throw new Error('Tank is empty') if @isEmpty()
     result = @content.shift()
     @emit 'release'
-    @emit 'change'
-    if @isEmpty()
-      @emit 'empty'
+    @emit 'empty' if @isEmpty()
     result

 module.exports = Tank


Get token directly from learn-ide package

--- lib/nsync/atom-helper.coffee
+++ lib/nsync/atom-helper.coffee
@@ -91,9 +91,12 @@

   getToken: ->
     new Promise (resolve) ->
-      atom.config.observe 'learn-ide.oauthToken', (token) ->
-        if token? and token.length
-          resolve(token)
+      pkg = atom.packages.loadPackage('learn-ide')
+      token = pkg.mainModule.token
+
+      token.observe (value) ->
+        if value?
+          resolve(value)

   spawn: (modulePath) ->
     {BufferedNodeProcess} = require 'atom'


:green_heart: Backwards compatibility

:green_heart: Backwards compatibility

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -10,9 +10,12 @@
   constructor: ->
     @showIcon = atom.config.get('autocomplete-plus.defaultProvider') is 'Symbol'
     @snippetsSource =
-      snippetsForScopes: -> {}
+      snippetsForScopes: (scopeDescriptor) ->
+        atom.config.get('snippets', {scope: scopeDescriptor})

-  setSnippetsSource: (@snippetsSource) ->
+  setSnippetsSource: (snippetsSource) ->
+    if typeof snippetsSource?.snippetsForScopes is "function"
+      @snippetsSource = snippetsSource

   getSuggestions: ({scopeDescriptor, prefix}) ->
     return unless prefix?.length


fixes #6 Added replacement of path seperator on win32 platforms.

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -83,6 +83,7 @@
   insert: ->
       @buildIndex()
       @bindEvent()
+      os = require('os')
       path = require('path')
       editor = atom.workspace.getActiveTextEditor()
       position = editor.getCursorBufferPosition()
@@ -96,6 +97,9 @@
         defaultImport = symbol.defaultImport;
         fileFolder = path.resolve(filePath + '/..');
         relative = path.relative(fileFolder, location).replace(/\.(jsx?|tsx?)$/, '');
+        # Replace the windows path seperator with '/'
+        if (os.platform() == 'win32')
+            relative = relative.split(path.sep).join('/');

         if(defaultImport)
           importClause = "import #{selection} from './#{relative}';\n"


Fix width on PDF viewer

Fix width on PDF viewer

--- app/assets/asset/pdfViewer.coffee
+++ app/assets/asset/pdfViewer.coffee
@@ -9,6 +9,7 @@
       pdfElement = $($elem)[0]
       objectTag = document.createElement('object')
       objectTag.setAttribute('data', $scope.asset.downloadRoute(true))
+      objectTag.setAttribute('width', '80%')
       pdfElement.appendChild(objectTag)
       return


TODO: Show/hide the tutorial on hash change

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -1,4 +1,5 @@
 {Controller} = require 'spine'
+$ = require 'jqueryify'
 SubjectViewer = require './subject_viewer'
 AnimalSelector = require './animal_selector'
 animals = require 'lib/animals'
@@ -17,6 +18,8 @@
   className: 'classifier'

   tutorial: null
+
+  subject: null
   classification: null

   constructor: ->
@@ -39,7 +42,12 @@
     Subject.bind 'select', @onSubjectSelect
     User.bind 'sign-in', @onUserSignIn

+    $(window).on 'hashchange', =>
+      setTimeout @afterHashChange
+
   onSubjectSelect: (@subject) =>
+    @afterHashChange()
+
     @el.toggleClass 'tutorial', !!@subject.metadata.tutorial

     @classification = new Classification {@subject}
@@ -62,4 +70,13 @@
       tutorialSubject().select()
       @tutorial.start()

+  afterHashChange: =>
+    return unless !!@subject.metadata.tutorial
+
+    # TODO
+    if @el.is ':visible'
+      @log 'Tutorial should be showing'
+    else
+      @log 'Tutorial should be hidden'
+
 module.exports = Classifier


scroll to spotters guide on open

scroll to spotters guide on open

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -12,6 +12,7 @@
 { Tutorial }          = require 'zootorial'
 translate             = require 't7e'
 ChannelCycler         = require 'channel-cycler'
+$ = window.jQuery

 KEYS =
   space:  32
@@ -648,7 +649,11 @@
     Math.random() < @trainingRate()

   onClickGuide: ->
-    if @guideShowing then @spottersGuide.slideUp() else @spottersGuide.slideDown()
+    if @guideShowing
+      @spottersGuide.slideUp()
+    else
+      @spottersGuide.show()
+      $("html, body").animate scrollTop: @spottersGuide.offset().top - 20, 500
     @guideShowing = !@guideShowing

   onClickNextSubject: ->


fix finish button bug

fix finish button bug

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -101,7 +101,10 @@
           e.checked = false

         @el.find('button[name="to-select"]').addClass 'hidden'
-        @el.find('.what-kind').show()
+        @el.find('.what-kind').show()
+
+        # disable finished-marking button (until sighting is classified)
+        @finishButton.prop 'disabled', true

       exit: ->
         # console.log "STATE: \'whatKind/exit\'"
@@ -153,9 +156,6 @@
     @setState 'whatKind'      # set initial state
     @invert = false
     @currFrameIdx = 0
-
-    # disable finished-marking button (until sighting is classified)
-    @finishButton.prop 'disabled', true

     window.classifier = @


Fix Conversion 2 tests

Fix Conversion 2 tests

--- test/bw/moves.coffee
+++ test/bw/moves.coffee
@@ -3271,6 +3271,7 @@

   describe "Conversion 2", ->
     it "fails if the target has not yet moved", ->
+      shared.create.call(this)
       move = @battle.getMove("Conversion 2")
       mock = @sandbox.mock(move).expects('fail').once()

@@ -3279,6 +3280,7 @@
       mock.verify()

     it "changes user's type to one resisting or is immune to target's move", ->
+      shared.create.call(this)
       @p1.types = [ "Normal" ]

       move = @battle.getMove("Ember")


migrating array extensions to coffeescript

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -103,10 +103,11 @@

 Array::eachSlice = (n, iterator, context) ->
   if n > 0
-    len = (this.length / n).ceil
-    i = 0
-
-    iterator.call(context, this.slice(i*n, (i+1)*n), i*n, this) while i++ < len
+    len = (this.length / n).floor()
+    i = -1
+
+    while ++i < len
+      iterator.call(context, this.slice(i*n, (i+1)*n), i*n, this)

   return this


List asar file in expected dir

List asar file in expected dir

--- test/cli-spec.coffee
+++ test/cli-spec.coffee
@@ -113,7 +113,8 @@
       return
     return
   it 'should list files/dirs in archive with unpacked dirs', (done) ->
-    exec 'node bin/asar l tmp/packthis-unpack-dir-cli.asar', (error, stdout, stderr) ->
+    exec 'node bin/asar l test/expected/packthis-unpack-dir.asar', (error, stdout, stderr) ->
+      return done(error) if error?
       actual = stdout
       expected = fs.readFileSync('test/expected/extractthis-filelist.txt', 'utf8') + '\n'
       # on windows replace slashes with backslashes and crlf with lf


Add tests for exit codes when called with -h and --help

--- test/cli/help.coffee
+++ test/cli/help.coffee
@@ -5,7 +5,17 @@
 				stdout.should.include 'Usage: brave-mouse'
 				done()

+		it 'should exit with an exit code of 0 when called with -h', (done) ->
+			exec ['-h'], (error, stdout, stderr) ->
+				should.not.exist error
+				done()
+
 		it 'should output usage information when called with --help', (done) ->
 			exec ['--help'], (error, stdout, stderr) ->
 				stdout.should.include 'Usage: brave-mouse'
 				done()
+
+		it 'should exit with an exit code of 0 when called with --help', (done) ->
+			exec ['--help'], (error, stdout, stderr) ->
+				should.not.exist error
+				done()


Change isDev util function to use re.test() instead of search()

--- site/lib/utils.coffee
+++ site/lib/utils.coffee
@@ -3,8 +3,8 @@
 exports.isDev = (req) ->
   # We are on DEV when Host has string like
   # '/dev.' or '/staging.' or '.local' or '.dev' or 'localhost'
-  found = req.headers.Host.search(/(\/dev\.|\/staging\.|\.local$|\.dev$|localhost)/) > -1
-  return found
+  re = /(\/dev\.|\/staging\.|\.local$|\.dev$|localhost)/
+  return re.test(req.headers.Host)

 exports.prettyDate = (date) ->
   moment.utc(date).local().format('MMM Do YYYY')


Removing test, it was too specific to my enviroment

--- test/git.test.coffee
+++ test/git.test.coffee
@@ -23,14 +23,5 @@
     url = rallyAppBuilder.git.parseConfigFile(config)
     assert.strictEqual(url,"//github.com/ferentchak/rally-app-builder.git")
   )
-  describe('gatherGitInfo', ()->
-    it('should get the current git repos name', (done)->
-      process = (err,url)->
-        assert.strictEqual(url,"//github.com/ferentchak/rally-app-builder.git")
-        done()
-
-      rallyAppBuilder.git.gatherGitInfo(path: "../",process)
-    )
-  )

 )


Tests for grammar config

Tests for grammar config

--- spec/markdown-table-formatter-spec.coffee
+++ spec/markdown-table-formatter-spec.coffee
@@ -63,13 +63,16 @@
       editor.getText()

     beforeEach ->
-      editor = atom.workspace.buildTextEditor()
-      editor.getGrammar().scopeName = 'source.gfm'
-      MarkdownTableFormatter.tableFormatter.spacePadding = 1
-      MarkdownTableFormatter.tableFormatter.keepFirstAndLastPipes = true
-      MarkdownTableFormatter.tableFormatter.defaultTableJustification = 'Left'
-      MarkdownTableFormatter.tableFormatter.autoSelectEntireDocument = true
-      MarkdownTableFormatter.tableFormatter.formatOnSave = false
+      waitsForPromise ->
+        atom.packages.activatePackage('language-gfm').then ->
+          editor = atom.workspace.buildTextEditor()
+          editor.setGrammar(atom.grammars.grammarForScopeName('source.gfm'))
+          MarkdownTableFormatter.tableFormatter.spacePadding = 1
+          MarkdownTableFormatter.tableFormatter.keepFirstAndLastPipes = true
+          MarkdownTableFormatter.tableFormatter.defaultTableJustification = 'Left'
+          MarkdownTableFormatter.tableFormatter.autoSelectEntireDocument = true
+          MarkdownTableFormatter.tableFormatter.formatOnSave = false
+          MarkdownTableFormatter.tableFormatter.markdownGrammarScopes = ['source.gfm']

     testSuite test


remove duplicate beforeEach’s

remove duplicate beforeEach’s

--- test/src/math.coffee
+++ test/src/math.coffee
@@ -2,11 +2,13 @@
   it 'exists', ->
     expect(Love.Math).to.be.a("function")

+  math = null
+  beforeEach ->
+    math = new Love.Math()
+
   describe '.noise', ->
     noise = null
-    math = null
-    beforeEach ->
-      math = new Love.Math()
+    beforeEach ->
       noise = math.noise

     describe 'when called with one argument', ->
@@ -91,7 +93,7 @@
   describe '.random', ->
     random = null
     beforeEach ->
-      random = new Love.Math().random
+      random = math.random

     describe 'when called with no arguments', ->
       it 'should return a value between 0 and 1', ->
@@ -106,9 +108,8 @@
         expect(random(5, 10)).to.be.within(5, 10 + 1)

   describe '.setRandomSeed', ->
-    [math, random] = []
-    beforeEach ->
-      math = new Love.Math()
+    random = null
+    beforeEach ->
       random = math.random

     it 'should result in the same random numbers when passed the same args', ->
@@ -125,9 +126,8 @@
       expect(result_set_a).to.eql(result_set_b)

   describe '.getRandomSeed', ->
-    [math, random] = []
-    beforeEach ->
-      math = new Love.Math()
+    random = null
+    beforeEach ->
       random = math.random

     it 'should return the same numbers passed to setRandomSeed', ->
@@ -138,10 +138,6 @@
       expect(math.getRandomSeed()).to.eql([seed_low, seed_high])

   describe '.gammaToLinear', ->
-    [math] = []
-    beforeEach ->
-      math = new Love.Math()
-
     it 'should be reversed by linearToGamma', ->
       rounding_error_margin = 0.1
       [gr, gg, gb] = [120, 50, 100]
@@ -176,10 +172,6 @@
         expect(lb).to.be.ok

   describe '.isConvex', ->
-    [math] = []
-    beforeEach ->
-      math = new Love.Math()
-
     it 'returns true for a convex polygon', ->
       vertices = [0,0, 0,100, 100,100, 100,0]
       expect(math.isConvex(vertices)).to.be.true
@@ -189,10 +181,6 @@
       expect(math.isConvex(vertices)).to.be.false

   describe '.linearToGamma', ->
-    [math] = []
-    beforeEach ->
-      math = new Love.Math()
-
     it 'should be reversed by gammaToLinear', ->
       rounding_error_margin = 0.1
       [lr, lg, lb] = [120, 50, 100]
@@ -230,10 +218,6 @@
     it ''

   describe '.newRandomGenerator', ->
-    [math] = []
-    beforeEach ->
-      math = new Love.Math()
-
     it 'should return a new RandomGenerator object', ->
       expect(math.newRandomGenerator()).to.be.an.instanceof(Love.Math.RandomGenerator)

@@ -248,10 +232,6 @@
     it ''

   describe '.triangulate', ->
-    [math] = []
-    beforeEach ->
-      math = new Love.Math()
-
     it 'should return two triangles when you pass it a rectangular polygon', ->
       vertices = [0,0, 0,100, 100,100, 100,0]
       triangles = math.triangulate(vertices)


Fixing transaction

Fixing transaction

--- app/src/wallet/transaction.coffee
+++ app/src/wallet/transaction.coffee
@@ -261,6 +261,8 @@
   @create: ({amount, fees, address, utxo, changePath}, callback = null) ->
     d = ledger.defer(callback)
     dust = Amount.fromSatoshi(ledger.config.network.dust)
+    l "CREATE TRANSACTION", arguments
+    l "DUST", dust
     return d.rejectWithError(Errors.DustTransaction) && d.promise if amount.lte(dust)
     totalUtxoAmount = _(utxo).chain().map((u) -> ledger.Amount.fromSatoshi(u.get('value'))).reduce(((a, b) -> a.add(b)), ledger.Amount.fromSatoshi(0)).value()
     return d.rejectWithError(Errors.NotEnoughFunds) && d.promise if totalUtxoAmount.lt(amount.add(fees))


support for more expression string conversion types (multip, subexps)

--- javascripts/lib/math/expression_to_string.coffee
+++ javascripts/lib/math/expression_to_string.coffee
@@ -35,6 +35,13 @@
         "×"
       });

+    ref.forType(comps.division, {
+      toString: ->
+        "/"
+      toHTMLString: ->
+        "÷"
+      })
+
     ref.forType(comps.subtraction, {
       toString: ->
         "*"
@@ -43,12 +50,20 @@
       })

     ref.forType(comps.expression, {
-      toString: ->
-        @mapconcatWithMethod('toString')
-      toHTMLString: ->
-        @mapconcatWithMethod('toHTMLString')
+      toString: (wrap_with_parentheses=true)->
+        ret = @mapconcatWithMethod('toString')
+        @maybeWrapWithParentheses(ret, wrap_with_parentheses)
+      toHTMLString: (wrap_with_parentheses=true)->
+        ret = @mapconcatWithMethod('toHTMLString')
+        @maybeWrapWithParentheses(ret, wrap_with_parentheses)
+
       mapconcatWithMethod: (method)->
         _(@expression).map((it)-> ref.refine(it)[method]()).join(' ')
+      maybeWrapWithParentheses: (str, do_wrap)->
+        if do_wrap
+          "( #{str} )"
+        else
+          str
       });

     ref.forType(comps.blank, {
@@ -79,13 +94,12 @@
           "#{@val}"
       })

-
     class ExpressionToString
       initialize: (@expression)->
       toString: ->
-        ref.refine(@expression).toString()
+        ref.refine(@expression).toString(false)
       toHTMLString: ->
-        ref.refine(@expression).toHTMLString()
+        ref.refine(@expression).toHTMLString(false)

     class_mixer ExpressionToString


comment

comment

--- src/art/engine/core/canvas_element.coffee
+++ src/art/engine/core/canvas_element.coffee
@@ -1,3 +1,5 @@
+# This page has a potentially BETTER solution to detecting resizes:
+# http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
 require "javascript-detect-element-resize"

 # https://developer.mozilla.org/en-US/docs/Web/Reference/Events/mousemove


multi word at the same time

multi word at the same time

--- src/kick.coffee
+++ src/kick.coffee
@@ -1,4 +1,5 @@
-words = []
+pool_words = []
+shoot_word = null

 class ShootingWord
     constructor: (@full) ->
@@ -12,12 +13,16 @@
     shot: ->
         @remain = @remain.slice(1)

-words.push(new ShootingWord('kick neizod'))
+pool_words.push(new ShootingWord('kick neizod'))
+pool_words.push(new ShootingWord('punch neizod'))
+pool_words.push(new ShootingWord('strike neizod'))

 draw = ->
     $('#playground').empty()
-    for word in words
+    if shoot_word?
+        $('#playground').append(shoot_word.show())
+    for word in pool_words
         $('#playground').append(word.show())

@@ -26,6 +31,12 @@

 $(document).keypress (event) ->
     c = String.fromCharCode(event.charCode)
-    for word in words
-        if c == word.remain[0]
-            word.shot()
+    if not shoot_word?
+        for word, i in pool_words
+            if c == word.remain[0]
+                [shoot_word] = pool_words.splice(i, 1)
+                break
+
+    if c == shoot_word?.remain[0]
+        shoot_word.shot()
+        shoot_word = null if not shoot_word.remain


Layer: Added another boundary check for elements which are bigger than the viewport.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -448,7 +448,6 @@
 			vp.dim_layer = dim_layer
 			vp.dim_element = dim_element
 			vp.dim_pointer = dim_pointer[placement]
-

 			switch placement
 				when "c"
@@ -535,6 +534,19 @@
 				continue

 			vp = vp_pl[placement]
+
+			if vp.window_top > vp.top
+				vp.top = vp.window_top
+
+			if vp.window_left > vp.left
+				vp.left = vp.window_left
+
+			if vp.window_bottom < vp.bottom
+				vp.bottom = vp.window_bottom
+
+			if vp.window_right < vp.right
+				vp.right = vp.window_right
+
 			vp.width = vp.right - vp.left
 			vp.height = vp.bottom - vp.top

@@ -571,8 +583,6 @@
 					layer_pos.width = dim_layer.borderBoxWidth
 					layer_pos.height = dim_layer.borderBoxHeight

-			# console.debug placement, vp, layer_pos.width, layer_pos.height, vp.width, vp.height
-
 			if layer_pos.width > vp.width
 				layer_pos.width = vp.width
 				vp.cuts++
@@ -580,6 +590,8 @@
 			if layer_pos.height > vp.height
 				layer_pos.height = vp.height
 				vp.cuts++
+
+			# console.debug @_fill_space, placement, vp, layer_pos.width, layer_pos.height, vp.width, vp.height

 			# now align the layer within the available viewport
 			switch vp.align_horizontal


Legend

Legend

--- site/app.coffee
+++ site/app.coffee
@@ -756,6 +756,26 @@
       ctx.fillText('NOTE 1: %ILI is not meaningfully comparable between states,', wx, h-24-24 * Math.max(0.7, w / 1000))
       ctx.fillText('               due to differences in reporter types.', wx, h-24-12 * Math.max(0.7, w / 1000))
       ctx.fillText('NOTE 2: Color intensity is similar to, but not the same as, CDC’s Flu Activity Level.', wx, h-24)
+    # Draw legend
+    [lw, lh] = [w/25, h/2]
+    [x0, y0, x1, y1] = [w/7, h/10, w/7+lw, h/10+lh]
+    for i in [1..10]
+      ctx.beginPath()
+      ctx.moveTo(x0, y0+(i-1)*(lh/10))
+      ctx.lineTo(x0, y0+i*(lh/10))
+      ctx.lineTo(x1, y0+i*(lh/10))
+      ctx.lineTo(x1, y0+(i-1)*(lh/10))
+      ctx.lineTo(x0, y0+(i-1)*(lh/10))
+      ctx.closePath()
+      v = level2Color(11-i)
+      c = ('0' + Math.round(0x3f + v * 0xc0).toString(16)).slice(-2)
+      ctx.fillStyle = '#' + c + '4040'
+      ctx.fill()
+      ctx.stroke()
+    ctx.fillStyle = '#eee'
+    ctx.font = 12 + 'px sans-serif'
+    ctx.fillText('Highest', x0+lw/4, y0-Math.min(10, lh/10))
+    ctx.fillText('Lowest', x0+lw/4, y1+Math.min(16, lh/10))
     return 0

   renderChart: () ->


Not using element.remove() to fix for IE

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -325,8 +325,7 @@

           return element

-        oldElements?.forEach (element) ->
-          element.remove()
+        oldElements?.forEach remove

       replace(null, items)

@@ -354,3 +353,7 @@
     options.map (option) ->
       option.toString()
     .indexOf value.toString()
+
+remove = (element) ->
+  # TODO: Unbind any events to prevent leaks
+  element.parentNode?.removeChild(element)


Dropped unused line.

Dropped unused line.

--- vendor/assets/javascripts/simple_datatables.js.coffee
+++ vendor/assets/javascripts/simple_datatables.js.coffee
@@ -15,7 +15,6 @@
   iDisplayLength = 0;
   data = [];
   data_types = ["integer" , "date" , "date", "string" , "nil"]
-  suffixes = {'integer': '_eq', 'string': '_contains'}

   $.each(aoData, (index, dataObj) ->
     switch dataObj.name