fix #5269 -- this might fix an issue with opts.cb in the hub

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -435,7 +435,7 @@

         # quick check for write query against read-only connection
         if @is_standby and (opts.set? or opts.jsonb_set? or opts.jsonb_merge?)
-            opts.cb("set queries against standby not allowed")
+            opts.cb?("set queries against standby not allowed")
             return

         if opts.retry_until_success
@@ -450,7 +450,7 @@
                 cb       : (err) =>
                     if err
                         dbg("FAILED to connect -- #{err}")
-                        opts.cb("database is down (please try later)")
+                        opts.cb?("database is down (please try later)")
                     else
                         dbg("connected, now doing query")
                         @__do_query(opts)
@@ -475,9 +475,9 @@
         # calls the original cb.
         retry_opts.cb = (err) =>
             if err
-                orig_cb(err)
+                orig_cb?(err)
             else
-                orig_cb(args...)
+                orig_cb?(args...)

         # OK, now start it attempting.
         misc.retry_until_success(retry_opts)


Method moved to Attachment

Method moved to Attachment

--- src/trix/views/piece_view.coffee
+++ src/trix/views/piece_view.coffee
@@ -20,7 +20,7 @@
       @createStringNodes()

   createAttachmentElement: ->
-    if @piece.isImage()
+    if @attachment.isImage()
       @findOrCreateChildView(Trix.ImageAttachmentView, @piece.attachment, {@piece}).render()
     else
       @findOrCreateChildView(Trix.FileAttachmentView, @piece.attachment, {@piece}).render()


Finer loops counting in watchers.

Finer loops counting in watchers.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -108,7 +108,10 @@
   values = Object.create null
   values.common = init

-  isProcessing = false
+  # The number of nested processing for this watcher is counted to
+  # avoid an infinite loop.
+  loops = 0
+
   process = (event, items) ->

     # Values are grouped by namespace.
@@ -137,9 +140,9 @@
       return false for _ of valuesByNamespace
       true

-    if isProcessing
-      return unless loopDetected() is true
-    isProcessing = true
+    if loops
+      return unless (loopDetected loops) is true
+    previousLoops = loops++

     # For each namespace.
     for namespace, values_ of valuesByNamespace
@@ -164,7 +167,7 @@
         else
           collection.touch (namespace.substr 1)

-    isProcessing = false
+    loops = previousLoops

   processOne = (event, item) ->
     process event, [item]


Tiny tidy

Tiny tidy

--- src/hercule.coffee
+++ src/hercule.coffee
@@ -51,7 +51,7 @@
     return cb input

-validateOptionalArgs = (args) ->
+validateOptionalArgs = ([input, optionalArgs..., cb]) ->
   OPTION_LOGGER = 0
   OPTION_OPTIONS = 1

@@ -59,10 +59,7 @@
     relativePath: ""
     parents: []
     parentRefs: []
-
   logger = (message) -> return true
-
-  [input, optionalArgs..., cb] = args

   if not (typeof cb is 'function')
     throw new Error("Argument error: 'callback' should be a function")


prefect the unvaild condition

prefect the unvaild condition

--- src/iced/view.iced
+++ src/iced/view.iced
@@ -23,7 +23,7 @@
 			localStorage.setItem (this.prefix + key), old
 		get: (key, defaultValue) ->
 			item = localStorage.getItem (this.prefix + key), null
-			if not item
+			if not item or item.vaild isnt true
 				return defaultValue
 			else
 				now = new Date().getTime()


:bug: fix #231

:bug: fix #231

fix typo

--- src/install.coffee
+++ src/install.coffee
@@ -213,7 +213,7 @@
           filePath = path.join(temp.mkdirSync(), 'package.tgz')
           writeStream = fs.createWriteStream(filePath)
           readStream.pipe(writeStream)
-          writeStream.on 'error', (errror) ->
+          writeStream.on 'error', (error) ->
             callback("Unable to download #{packageUrl}: #{error.message}")
           writeStream.on 'close', -> callback(null, filePath)
         else


need to catch compile time errors and return them in the callback

--- src/jamoose.coffee
+++ src/jamoose.coffee
@@ -30,7 +30,10 @@
       if err
         cb err
       else
-        template = hogan.compile contents
+        try
+          template = hogan.compile contents
+        catch err
+          return cb err
         cb null, template.render(data)

   send: (to, subject, tplName, tplData, cb) ->
@@ -46,7 +49,7 @@
         switch
           when process.env.NODE_ENV is 'development'
             tmp = require 'tmp'
-
+
             tmp.file { keep: true, postfix: '.html' }, (err, path, fd) ->
               return cb err if err


keydown examples

keydown examples

--- src/keydown.coffee
+++ src/keydown.coffee
@@ -1,12 +1,24 @@
 $ ->
   ###*
   The global keydown property lets your query the status of keys.
-
+
   <pre>
+  # Examples:
+
   if keydown.left
     moveLeft()
+
+  if keydown.a or keydown.space
+    attack()
+
+  if keydown.return
+    confirm()
+
+  if keydown.esc
+    cancel()
+
   </pre>
-
+
   @name keydown
   @namespace
   ###


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -18,7 +18,7 @@
         width
       } = meterData

-      if not
+      if not I[name]
       ratio = I[name] / I["max#{name.capitalize()}"]

       if showName || text


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -188,18 +188,6 @@
   @name meter
   @methodOf Metered#
   @param {String} name The name of the property to meter
-  @param {Object} options The meter configuration options
-  @param {String} borderColor Color of the meter's border
-  @param {Number} borderWidth Width of the meter's border
-  @param {String} color Color of the meter's inner rectangle
-  @param {String} nameColor Color of the property name displayed above the meter
-  @param {String} font Size and style of the meter's font
-  @param {Number} height Height of the meter
-  @param {Object} position An x, y object representing the position of the meter
-  @param {Number} radius Border radius of the meter
-  @param {Boolean} showName Boolean to toggle whether or not to show the attribute associated with the meter
-  @param {String} text A String to display over the meter. Overrides default name attribute
-  @param {Number} width How wide the meter is
   ###
   showMeter: (name) ->
     setMeter(name, true)


adding yet another reflection native method

--- src/methods.coffee
+++ src/methods.coffee
@@ -388,6 +388,15 @@
             f.static_value
       ]
     reflect:
+      NativeMethodAccessorImpl: [
+        o 'invoke0(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;', (rs,m,obj,params) ->
+            cls = rs.get_obj(m.fields.clazz).fields.$type.toClassString()
+            method = rs.class_lookup(cls).methods[m.fields.slot]
+            rs.push obj.ref unless method.access_flags.static
+            rs.push params.array...
+            method.run(rs)
+            rs.pop()
+      ]
       Reflection: [
         o 'getCallerClass(I)Ljava/lang/Class;', (rs, frames_to_skip) ->
             #TODO: disregard frames assoc. with java.lang.reflect.Method.invoke() and its implementation


EditorModal: Make buttons optional.

EditorModal: Make buttons optional.

--- client/Main/CommonViews/editormodal.coffee
+++ client/Main/CommonViews/editormodal.coffee
@@ -24,7 +24,7 @@
     editorOptions = options.editor or {}

     appManager.require "Teamwork", =>
-      {title, content, readOnly} = editorOptions
+      {title, content, readOnly, buttons} = editorOptions
       @editor        = new EditorPane
         cssClass     : "hidden"
         title        : title    or ""
@@ -33,7 +33,7 @@
         size         :
           width      : 800
           height     : 400
-        buttons      : [
+        buttons      : buttons or [
           {
             title    : "Save"
             cssClass : "solid compact green"


add actions to tinymce toolbar

add actions to tinymce toolbar

--- app/assets/javascripts/comfy/admin/cms/editor.js.coffee
+++ app/assets/javascripts/comfy/admin/cms/editor.js.coffee
@@ -6,8 +6,12 @@
       tinymce.init({
         selector: 'textarea.rich-text-editor, textarea[data-cms-rich-text]',
         height: 400,
-        plugins: "code",
-        toolbar: "code",
+        plugins: "code anchor",
+        toolbar: """
+          insertfile undo redo | styleselect | bold italic |
+          alignleft aligncenter alignright alignjustify |
+          bullist numlist outdent indent | anchor link | code
+        """,
         style_formats: [
           {title: 'Normal text', block: 'p', classes: 'article__paragraph' },
           {title: 'Bigger text (Introduction)', block: 'p', classes: 'article__paragraph article__paragraph--bigger' },


Fix clearing source address not changing access list.

--- app/assets/javascripts/repository/edit_remote.js.coffee
+++ app/assets/javascripts/repository/edit_remote.js.coffee
@@ -34,28 +34,28 @@
       else
         set_options(options_non_mirror, to_non_mirror_option, last_value)

+    is_mirror_active = ->
+      is_mirror_selected() && is_source_address_set()
+
     is_mirror_selected = ->
       remote_type_mirror_el.is(':checked')

     is_source_address_set = ->
       source_address_el.val().trim().length > 0

-    was_mirror_selected = is_mirror_selected()
+    was_mirror_active = is_mirror_selected()

     was_source_address_set = is_source_address_set()
-
-    reset_mirror_selected = ->
-      was_mirror_selected = is_mirror_selected()

     reset_was_source_address_set = ->
       was_source_address_set = is_source_address_set()

     change_options = (event) ->
-      if !was_mirror_selected && is_mirror_selected()
+      if !was_mirror_active && is_mirror_active()
         modify_access_list 'mirror'
-      else if was_mirror_selected && !is_mirror_selected()
+      else if was_mirror_active && !is_mirror_active()
         modify_access_list 'non_mirror'
-      else if !was_mirror_selected && !is_mirror_selected()
+      else if !was_mirror_active && !is_mirror_active()
         modify_access_list 'non_mirror'

     change_type_display = (event) ->
@@ -73,6 +73,7 @@

     source_address_el.on('input', change_type_display)
     source_address_el.on('input', reset_was_source_address_set)
+    source_address_el.on('input', change_options)
     _.each(remote_type_els, (el) ->
       $(el).on('click', change_options)
     )


Don't override variant name

Don't override variant name

--- app/assets/javascripts/sprangular/models/variant.coffee
+++ app/assets/javascripts/sprangular/models/variant.coffee
@@ -1,7 +1,5 @@
 class Sprangular.Variant
   init: ->
-    @name = if @option_values[0] then @option_values[0].presentation else ""
-
     @images = Sprangular.extend(@images, Sprangular.Image)

     @image = @images[0]


change key binding

change key binding

--- keymaps/taskmill-editor-atom.cson
+++ keymaps/taskmill-editor-atom.cson
@@ -8,7 +8,7 @@
 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
 '.platform-darwin':
-  'cmd-shift-r': 'taskmill-editor-atom:chose'
+  'cmd-alt-r': 'taskmill-editor-atom:chose'

 '.platform-linux, .platform-win32':
-  'ctrl-shift-r': 'taskmill-editor-atom:chose'
+  'ctrl-alt-r': 'taskmill-editor-atom:chose'


Remove load method; Row instances should not be mutated after creation

--- assets/js/data.coffee
+++ assets/js/data.coffee
@@ -16,11 +16,6 @@
   serialize: () ->
     return @id

-  load: (obj) ->
-    if typeof obj == 'number'
-      obj = {id: obj}
-    @id = obj.id
-
   setParent: (parent) ->
     @parent = parent

@@ -53,9 +48,11 @@
   new Row null, constants.root_id

 Row.loadFrom = (parent, serialized) ->
-  row = new Row parent
-  row.load serialized
-  row
+  id = if typeof serialized == 'number'
+    serialized
+  else
+    serialized.id
+  new Row parent, id

 Row.loadFromAncestry = (ancestry) ->
   if ancestry.length == 0


Modify change tracker to detect nested attributes

--- app/assets/javascripts/admin/extensions/change_tracker.js.coffee
+++ app/assets/javascripts/admin/extensions/change_tracker.js.coffee
@@ -8,14 +8,29 @@
   changed: (evt) ->
     field = $(evt.currentTarget)
     name = field.attr("name")
+    if name.indexOf("[") > -1
+      pattern = /((?:[a-z][a-z]+)).*?((?:[a-z][a-z0-9_]*))/i
+      matches = name.match(pattern)
+      root = matches[1]
+      attr = matches[2]
+      console.log root, attr
     attrs = {}
-    if field.is("input[type=\"checkbox\"]")
+    if root && attr
+      if field.is("input[type=\"checkbox\"]")
+        attrs[root] = {}
+        attrs[root][attr] = field.is(":checked")
+      else
+        attrs[root] = {}
+        attrs[root][attr] = field.val()
+    else if field.is("input[type=\"checkbox\"]")
       attrs[name] = field.is(":checked")
     else
       attrs[name] = field.val()
     @model.set attrs,
       silent: true
+    console.log @model

     @model.collection.trigger "change", @model  if _.isObject(@model.collection)
     evt.stopPropagation()
+
 )


Increase refresh interval to 62 sec.

Increase refresh interval to 62 sec.

--- app/assets/javascripts/app/controllers/_profile/token_access.coffee
+++ app/assets/javascripts/app/controllers/_profile/token_access.coffee
@@ -12,7 +12,7 @@
     @interval(
       =>
         @load()
-      12000
+      62000
     )

   # fetch data, render view


Use :: to start text templates lookups.

Use :: to start text templates lookups.

--- app/assets/javascripts/app/controllers/text_module_widget.js.coffee
+++ app/assets/javascripts/app/controllers/text_module_widget.js.coffee
@@ -26,7 +26,7 @@
                           .find('small')
                           .text("(" + e.keywords + ")").end()
       element.append(template)
-    $('textarea').sew({values: values, token: ':', elementFactory: elementFactory })
+    $('textarea').sew({values: values, token: '::', elementFactory: elementFactory })

 class App.TextModuleUIOld extends App.Controller


Larger delay for change introduced in b07102dbae873c200c2e4eb1ce144aeac9cae4ae

--- app/assets/javascripts/ng-controllers/ng-FeedbunchCtrl.js.coffee
+++ app/assets/javascripts/ng-controllers/ng-FeedbunchCtrl.js.coffee
@@ -116,7 +116,7 @@
     menuCollapseSvc.close()
     $timeout ->
       sidebarVisibleSvc.toggle()
-    , 200
+    , 300

   #--------------------------------------------
   # Set the currently selected folder
@@ -127,7 +127,7 @@
     menuCollapseSvc.close()
     $timeout ->
       sidebarVisibleSvc.toggle()
-    , 200
+    , 300

   #--------------------------------------------
   # Load a page of entries for the currently selected feed or folder


Adding special style for Mozilla Firefox

2002 called, they want their vendor-specific styles back.

--- app/assets/javascripts/views/_shared/chart_totals_view.js.coffee
+++ app/assets/javascripts/views/_shared/chart_totals_view.js.coffee
@@ -40,7 +40,8 @@
     list.enter().append("li")
       .attr("class", get_class)
       .style("height", height)
-      .style("background-image", get_gradient)
+      .style("background-image", get_gradient_moz)
+      .style("background-image", get_gradient_webkit)
         .append("span")
         .text(get_value)

@@ -110,10 +111,13 @@
   else
     total

-get_gradient = (d) ->
+get_gradient_moz = (d) ->
+  percentage = 100 - ((Math.floor(get_proposed_value(d) / get_value(d) * 10000) / 100) || 0)
+  "-moz-linear-gradient(to bottom, #5E9B69 " + percentage + "%,  #9C0 0%)"
+
+get_gradient_webkit = (d) ->
   percentage = 100 - ((Math.floor(get_proposed_value(d) / get_value(d) * 10000) / 100) || 0)
   "-webkit-linear-gradient(top, #5E9B69 " + percentage + "%,  #9C0 0%)"
-
 ###*
   * Determine the correct class for a given week.
   * @param {!@Object} d Object of week and data.


reorder functions

reorder functions

--- backbone.debug.coffee
+++ backbone.debug.coffee
@@ -42,6 +42,13 @@
       @_options['log:events'] = false
       @_options['log:sync'] = false

+  ##### Hook Object Creation
+  _trackObjects: =>
+    @_hookPrototype('Collection', 'constructor', @_saveObjects)
+    @_hookPrototype('Model', 'constructor', @_saveObjects)
+    @_hookPrototype('View', 'constructor', @_saveObjects)
+    @_hookPrototype('Router', 'constructor', @_saveObjects)
+
   ##### Hook Events
   _hookEvents: =>
     @_hookPrototype('Collection', 'trigger', @_logEvent)
@@ -52,13 +59,6 @@
   ##### Hook Sync
   _hookSync: =>
     @_hookMethod('sync', @_logSync)
-
-  ##### Hook Object Creation
-  _trackObjects: =>
-    @_hookPrototype('Collection', 'constructor', @_saveObjects)
-    @_hookPrototype('Model', 'constructor', @_saveObjects)
-    @_hookPrototype('View', 'constructor', @_saveObjects)
-    @_hookPrototype('Router', 'constructor', @_saveObjects)

   _saveObjects: (type, method, object) =>
     @_objects[type][object.constructor.name + ':' + object.cid] = object


pricing: improve "Thank You" view according to UX design

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -2,49 +2,88 @@

   addWorkflow: (@workflow) ->
     @addSubView @workflow
-    @workflow.on 'Finished', @bound "showThankYou"
+    @workflow.on 'Finished', @bound "workflowFinished"
     @workflow.on 'Cancel', @bound "showCancellation"

   hideWorkflow: ->
     @workflow.hide()

-  showThankYou: (@workflowData, @subscription) ->
+  workflowFinished: (@formData, @subscription) ->
     @hideWorkflow()

-    @goToVmButton = new KDButtonView
-      style       : "solid green"
-      title       : "GO TO MY VM"
-      callback    : ->
-        KD.singleton("router").handleRoute "/Environments"
+    {productData: {plan: {tags}}} = @formData
+    if "vm" in tags
+    then @showPaymentSucceded()
+    else if "custom-plan" in tags
+    then @showGroupForm()

-    @thankYou = new KDCustomHTMLView
-      cssClass: "pricing-thank-you"
-      partial:
+  showCancellation: ->
+    return  if @cancellation
+    @hideWorkflow()
+    @cancellation = new KDView partial: "
This order has been cancelled.
"
+    @addSubView @cancellation
+
+  showGroupForm: ->
+    return  if @groupForm and not @groupForm.isDestroyed
+    @hideWorkflow()
+    @addSubView @groupForm = @createGroupForm()
+
+  showPaymentSucceded: ->
+    subtitle =
+      if @formData.createAccount
+      then "Please check your email to complete your registration."
+      else "Now it’s time, time to start Koding!"
+
+    @addSubView thankYou = new KDCustomHTMLView
+      cssClass : "pricing-thank-you"
+      partial  :
         """

So much wow, so much horse-power!

-
Now it’s time, time to start Koding!

-
-        #{
-          if @subscription.createAccount
-          then '
Please check your email for your registration link.
'
-          else ''
-        }
+
#{subtitle}

         """

-    @thankYou.addSubView @goToVmButton
+    if @formData.loggedIn
+      thankYou.addSubView new KDButtonView
+        style    : "solid green"
+        title    : "Go to your environment"
+        callback : ->
+          KD.singleton("router").handleRoute "/Environments"

-    if "custom-plan" in @workflowData.productData.plan.tags
-      @thankYou.addSubView @createGroupNameForm()
+  showGroupCreated: (group, subscription) ->
+    planCodes = Object.keys subscription.quantities
+    KD.remote.api.JPaymentProduct.some {planCode: $in: planCodes}, limit: 30, (err, products) =>
+      return  if KD.showError err

-    @addSubView @thankYou
+      subtitle =
+        if @formData.createAccount
+        then "Please check your email to complete your registration."
+        else ""

-  showGroupCreateForm: ->
-    unless @groupForm?
-      @addSubView @groupForm = @createGroupNameForm()
+      @addSubView thankYou = new KDCustomHTMLView
+        cssClass : "pricing-thank-you"
+        partial  :
+          """
+
+
#{group.title} has been successfully created

+
#{subtitle}

+          """

-  createGroupNameForm: ->
-    @groupForm              = new KDFormViewWithFields
+      productList = products.map (product) ->
+        "
#{subscription.quantities[product.planCode]}x #{product.title}
"
+
+      thankYou.addSubView new KDCustomHTMLView
+        cssClass : "product-list"
+        partial  : productList.join ""
+
+      if @formData.loggedIn
+        thankYou.addSubView new KDButtonView
+          title    : "Go to Group"
+          callback : ->
+            window.open "#{window.location.origin}/#{group.slug}", "_blank"
+
+  createGroupForm: ->
+    return new KDFormViewWithFields
       title                 : "Enter new group name"
       cssClass              : "pricing-create-group"
       callback              : @bound "createGroup"
@@ -78,7 +117,7 @@
         Slug                :
           label             : "Address"
           itemClass         : KDCustomHTMLView
-          partial           : "#{location.protocol}//#{location.host}/"
+          partial           : "#{window.location.origin}"
         Visibility          :
           itemClass         : KDSelectBox
           label             : "Visibility"
@@ -86,8 +125,8 @@
           name              : "visibility"
           defaultValue      : "hidden"
           selectOptions     : [
-            { title : "Hidden",    value : "hidden"  }
-            { title : "Visible",   value : "visible" }
+            title : "Hidden" ,   value : "hidden"
+            title : "Visible",   value : "visible"
           ]

   createGroup: ->
@@ -105,40 +144,16 @@
     JGroup.create options, (err, group, subscription) =>
       return KD.showError err  if err

-      @showSummaryModal group, subscription
+      @groupForm.destroy()
+      @showGroupCreated group, subscription

       # enter first post of group.
       JGroup.createGroupBotAndPostMessage
         title   : "Welcome"
         body    : "Welcome to your group."
         botname : "groupbot"
-      , (err, update)->
-        return KD.showError err if err
-
-  showSummaryModal: (group, subscription) ->
-    { JPaymentProduct } = KD.remote.api
-
-    planCodes = Object.keys subscription.quantities
-
-    JPaymentProduct.some { planCode: $in: planCodes }, { limit: 30 },
-      (err, products) ->
-        return  if KD.showError err
-
-        modal              = new KDModalView
-          title            : "Group successfully created"
-          width            : 600
-          overlay          : yes
-          buttons          :
-            "Go to Group"  :
-              style        : "modal-clean-red"
-              callback     : ->
-                window.open "#{window.location.origin}/#{group.slug}", "_blank"
-            Close          :
-              style        : "modal-cancel"
-              callback     : -> modal.destroy()
-          content          : products.map (product) ->
-            "
#{ subscription.quantities[product.planCode] }x #{ product.title }
"
-
+      , (err) ->
+        KD.showError err

   checkSlug: ->
     slug      = @groupForm.inputs.GroupUrl
@@ -150,9 +165,3 @@
       KD.remote.api.JGroup.suggestUniqueSlug slugy, (err, newSlug)->
         slugView.updatePartial "#{location.origin}/#{newSlug}"
         slug.setValue newSlug
-
-  showCancellation: ->
-    @hideWorkflow()
-    return  if @cancellation
-    @cancellation = new KDView partial: "
This order has been cancelled.
"
-    @addSubView @cancellation


Missing parens

Missing parens

--- src/rpgdice.coffee
+++ src/rpgdice.coffee
@@ -24,7 +24,7 @@
     else
       results = []
       results.push(randint sides) for [1..num]
-      return results.toString
+      return results.toString()

   robot.respond /roll (?:([0-9]+)d([0-9]+))(?: (.*))*/i, (msg) ->
     num = msg.match[1]


add token.coffee file

add token.coffee file

--- client/app/models/token.coffee
+++ client/app/models/token.coffee
@@ -7,4 +7,4 @@
         @id = id

     getToken: (callbacks) ->
-        client.get "api/getToken/:id", callbacks
+        client.get "api/getToken/#{@id}", callbacks


use correct el id

use correct el id

--- app/assets/javascripts/states/main/views/_select_user.js.coffee
+++ app/assets/javascripts/states/main/views/_select_user.js.coffee
@@ -5,4 +5,4 @@
     template: "main/_select_user"

     after_render: ->
-      @$("select#users").select2()
+      @$("select#"+@name).select2()


fix `upscale` option meaning in crop widget

--- app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
+++ app/assets/javascripts/uploadcare/ui/crop/crop-widget.js.coffee
@@ -21,8 +21,8 @@
         # like "-/resize/%preferedSize%/". (optional)
         scale: true

-        # If set to `true` image in widget will be scaled up
-        # to widget size if necessary. (optional)
+        # If set to `true` "-/resize/%preferedSize%/" will be added
+        # even if selected area smaller than `preferedSize`
         upscale: false

         # Defines widget size. if set to `null` widget size will be equal
@@ -171,7 +171,7 @@
       __calcImgSizes: ->
         {width: @__originalWidth, height: @__originalHeight} = @__img[0]
         [@__resizedWidth, @__resizedHeight] =
-          fitSize @__originalWidth, @__originalHeight, @__wrapWidth, @__wrapHeight, @__options.upscale
+          fitSize @__originalWidth, @__originalHeight, @__wrapWidth, @__wrapHeight
         paddingTop = (@__wrapHeight - @__resizedHeight) / 2
         paddingLeft = (@__wrapWidth - @__resizedWidth) / 2


do not focus on 'save' button

do not focus on 'save' button

We do not do this because it scrolls the dialog all the way down.

--- client/driver_lines.coffee
+++ client/driver_lines.coffee
@@ -148,6 +148,8 @@
             result = save_driver_line(dialog_template)
             if result.errors
               bootbox.alert('Errors: '+result.errors.join(", "))
+
+    window.dialog_template.off("shown.bs.modal") # do not focus on button
     return

 # ------------- general functions --------


Fixed update from collection view

Fixed update from collection view

--- client/collection_view.coffee
+++ client/collection_view.coffee
@@ -53,7 +53,7 @@
     fields_in_order = get_collection_view_fields(@collection)
     names_in_order = _.clone fields_in_order
     values = (Houston._nested_field_lookup(@, field.name) for field in fields_in_order[1..])  # skip _id
-    ({field_value, field_name} for [field_value, {name:field_name}] in _.zip values, names_in_order[1..])
+    ({field_value, field_name, collection: @collection} for [field_value, {name:field_name}] in _.zip values, names_in_order[1..])
   filter_value: ->
     if Houston._session('field_selectors') and Houston._session('field_selectors')[@]
       Houston._session('field_selectors')[@]
@@ -86,6 +86,7 @@

   'dblclick .houston-collection-field': (e) ->
     $this = $(e.currentTarget)
+    collection = @collection
     field_name = $this.data('field')
     type = Houston._get_type(field_name, get_current_collection())
     input = 'text' # TODO schemaToInputType type fix on blur bug
@@ -104,7 +105,7 @@
         get_current_collection())
       update_dict = {}
       update_dict[field_name] = updated_val
-      Houston._call("#{Houston._session('collection_name')}_update",
+      Houston._call("#{collection}_update",
         document_id, $set: update_dict)

   'keyup .houston-column-filter': (e) ->


Fixes #3 and allows d3.TimeSlider to work on Firefox as well!

Signed-off-by: Marko Locher <9efe9a5a1da5f41a4eb7599f2715dc24abf5bbc8@locher.at>

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -12,9 +12,14 @@
         # create the root svg element
         @svg = d3.select(element).append('svg').attr('class', 'timeslider')

+        @useBBox = false;
+        if(@svg[0][0].clientWidth == 0)
+            d3.select(element).select('svg').append('rect').attr('width', '100%').attr('height', '100%').attr('opacity', '0')
+            @useBBox = true
+
         # default options and other variables for later
-        @options.width  = @svg[0][0].clientWidth
-        @options.height = @svg[0][0].clientHeight
+        @options.width = if @useBBox then @svg[0][0].getBBox().width else @options.width  = @svg[0][0].clientWidth
+        @options.height = if @useBBox then @svg[0][0].getBBox().height else @svg[0][0].clientHeight
         @options.brush ||= {}
         @options.brush.start || = @options.start
         @options.brush.end ||= new Date(new Date(@options.brush.start).setDate(@options.brush.start.getDate() + 3))
@@ -189,7 +194,8 @@
         # resizing (the window)
         resize = =>
             # update the width of the element and the scales
-            @options.width = d3.select(@element).select('svg.timeslider')[0][0].clientWidth
+            svg = d3.select(@element).select('svg.timeslider')[0][0]
+            @options.width = if @useBBox then svg.getBBox().width else svg.clientWidth
             @scales.x.range([0, @options.width])

             redraw()


display less precision in charts

display less precision in charts

--- demo.coffee
+++ demo.coffee
@@ -20,6 +20,7 @@
         bodyColor: '#ff0000'
         radius: [ 6, 2, 20, 'body size' ]
 byId = (id) -> document.getElementById(id)
+trimNum = (n) -> parseFloat n.toPrecision(3)
 decamelize = (str) ->
   str = str.replace /([a-z])([A-Z])/g, "$1 $2"
   str.toLowerCase()
@@ -162,11 +163,11 @@
         mean += v for v in values
         mean /= values.length
         if values.length % 2
-          median = values[ Math.ceil(values.length/2) ]
+          median = values[ Math.floor(values.length/2) ]
         else
           i = values.length / 2
           median = ( values[i] + values[i - 1] ) / 2
-        rows.push [ evoData.generation, median, mean, min, max ]
+        rows.push [ evoData.generation, trimNum(median), trimNum(mean), trimNum(min), trimNum(max) ]
     rows: []
   }
 collectData = (u) ->


[Filetree] Change timeout to 10000

[Filetree] Change timeout to 10000

--- client/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -158,7 +158,7 @@
         @hideNotification()
       else
         failCallback err
-    , failCallback), no
+    , failCallback, 10000), no

   collapseFolder:(nodeView, callback, silence=no)->


improved finder keynav

improved finder keynav

--- client/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -561,8 +561,18 @@
     @openItem nodeView

   performRightKey:(nodeView, event)->
+
+    {type} = nodeView.getData()
+    if /mount|folder/.test type
+      @expandFolder nodeView
+
   performUpKey:(nodeView, event)-> super
-  performLeftKey:(nodeView, event)-> super
+  performLeftKey:(nodeView, event)->
+
+    if nodeView.expanded
+      @collapseFolder nodeView
+      return no
+    super

   ###


Teamwork: Sessions are now group sensitive.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -34,6 +34,8 @@
     @historyRef     = @workspaceRef.child "history"
     @chatRef        = @workspaceRef.child "chat"
     @usersRef       = @workspaceRef.child "users"
+    @keysRef        = @workspaceRef.child "keys"
+    @groupNameRef   = @workspaceRef.child "groupName"
     @userRef        = @usersRef.child KD.nick()
     @requestPingRef = @workspaceRef.child "requestPing"
     @onlineCountRef = @workspaceRef.child "onlineUserCount"
@@ -53,7 +55,8 @@
         @createPanel()
       else
         @createPanel()
-        @workspaceRef.set "keys": @sessionData
+        @keysRef.set @sessionData
+        @groupNameRef.set KD.getSingleton("groupsController").currentGroupName

       if not isOldSession
         @addToHistory { message: "$0 started the session", by: KD.nick() }
@@ -125,9 +128,17 @@
         return @pingHostTimer = KD.utils.wait 10000, =>
           @showNotActiveView()

-      @syncWorkspace()
-      @connected = yes
-      KD.utils.killWait @pingHostTimer
+      @groupNameRef.once "value", (snapshot) =>
+        groupName = snapshot.val()
+
+        KD.whoami().checkGroupMembership groupName, (err, isMember) =>
+          unless isMember
+            warn "you are not belong to this group"
+            return @showNotActiveView()
+
+          @syncWorkspace()
+          @connected = yes
+          KD.utils.killWait @pingHostTimer

     @usersRef.on "value", (snapshot) =>
       data   = @reviveSnapshot snapshot


fix(service): invalid promise on login (see also #17 & ca65a8d2)

--- src/service.coffee
+++ src/service.coffee
@@ -202,15 +202,13 @@
         return connectionState
       isValidSession: -> validSession
       login : (username, password) ->
-        deferred = $q.defer()
         util.login(username, password)
         .then (reply) ->
           validSession = true
-          return
+          return reply
         .catch (reply) ->
           validSession = false
-          return
-        return deferred.promise
+          return reply

     # Update the current connection state periodially.
     $interval (-> wrapped.getConnectionState(true)), sockjsStateInterval


Fixed off-by-one

Fixed off-by-one

--- src/shaders.coffee
+++ src/shaders.coffee
@@ -48,7 +48,7 @@
       shadersToInclude++
       do (insertLocation) ->
         loadShaderSource webglmc.joinFilename(filename, match[1]), (source) ->
-          lines[insertLocation] = source + '\n#line ' + (insertLocation - 1) +
+          lines[insertLocation] = source + '\n#line ' + insertLocation +
             ' ' + sourceID
           shadersToInclude--
           checkDone()


Better handling of testing whole screen screenshots

--- lib/testing/screenshot/server.coffee
+++ lib/testing/screenshot/server.coffee
@@ -15,7 +15,7 @@
 INITIALIZATION_FILE_PATH = pathUtils.join DEST, 'initialization.png'
 SCREENSHOT_TRIES = 100
 SCREENSHOT_TRY_DELAY_SEC = 0.2
-CUSTOM_SCREENSHOT_MAX_DELAY_MS = 1000
+CUSTOM_SCREENSHOT_MAX_DELAY_MS = 2000
 {LOG_SCREENSHOT_DATA_URI} = process.env

 if LOG_SCREENSHOT_DATA_URI
@@ -58,6 +58,17 @@
         Expected: #{expectedUri}
     """

+waitForScreenshot = (path, maxDelay) ->
+    startTime = Date.now()
+    itertion = 0
+    while Date.now() < startTime + maxDelay
+        try
+            stats = fs.statSync path
+            if stats?.size
+                return true
+        childProcess.execSync "sleep 0.1"
+    return false
+
 takeScreenshot = (opts) ->
     # use target-specified screenshot function
     if opts.env
@@ -65,17 +76,9 @@
         handler.focusWindow?()
         if handler.takeScreenshot
             startTime = Date.now()
+            handler.takeScreenshot opts
             maxDelay = handler.TAKE_SCREENSHOT_DELAY_MS ? CUSTOM_SCREENSHOT_MAX_DELAY_MS
-            itertion = 0
-            while Date.now() < startTime + maxDelay
-                if itertion++ % 10 is 0
-                    handler.takeScreenshot opts
-                try
-                    stats = fs.statSync opts.path
-                    if stats?.size
-                        break
-                childProcess.execSync "sleep 0.1"
-            if stats?.size
+            if waitForScreenshot(opts.path, maxDelay)
                 return
             else
                 log.warn """
@@ -86,6 +89,7 @@

     # take the whole screen screenshot
     driver.takeScreenshot opts
+    waitForScreenshot(opts.path, CUSTOM_SCREENSHOT_MAX_DELAY_MS)
     return

 tryTakeScreenshot = (opts, isOk) ->


Adds kill task error code for windows

Adds kill task error code for windows

--- tasks/kill.coffee
+++ tasks/kill.coffee
@@ -26,7 +26,7 @@
       console.log 'killing app' if args.verbose
       lock.killTask.skip[dist] = true
       cb = (err) ->
-        if err.code == 'ENOENT' or err.code == 1
+        if err.code == 'ENOENT' or err.code == 1 or err.code == 128
           console.error err if args.verbose
           done()
         else


bring back settings button, refactor buttons a bit

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -36,20 +36,10 @@
         @a outlet: 'loginLink', class: 'author', href: "https://atom.io/users/#{owner}", owner
         @div class: 'meta-right', =>
           @div outlet: 'buttons', class: 'btn-group', =>
-            #
-            # @button outlet: 'issueButton', class: 'btn btn-default icon icon-bug', 'Report Issue'
-            # @button outlet: 'readmeButton', class: 'btn btn-default icon icon-book', 'Open README'
-            # @button outlet: 'changelogButton', class: 'btn btn-default icon icon-squirrel', 'Open CHANGELOG'
-            # @button outlet: 'openButton', class: 'btn btn-default icon icon-link-external', 'Open in Atom'
-            #
-            @button type: 'button', class: 'btn',outlet: 'installButton', => # TODO hide in installedpackagesview
-              @span class: 'icon icon-cloud-download'
-              @text "Install"
-            @button type: 'button', class: 'btn', outlet: 'uninstallButton', => # TODO hide in installedpackagesview
-              @span class: 'icon icon-trashcan'
-              @text "Uninstall"
-            @button outlet: 'enablementButton', class: 'btn btn-default', =>
-              @span class: 'icon icon-playback-pause'
+            @button type: 'button', class: 'btn icon icon-gear', outlet: 'settingsButton', 'Settings'
+            @button type: 'button', class: 'btn icon icon-cloud-download', outlet: 'installButton', 'Install'
+            @button type: 'button', class: 'btn icon icon-trashcan', outlet: 'uninstallButton', 'Uninstall'
+            @button outlet: 'enablementButton', class: 'btn btn-default icon icon-playback-pause', =>
               @span class: 'disable-text', 'Disable'

   initialize: (@pack, @packageManager) ->
@@ -71,6 +61,9 @@

     @uninstallButton.on 'click', =>
       @uninstall()
+
+    @settingsButton.on 'click', =>
+      @parents('.settings-view').view()?.showPanel(@pack.name, {back: 'Installed Packages'})

     @packageName.on 'click', =>
       @parents('.settings-view').view()?.showPanel(@pack.name, {back: 'Installed Packages'})
@@ -96,14 +89,12 @@
       @addClass('disabled')
       @enablementButton.find('.disable-text').text('Enable')
       @enablementButton
-        .find('.icon')
         .addClass('icon-playback-play')
         .removeClass('icon-playback-pause')
     else
       @removeClass('disabled')
       @enablementButton.find('.disable-text').text('Disable')
       @enablementButton
-        .find('.icon')
         .addClass('icon-playback-pause')
         .removeClass('icon-playback-play')


[grunt] cleaned up `watch` task a little bit

--- tasks/grunt-contrib-watch.coffee
+++ tasks/grunt-contrib-watch.coffee
@@ -6,7 +6,7 @@
 module.exports = ->
   @config 'watch',
     configs:
-      files: ['gruntfile.coffee', '<%= path.tasks.root %>/*']
+      files: ['gruntfile.coffee', '<%= path.tasks.root %>/{,**/}*']
       options:
         reload: true
     static:
@@ -26,7 +26,10 @@
       tasks: ['webfont']
     images:
       files: ['<%= path.source.images %>/{,**/}*']
-      tasks: ['copy:images']
+      tasks: [
+        'newer:copy:images'
+        'newer:responsive_images:thumbnails'
+      ]
     templates:
       files: ['<%= path.source.templates %>/{,**/}*.nj', '!<%= path.source.templates %>{,**/}_*.nj']
       tasks: ['newer:nunjucks']
@@ -47,7 +50,4 @@
       ]
     sprites:
       files: ['<%= path.source.sprites %>/{,**/}*.{jpg,jpeg,gif,png}']
-      tasks: ['sprite']
-    thumbnails:
-      files: ['<%= path.source.images %>/{,**/}*.{jpg,jpeg,gif,png}']
-      tasks: ['newer:responsive_images:thumbnails']+      tasks: ['sprite']


Trigger current event on current slide and add class current.

--- coffee/presentation.coffee
+++ coffee/presentation.coffee
@@ -115,6 +115,9 @@
         memo + height
       , 0)
       $('.presentation').css('transform', "translateY(-#{y}px)")
+
+      @$current.removeClass('current')
+      $section.addClass('current').trigger('current')
       @$current = $section


added coffee-script 2

added coffee-script 2

--- tasks/test.coffee
+++ tasks/test.coffee
@@ -1,2 +1,2 @@
-#!node_modules/coffee-script/bin/coffee
+#!/app/node_modules/coffee-script/bin/coffee
 c = 'TESTING FOR DAYS YO'


 Merge branch 'master.embedlyCache' into 'master'

Master.Embedly Cache

--- server/lib/server/image_cache.coffee
+++ server/lib/server/image_cache.coffee
@@ -1,6 +1,7 @@
-fs   = require "fs"
-http = require "https"
-mime = require "mime"
+fs     = require "fs"
+http   = require "https"
+mime   = require "mime"
+crypto = require "crypto"

 {
   embedly : {
@@ -21,7 +22,14 @@
              "key=#{apiKey}&" +
              "url=#{url}"

-  filename = "#{imagePath}/#{url.split('/').join('_')}"
+  # split the ext, md5 just the url and write md5+ext to disk
+  # md5 since some urls are too big and cause 'ENAMETOOLONG' error
+  splitUrl = url.split(".")
+  ext      = splitUrl.pop()
+  noExt    = splitUrl.join(".")
+
+  md5      = crypto.createHash("md5").update(noExt).digest("hex")
+  filename = "#{imagePath}/#{md5}.#{ext}"

   serveFile = (filename, res)->
     fileStream = fs.createReadStream filename
@@ -29,7 +37,7 @@

     mimeType = mime.lookup filename

-    res.writeHead(200, {'Content-Type': mimeType });
+    res.writeHead(200, { 'Content-Type': mimeType });

   if fs.existsSync filename
     return serveFile filename, res


:lipstick:

:lipstick:

--- lib/models/git-push.coffee
+++ lib/models/git-push.coffee
@@ -1,10 +1,10 @@
 git = require '../git'
-ListView = require '../views/remote-list-view'
+RemoteListView = require '../views/remote-list-view'

 gitPush = ->
   git.cmd(
     args: ['remote'],
-    stdout: (data) -> new ListView(data.toString(), 'push')
+    stdout: (data) -> new RemoteListView(data, 'push')
   )

 module.exports = gitPush


Remove now-overzeaulous comment

Remove now-overzeaulous comment

--- lib/operators/input.coffee
+++ lib/operators/input.coffee
@@ -4,12 +4,6 @@
 # The operation for text entered in input mode. This operator is not
 # used as the user types, but is created when the user leaves insert mode,
 # and is available for repeating with the . operator (Replace)
-#
-# Currently, limitations with Transaction (from the text-buffer package)
-# prevent us from doing anything reasonable with text input if it is
-# anything but straight-up typed characters. No backspacing. It won't match
-# vim when using the substitution command. You'll regret using this code.
-# Never use this code.
 #
 class Input extends Operator
   standalone: true


Memoize packageHasSettings

Memoize packageHasSettings

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -22,7 +22,7 @@
     else
       false

-  packageHasSettings: (packageName) ->
+  packageHasSettings: _.memoize((packageName) ->
     grammars = atom.grammars.getGrammars() ? []
     for grammar in grammars when grammar.path
       return true if grammar.packageName is packageName
@@ -31,6 +31,7 @@
     pack.activateConfig() if pack? and not atom.packages.isPackageActive(packageName)
     schema = atom.config.getSchema(packageName)
     schema? and (schema.type isnt 'any')
+  )

   runCommand: (args, callback) ->
     command = atom.packages.getApmPath()


Fix for #24

Fix for #24

--- lib/table-formatter.coffee
+++ lib/table-formatter.coffee
@@ -169,10 +169,10 @@
     )
     ( # format capture
       (?:
-        \|\ *:?-*:?\ *            # format starting w/pipe
-        |\|?(?:\ *:?-*:?\ *\|)+   # or separated by pipe
+        \|\ *(?::?-+:?|::)\ *            # format starting w/pipe
+        |\|?(?:\ *(?::?-+:?|::)\ *\|)+   # or separated by pipe
       )
-      (?:\ *:?-*:?\ *)?           # maybe w/o trailing pipe
+      (?:\ *(?::?-+:?|::)\ *)?           # maybe w/o trailing pipe
       \ *                         # maybe trailing whitespace
       \r?\n                       # newline
     )


Removed stray console.log

Removed stray console.log

--- public/scripts/backbone-schedule/views/scheduler/friends-list.coffee
+++ public/scripts/backbone-schedule/views/scheduler/friends-list.coffee
@@ -26,7 +26,6 @@
           friend.listView = new FriendView model: friend
           @$list.append friend.listView.el if @$list
       Shark.friendsList.bind 'addComplete', (friend) =>
-        console.log 'here'
         friend.listView = new FriendView model: friend
         @$list.append friend.listView.el if @$list
       Shark.friendsList.bind 'remove', (friend) =>


Fix spec for rpc request middleware

Fix spec for rpc request middleware

--- src/endpoint.spec.coffee
+++ src/endpoint.spec.coffee
@@ -111,6 +111,7 @@
         endpoint.initialize
           ioInstance: ioStub
           rpcRequestMiddleware: rpcRequestMiddlewareStub
+        endpoint.setRPCHandler rpcHandlerStub

       it 'should pass in the rpc request data, the assiocated socket and a done callback', ->


Add TODO to the spec file

Add TODO to the spec file

--- src/endpoint.spec.coffee
+++ src/endpoint.spec.coffee
@@ -1,3 +1,5 @@
+# TODO: Refactor specs to test "addRpcRequestMiddleware" separately
+
 describe 'socket io remote endpoint', ->
   sandbox = null
   socketIoRemoteEndpoint = null


Really silly typo.

Really silly typo.

--- server/battle.coffee
+++ server/battle.coffee
@@ -168,7 +168,7 @@
       delete @playerActions[id]

     if @isOver()
-      @scontinueTurn()
+      @continueTurn()
       @endBattle()
       return

@@ -177,11 +177,11 @@
       @requestAction(player, validActions)

     # Send a message to each player.
-    @scontinueTurn()
+    @continueTurn()

     if @areAllRequestsCompleted() then @startNewTurn()

-  scontinueTurn: =>
+  continueTurn: =>
     @message 'The turn continued.'
     for id, player of @players
       player.updateChat('SERVER', @buffer.join("<br>"))


Allow specifying baseUrl for individual endpoints


:racehorse: Doesn't recreate decorations when selection ranges changes

--- lib/minimap-selection-view.coffee
+++ lib/minimap-selection-view.coffee
@@ -9,7 +9,6 @@

     @subscriptions = new CompositeDisposable

-    @subscriptions.add editor.onDidChangeSelectionRange @handleSelection
     @subscriptions.add editor.onDidAddCursor @handleSelection
     @subscriptions.add editor.onDidChangeCursorPosition @handleSelection
     @subscriptions.add editor.onDidRemoveCursor @handleSelection


state change events

state change events


update acceptance tests helper to include version

--- services/web/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee
+++ services/web/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee
@@ -12,7 +12,7 @@
 	getProjectStructureUpdates: (project_id) ->
 		@updates[project_id] || { docUpdates: [], fileUpdates: [] }

-	addProjectStructureUpdates: (project_id, userId, docUpdates, fileUpdates) ->
+	addProjectStructureUpdates: (project_id, userId, docUpdates, fileUpdates, version) ->
 		@updates[project_id] ||= { docUpdates: [], fileUpdates: [] }

 		for update in docUpdates
@@ -22,6 +22,8 @@
 		for update in fileUpdates
 			update.userId = userId
 			@updates[project_id].fileUpdates.push(update)
+
+		@updates[project_id].version = version

 	run: () ->
 		app.post "/project/:project_id/flush", (req, res, next) =>
@@ -29,8 +31,8 @@

 		app.post "/project/:project_id", jsonParser, (req, res, next) =>
 			project_id = req.params.project_id
-			{userId, docUpdates, fileUpdates} = req.body
-			@addProjectStructureUpdates(project_id, userId, docUpdates, fileUpdates)
+			{userId, docUpdates, fileUpdates, version} = req.body
+			@addProjectStructureUpdates(project_id, userId, docUpdates, fileUpdates, version)
 			res.sendStatus 200

 		app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>


Use correct cover_image property

Use correct cover_image property

--- compiler/ContentAPI.coffee
+++ compiler/ContentAPI.coffee
@@ -106,11 +106,10 @@
         if name in ['content', 'cover_content'] and @_data.type in [ENTRY, IMAGE, TEXT,  EMBED]
             return @_data[name]

-        # Treat the old cover_content images as new ones.
         if name is 'cover_image'
-            unless @_data.cover_content
+            unless @_data.cover_image
                 return null
-            return new CDNImage(@_data.cover_content)
+            return new CDNImage(@_data.cover_image)

         switch name.split('_').pop()
             when 'date'


AccountCredentialListController: if provider passed don't create Add New button

--- client/account/lib/views/accountcredentiallistcontroller.coffee
+++ client/account/lib/views/accountcredentiallistcontroller.coffee
@@ -52,10 +52,15 @@
     view = @getView()
     view.on "ShowShareCredentialFormFor", @bound "showShareCredentialFormFor"

+    {provider} = @getOptions()
+    @createAddCredentialMenu()  if not provider?
+
+
+  createAddCredentialMenu: ->
+
+    Providers    = globals.config.providers
     providerList = { }

-
-    Providers    = globals.config.providers
     Object.keys(Providers).forEach (provider) =>

       return  if Object.keys(Providers[provider].credentialFields).length is 0
@@ -65,7 +70,7 @@
           @_addButtonMenu.destroy()
           @showAddCredentialFormFor provider

-    view.parent.prepend addButton = new KDButtonView
+    @getView().parent.prepend addButton = new KDButtonView
       cssClass  : 'add-big-btn'
       title     : 'Add new credential'
       icon      : yes
@@ -78,6 +83,7 @@
         , providerList

         @_addButtonMenu.setCss 'z-index': 10002
+

   showShareCredentialFormFor: (credential) ->


CommentView: change true2yes

CommentView: change true2yes

--- client/Social/Activity/views/comments/commentlistitemview.coffee
+++ client/Social/Activity/views/comments/commentlistitemview.coffee
@@ -50,13 +50,13 @@
     # if i am the owner of the comment or activity
     # i can delete it
     if isCommentMine or isActivityMine
-      settingsOptions.delete = true
-      showSettingsMenu = yes
+      settingsOptions.delete = yes
+      showSettingsMenu       = yes

     # if i can edit comments(have permission)
     if isCommentMine and "edit own comments" in KD.config.permissions or \
       "edit comments" in KD.config.permissions
-        settingsOptions.edit = true
+        settingsOptions.edit = yes
         showSettingsMenu     =  yes

     # if settings menu should be visible


Refresh message list after a successful message sent

--- client/app/Applications/Inbox.kdapplication/AppController.coffee
+++ client/app/Applications/Inbox.kdapplication/AppController.coffee
@@ -116,7 +116,7 @@
       KDEventTypes  : 'MessageShouldBeSent'
       listener      : @
       callback      : (pubInst,{formOutput,callback})->
-        @prepareMessage formOutput,callback
+        @prepareMessage formOutput, callback, newMessageBar

     newMessageBar.registerListener
       KDEventTypes: 'MessageShouldBeDisowned'
@@ -180,14 +180,15 @@
     # log "I just send a new message: ", messageDetails
     bongo.api.JPrivateMessage.create messageDetails, callback

-  prepareMessage:(formOutput, callback)=>
+  prepareMessage:(formOutput, callback, newMessageBar)=>
     {body, subject, recipients} = formOutput

     to = recipients.join ' '

-    @sendMessage {to, body, subject}, (err, message)->
+    @sendMessage {to, body, subject}, (err, message)=>
       new KDNotificationView
         title     : if err then "There was an error sending your message - try again" else "Message Sent!"
         duration  : 1000
       message.mark 'read'
+      newMessageBar.emit 'RefreshButtonClicked'
       callback? err, message


chat: almost working chat

chat: almost working chat

- awaits properly-implemented wildcard event emitter

--- client/app/Applications/Chat.kdapplication/AppController.coffee
+++ client/app/Applications/Chat.kdapplication/AppController.coffee
@@ -10,24 +10,30 @@

     notificationController = KD.getSingleton 'notificationController'
     notificationController.on 'chatRequest', @bound 'handleChatRequest'
-    notificationController.on 'chatRoomOpen', @bound 'handleChatRoomOpen'
+    notificationController.on 'chatOpen', @bound 'handleChatOpen'

-  handleChatRoomOpen:({publishingToken, subscriptionToken})->
+    @channels       = {}
+    @conversations  = {}

+  handleChatOpen:({routingKey, bindingKey, publicName})->
+    channel = KD.remote.mq.setP2PKeys publicName, { routingKey, bindingKey }, 'secret'

   create:(invitees, callback)->
     {JChatConversation} = KD.remote.api
     JChatConversation.create invitees, (err, chatConversation)=>
       return callback err  if err
-      chatChannel = @subscribe chatConversation.publicName
-      callback null, chatChannel
+
+      @handleChatRequest {
+        invitee     : KD.whoami().profile.nickname
+        publicName  : chatConversation.publicName
+      }, callback

   subscribe:(publicName)->
     options = { serviceType: 'chat', isP2P: yes, exchange: 'chat' }
     channel = KD.remote.subscribe publicName, options
     channel.on 'message', @bound 'handleChatMessage'

-  handleChatRequest:(request)->
+  handleChatRequest:(request, callback)->
     {invitee, publicName} = request

     if invitee isnt KD.whoami().profile.nickname
@@ -35,6 +41,8 @@

     chatChannel = @subscribe publicName

-    console.log {chatChannel}
+    @channels[publicName] = chatChannel
+
+    callback? null, chatChannel

   handleChatMessage:-> console.log arguments


SidebarMachineSharePopup: Handle collaboration routes.

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -124,7 +124,8 @@

   approve: ->

-    { jMachine } = @getData()
+    machine      = @getData()
+    { jMachine } = machine

     @approveButton.showLoader()
     jMachine.approve (err) =>
@@ -132,8 +133,13 @@
       { router, mainView } = kd.singletons

       doNavigation = =>
+        route = "/IDE/#{jMachine.uid}/my-workspace" # permanent shared route
+
+        unless machine.isPermanent() # collaboration route
+          route = "/IDE/#{@getOptions().channelId}"
+
         # route to permanent shared url to open the ide
-        router.handleRoute "/IDE/#{jMachine.uid}/my-workspace"
+        router.handleRoute route

         # defer sidebar redraw sidebar to properly select workspace
         kd.utils.defer =>


Corrected far planet normals (too noisy due to incorrect normal delta step)

--- shaders/FarMapGeneratorShader.coffee
+++ shaders/FarMapGeneratorShader.coffee
@@ -42,7 +42,7 @@
         return a;
 }

-#define ONE_TEXEL (1.0/512.0)
+#define ONE_TEXEL (1.0/128.0)

 vec4 positionAndHeight(vec3 cubePos)


Use underscore node module

Use underscore node module

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -1,7 +1,7 @@
 {View} = require 'space-pen'
 Editor = require 'editor'
 $ = require 'jquery'
-_ = require 'underscore'
+_ = nodeRequire 'underscore'
 fs = require 'fs'

 module.exports =


quick typo

quick typo

--- components/panel/logic.coffee
+++ components/panel/logic.coffee
@@ -3,4 +3,4 @@
     template_name: (tname) ->
     	tname = tname.substring(1,tname.length-1)
     	console.log tname
-    	return Template[tname];
+    	return Template[tname]


Rewrite development config

Rewrite development config

--- config/env/development.coffee
+++ config/env/development.coffee
@@ -2,16 +2,12 @@

 module.exports =
   db:
-    url: 'mongodb://localhost:27017/node-tt'
     adapters:
       mongo: mongoAdapter
     connections:
       default:
         adapter: 'mongo'
-        module: 'sails-mongo'
-        host: 'localhost'
-        port: 27017
-        database: 'node-tt'
+        url: 'mongodb://localhost:27017/node-tt'
     defaults:
       migrate: 'alter'
       autoPK: true


remove blank lines

remove blank lines

--- test/account_tests.coffee
+++ test/account_tests.coffee
@@ -130,5 +130,3 @@
             instance = instances.rows[0]
             instance.domain.should.equal 'domain.newnew'
             done()
-
-


Increase number of workers

Increase number of workers

--- config/main.production.coffee
+++ config/main.production.coffee
@@ -31,7 +31,7 @@
   webserver     :
     login       : 'prod-webserver'
     port        : 3020
-    clusterSize : 2
+    clusterSize : 6
     queueName   : socialQueueName+'web'
     watch       : no
   mongo         : mongo
@@ -65,11 +65,11 @@
     login       : 'prod-auth-worker'
     queueName   : socialQueueName+'auth'
     authResourceName: 'auth'
-    numberOfWorkers: 2
+    numberOfWorkers: 6
     watch       : no
   social        :
     login       : 'prod-social'
-    numberOfWorkers: 2
+    numberOfWorkers: 6
     watch       : no
     queueName   : socialQueueName
   cacheWorker   :


geometry-test: fix exception testing

geometry-test: fix exception testing

--- test/geometry-test.coffee
+++ test/geometry-test.coffee
@@ -229,14 +229,9 @@
       geom.isValid (error, isValid) ->
         assert.isFalse isValid

-    #TODO fix this text
-    #"should throw an error on spatial operations": (geom) ->
-    #  try
-    #    geom.getArea()
-    #  catch e
-    #    console.log "test"
-    #    assert.instanceOf e, Error
-
-
+    "should throw an error on spatial operations": (geom) ->
+      fn = ->
+        geom.intersects geom
+      asseeort.throws fn, Error

 tests.export module


Refactor of BuildExpressionFromJavascriptObject

--- javascripts/lib/math/build_expression_from_javascript_object.coffee
+++ javascripts/lib/math/build_expression_from_javascript_object.coffee
@@ -17,31 +17,107 @@
         @exponentiation_builder = @opts.exponentiation_builder || components.exponentiation
         @blank_builder = @opts.blank_builder || components.blank
         @multiplication_builder = @opts.multiplication_builder || components.multiplication
+        @equals_builder = @opts.equals_builder || components.equals

-      proc_method = (object_to_convert)->
-        return @blank_builder.build() unless object_to_convert
-        if object_to_convert instanceof Array
-          @convertSubExpression(object_to_convert)
-        else
-          switch typeof(object_to_convert)
-            when "number" then @number_builder.build(value: object_to_convert)
-            when "string"
-              obj = object_to_convert
-              switch
-                when obj == "+" then @addition_builder.build()
-                when obj == "-" then @subtraction_builder.build()
-                when obj == "/" then @division_builder.build()
-                when obj == "*" then @multiplication_builder.build()
-                when @matchesNumberRegexp(obj)
-                  @numberFromString(obj)
-                else throw "STRING NOT IMPLEMENTED"
-            when "object" then @convertObject(object_to_convert)
-      process: logger().instrument(name: "Process", fn: proc_method)
+        @processor = _JSObjectExpressionProcessor.build()

+        @exponentiation_converter = _FromExponentiationObject.build(
+          @processor,
+          @exponentiation_builder)

-      matchesNumberRegexp: (str)->
-        str.search(/\d+/) != -1
+        @string_literal_converter = _FromStringLiteralObject.build(
+          "+": @addition_builder
+          "-": @subtraction_builder
+          "/": @division_builder
+          "*": @multiplication_builder
+          "=": @equals_builder
+        )
+
+        @closed_expression_converter = _FromClosedExpressionObject.build(
+          @expression_builder,
+          @processor)
+
+        @number_converter = _FromNumberObject.build(@number_builder)
+        @open_expression_converter = _FromOpenExpressionObject.build(@closed_expression_converter)
+
+        @processor.converters [
+          @closed_expression_converter
+          @open_expression_converter
+          @number_converter
+          @exponentiation_converter
+          @string_literal_converter
+          ]
+
+      process: (js_object)->
+        @processor.process(js_object)
+
+      processBuildExpression: (data)->
+        @process(data)
+
+    class_mixer BuildExpressionFromJavascriptObject
+
+    class _JSObjectExpressionProcessor
+      converters: (@js_object_converters)->
+
+      process: (js_object)->
+        for converter in @js_object_converters
+          if converter.isType(js_object)
+            return converter.convert(js_object)
+        throw "Unhandled js object: #{JSON.stringify js_object}"
+    class_mixer _JSObjectExpressionProcessor
+
+    class _FromOpenExpressionObject
+      initialize: (@expression_converter)->
+      isType: (js_object)->
+        js_object['open_expression'] != undefined
+
+      convert = (js_object)->
+        subexp = js_object['open_expression']
+        maybe_wrapped =
+          if typeof subexp == "number"
+            [subexp]
+          else if subexp == null || subexp == false
+            []
+          else if @isType(subexp) # this open expression contains another open expression
+            [subexp]
+          else subexp
+        @expression_converter.convert(maybe_wrapped).open()
+
+      convert: logger().instrument(name: "_FromOpenExpressionObject#convert", fn: convert)
+
+    class_mixer _FromOpenExpressionObject
+
+    class _FromClosedExpressionObject
+      initialize: (@expression_builder, @processor)->
+      isType: (js_object)->
+        typeof js_object == "object" && js_object instanceof Array
+
+      convert: (js_object)->
+        exp = @expression_builder.build()
+        for part in js_object
+          converted_part = @processor.process(part)
+          exp = exp.append(converted_part)
+        exp
+    class_mixer _FromClosedExpressionObject
+
+    class _FromNumberObject
+      initialize: (@number_builder)->
+
+      isType: (js_object)->
+        @isJSNumber(js_object) or @isStringNumber(js_object)
+
+      isJSNumber: (js_object)->
+        typeof js_object == "number"
+
+      isStringNumber: (js_object)->
+        typeof js_object == "string" and js_object.search(/\d+/) != -1
+
+      convert: (js_object)->
+        if @isJSNumber(js_object)
+          @number_builder.build(value: js_object)
+        else if @isStringNumber(js_object)
+          @numberFromString(js_object)

       numberFromString: (str)->
         if parsed = str.match /(\d+)(\.)(\d+)/
@@ -51,51 +127,47 @@
         else if parsed = str.match /(\d+)/
           @number_builder.build(value: parsed[1])

-      # privates
-      convertSubExpression: (parts) ->
-        exp = @expression_builder.build()
-        for x in parts
-          converted_part = @process(x)
-          exp = exp.append(converted_part)
-        exp
+    class_mixer _FromNumberObject

-      convertObject: (object)->
-        if (it = object['^'])
-          @convertExponentiation(it)
-        else if ((it = object['open_expression']) != undefined)
-          subexp = @convertImplicitSubexp(it)
-          subexp.open()
-        else
-          throw "Build Exp not recognized"
-
-      convertExponentiation: (data)->
-        base = @convertImplicitSubexp(data[0])
-        power = @convertImplicitSubexp(data[1])
+    class _FromExponentiationObject
+      initialize: (@processor, @exponentiation_builder)->
+      isType: (js_object)-> js_object['^'] instanceof Array
+      convert: (js_object)->
+        base = @convertImplicitSubexp(js_object['^'][0])
+        power = @convertImplicitSubexp(js_object['^'][1])

         @exponentiation_builder.build(
           base: base
           power: power
         )

-      convertImplicitSubexp = (subexp)->
-        processed = @process(subexp)
-        if typeof subexp == "number"
-          @expression_builder.build(expression: [processed])
-        else if subexp == null || subexp == false
-          @expression_builder.build(expression: [])
-        else if subexp instanceof Array
-          processed
-        else
-          @expression_builder.build(expression: [processed])
+      convertImplicitSubexp: (subexp)->
+        maybe_wrapped =
+          if typeof subexp == "number"
+            [subexp]
+          else if subexp == null || subexp == false
+            []
+          else
+            subexp
+        processed = @processor.process(maybe_wrapped)
+    class_mixer _FromExponentiationObject

-      convertImplicitSubexp: logger().instrument(name: "convertImplicitSubexp", fn: convertImplicitSubexp)
+    class _FromStringLiteralObject
+      initialize: (@literal_mappings)->
+        @keys = _.keys(@literal_mappings)

-    class_mixer BuildExpressionFromJavascriptObject
+      isType: (js_object)->
+        @keys.indexOf(js_object) != -1
+
+      convert: (js_object)->
+        @literal_mappings[js_object].build()
+
+    class_mixer _FromStringLiteralObject

     BuildExpressionFromJavascriptObject.buildExpression = ->
       builder = BuildExpressionFromJavascriptObject.build()
       arguments_as_array = Array.prototype.slice.call(arguments, 0)
-      converted_part = builder.process(arguments_as_array)
+      converted_part = builder.processBuildExpression(arguments_as_array)
       logger().info("returned converted from process in buildExpression", converted_part.toString(), arguments_as_array)
       converted_part


create large archive, test results

create large archive, test results

--- test/large_archive.coffee
+++ test/large_archive.coffee
@@ -5,18 +5,18 @@
 zip = require '../'

 tmpPath = path.resolve __dirname, 'tmp'
-zipFilePath = path.resolve tmpPath, 'output.zip'
+zipFilePath = path.resolve tmpPath, 'large_output.zip'
 dirs = ['', 'one', 'two/three-ohhhh', 'four/five 67/eight-8']

 test 'create files with faker', (t) ->
   # create all the data
   dirs.forEach (dir) ->
     contents = ''
-    [1..30].forEach (file) ->
+    [1..30].forEach (file, i) ->
       dirPath = path.resolve tmpPath, dir
       filename = Faker.Internet.userName()
       contents += Faker.Lorem.paragraphs()
-      fs.outputFileSync "#{dirPath}/#{filename}", contents
+      fs.outputFileSync "#{dirPath}/#{i}-#{filename}", contents

   # check that the dirs exist
   dirs.forEach (dir) ->
@@ -27,9 +27,13 @@
 test 'create zip from fake files', (t) ->
   zip.setOutput zipFilePath

-  zip.add tmpPath, (err) ->
+  zip.add "**",
+    cwd: tmpPath
+  , (err) ->
     t.error err, 'files queued up for archiving'
+    filelist = zip.getFiles()
+    t.true (filelist.length >= 100), 'archive has more than 100 files'
     zip.compress (err, bytes) ->
       t.error err, 'archive is created'
-      t.true (bytes > 0), 'archive has content'
-      t.end()+      t.true (bytes > 200), 'archive is larger than 200 bytes'
+      t.end()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/state.coffee
+++ test/state.coffee
@@ -1,3 +1,4 @@
+###
 test "State events should transition properly", ->
   animation = GameObject
     initialState: 'stand'
@@ -18,5 +19,6 @@
   animation.update()

   equals animation.currentState(), "stand"
+###


KDFramework: minor

KDFramework: minor

--- client/Framework/components/inputs/KDOnOffSwitch.coffee
+++ client/Framework/components/inputs/KDOnOffSwitch.coffee
@@ -5,8 +5,8 @@
 class KDOnOffSwitch extends KDInputView
   constructor:(options = {}, data)->

+    options.type           = "switch"
     options.title        or= ""
-    options.type         or= "switch"       # mandatory
     options.size         or= "small"        # a String tiny/small/big
     options.labels       or= ["ON", "OFF"]
     options.defaultValue  ?= off


Client: Activitiy: Added `is channel exist` check

--- client/activity/lib/components/messagelink/index.coffee
+++ client/activity/lib/components/messagelink/index.coffee
@@ -22,11 +22,13 @@
       channelId = message.get 'initialChannelId'
       channel = kd.singletons.socialapi.retrieveCachedItemById channelId

-      href = "/Channels/#{channel.name.toLowerCase()}"
-      href += '/summary'  if @props.isSummaryActive
-      href += '/'
-
-    href += message.get 'slug'
+      if channel
+        href = "/Channels/#{channel.name.toLowerCase()}"
+        href += '/summary'  if @props.isSummaryActive
+        href += '/'
+        href += message.get 'slug'
+      else
+        href = '/'

     return href


activity: remove unused module require

activity: remove unused module require

--- client/app/lib/components/scroller/scrollermixin.coffee
+++ client/app/lib/components/scroller/scrollermixin.coffee
@@ -1,5 +1,4 @@
-React          = require 'kd-react'
-scrollToTarget = require 'activity/util/scrollToTarget'
+React = require 'kd-react'

 module.exports = ScrollerMixin =


simplify settings crumb text

simplify settings crumb text

--- js/inner/crumb.coffee
+++ js/inner/crumb.coffee
@@ -35,7 +35,7 @@
     super(options)

   name: () =>
-    "Edit settings"
+    "settings"

   styles: () =>
     @komp =>


.$el -> .el

.$el -> .el

--- django/bb_chat/assets/scripts/views.coffee
+++ django/bb_chat/assets/scripts/views.coffee
@@ -68,7 +68,7 @@

   addMessage: (message) =>
     messageView = new MessageView model: message
-    @$el.append messageView.render().$el
+    @$el.append messageView.render().el
     this

   scrollToBottom: =>


Use data-attachment on the root of the attachment

--- lib/written/app/assets/javascripts/written/attachments/image.coffee
+++ lib/written/app/assets/javascripts/written/attachments/image.coffee
@@ -6,8 +6,8 @@
     @node.querySelector('img').addEventListener 'click', @select.bind(this, @node)

   template: ->
-    Written.toHTML("

-

+    Written.toHTML("

+

             <button data-action='cancel'>Cancel</button>


Catch and re-throw Not permitted errors

Catch and re-throw Not permitted errors

This allows softRemove and restore to use any selector in stubs without
extra checks.

--- softremovable.coffee
+++ softremovable.coffee
@@ -140,19 +140,21 @@
   @collection.softRemove = (selector, callback) ->
     return 0 unless selector

-    if Meteor.isClient and (not Meteor.isSimulation or not isLocalCollection)
-      throwIfSelectorIsntId selector, 'softRemove'
-
     modifier =
       $set: $set = {}

     $set[removed] = true

-    if Meteor.isServer or isLocalCollection
-      ret = @update selector, modifier, multi: true, callback
+    try
+      if Meteor.isServer or isLocalCollection
+        ret = @update selector, modifier, multi: true, callback

-    else
-      ret = @update selector, modifier, callback
+      else
+        ret = @update selector, modifier, callback
+
+    catch error
+      if error.reason.indexOf 'Not permitted.' isnt -1
+        throwIfSelectorIsntId selector, 'softRemove'

     if ret is false
       0
@@ -162,21 +164,23 @@
   @collection.restore = (selector, callback) ->
     return 0 unless selector

-    if Meteor.isClient and (not Meteor.isSimulation or not isLocalCollection)
-      throwIfSelectorIsntId selector, 'restore'
-
     modifier =
       $unset: $unset = {}

     $unset[removed] = true

-    if Meteor.isServer or isLocalCollection
-      selector = _.clone selector
-      selector[removed] = true
-      ret = @update selector, modifier, multi: true, callback
+    try
+      if Meteor.isServer or isLocalCollection
+        selector = _.clone selector
+        selector[removed] = true
+        ret = @update selector, modifier, multi: true, callback

-    else
-      ret = @update selector, modifier, callback
+      else
+        ret = @update selector, modifier, callback
+
+    catch error
+      if error.reason.indexOf 'Not permitted.' isnt -1
+        throwIfSelectorIsntId selector, 'restore'

     if ret is false
       0


Move menu item to Edit menu.

Move menu item to Edit menu.

--- menus/execute-as-ruby.cson
+++ menus/execute-as-ruby.cson
@@ -5,11 +5,11 @@

 'menu': [
   {
-    'label': 'Packages'
+    'label': 'Edit'
     'submenu': [
-      'label': 'Execute as Ruby'
+      'label': 'Text'
       'submenu': [
-        { 'label': 'Execute', 'command': 'execute-as-ruby:execute' }
+        { 'label': 'Execute as Ruby', 'command': 'execute-as-ruby:execute' }
       ]
     ]
   }


add an LToolkit doc

add an LToolkit doc

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -87,6 +87,11 @@
         @inject context, blob for blob in uniques
         return do => next.call this, undefined

+# A complementary part of the preflight procedures that provides the
+# ability to create and emit arbitrary linkage in the contexts that
+# are going to be assembled within the current preflight hierarchy.
+# The facilities of this toolkit should typically be used when you
+# need to link to a static asset file, within any domain or path.
 module.exports.LToolkit = class LToolkit extends RToolkit

     # This is a marker that indicates to some internal subsystems


Improve the brunch config.

Improve the brunch config.

--- brunch-config.coffee
+++ brunch-config.coffee
@@ -17,7 +17,7 @@
         autoReload:
             enabled:
                 js: on
-                scss: on
+                css: on
                 assets: on
         sass:
           options:


non essential refactoring in the bilateral

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -53,7 +53,7 @@
 # compounds adds the service-to-client communication on top of that.
 # The component itself is built heavily on top of a code emission
 # and delivery platform, as implemented by `Screenplay` service.
-module.exports.Bilateral = class Bilateral extends DuplexCore
+assert module.exports.Bilateral = class Bilateral extends DuplexCore

     # This is a marker that indicates to some internal subsystems
     # that this class has to be considered abstract and therefore
@@ -68,16 +68,23 @@
     # This is the place where you would be importing the dependencies.
     # Pay attention that most implementations side effect the context.
     prelude: (symbol, context, request, next) ->
-        _.forIn this, (value, name, service) =>
+        assert _.isString(symbol), "cannot found symbol"
+        assert _.isObject(context), "located no context"
+        assert _.isObject(request), "located no request"
+        assert _.isFunction(next), "got no next function"
+        identify = try @constructor.identify().underline
+        message = "Executing the bilateral linkage in %s"
+        logger.debug message.yellow, identify.toString()
+        execution = (arg) => next.call this, undefined
+        execution _.forIn this, (value, name, service) =>
             setter = "#{symbol}.#{name}.%s = (%s)"
             directives = value?.uplink?.directives
             return unless _.isPlainObject directives
             assert json = try JSON.stringify directives
             template = format setter, "directives", json
             context.invokes.push "\r\n#{template}\r\n"
-            uplinks = context.uplinks ?= new Object
-            uplinks[name] = directives; return @
-        return next.call this, undefined
+            uplinks = context.uplinks ?= new Object()
+            uplinks[name] = directives; return this

     # Declarate the supplied implementation function as the uplink.
     # An uplink is an external (remote) function published on socket
@@ -110,15 +117,18 @@
     # socket channel that makes it available for the invocation by
     # the corresponding server site facilities implemented belows.
     bilateral: @autocall z: +102, ->
-        assert _.isFunction o = -> _.head arguments
-        assert _.isFunction i = -> _.head arguments
-        uplinking = "Uplink %s at #{@location}, nsp=%s"
-        _.forIn this, (value, reference, context) =>
-            return unless reference of (@uplinks or {})
-            assert mangled = "#{@location}/#{reference}"
-            mangled += "/#{nsp}" if _.isString nsp = @nsp
-            assert value; return @socket.on mangled, =>
-                try logger.info uplinking, reference, nsp
+        assert _.isFunction o = -> try _.head arguments
+        assert _.isFunction i = -> try _.head arguments
+        assert _.isString(@service), "invalid service data"
+        assert _.isString(@location), "location misconfied"
+        assert _.isString(@nsp), "bilateral nsp malfunction"
+        try uplinking = "Uplink %s at #{@location}, nsp=%s"
+        _.forIn this, (value, reference, context) => do =>
+            return unless reference of (this.uplinks or {})
+            assert mangled = try "#{@location}/#{reference}"
+            mangled += "/#{nsp}" if _.isString nsp = this.nsp
+            assert value; return this.socket.on mangled, =>
+                try logger.info uplinking, reference, nsp or 0
                 value.call this, i(arguments)..., (params...) =>
                     id = @socket.sacks = (@socket.sacks ?= 0) + 1
                     ack = type: "ack", name: mangled, ack: "data"
@@ -142,10 +152,10 @@
         return (sequence..., callback) => # a complex sig
             parameters = _.reject arguments, _.isFunction
             callback = (->) unless _.isFunction callback
-            assert _.isFunction(callback or 0), notify
-            assert mangled = "#{@location()}/#{name}"
-            assert binder = socket.binder, noBinder
-            mangled += "/#{nsp}" if nsp = binder.nsp
+            assert _.isFunction(callback or null), notify
+            assert mangled = try "#{@location()}/#{name}"
+            assert (try binder = socket.binder), noBinder
+            mangled += "/#{nsp}" if nsp = try binder.nsp
             try logger.debug uplinking.cyan, name.bold
             socket.emit mangled, o(parameters)..., a = =>
                 key = _.findKey socket.acks, (x) -> x is a


Added rulepreview-mask id.

Added rulepreview-mask id.

--- src/DOM.coffee
+++ src/DOM.coffee
@@ -28,7 +28,8 @@
             'RULE_DROPDOWN':'wolfcage-generator-select-input',
             'RULE_GENERATE_BUTTON':'wolfcage-generator-generate-button',
             'COLORPICKER_BUTTON':'wolfcage-generator-colorpicker-button',
-            'THUMBMONTAGE_BUTTON':'wolfcage-generator-thumbmontage-button'
+            'THUMBMONTAGE_BUTTON':'wolfcage-generator-thumbmontage-button',
+            'RULEPREVIEW_MASK':'wolfcage-rulepreview-mask '
         },
         'COLORPICKER':{
             'CONTAINER':'wolfcage-colorpicker',


remove uglify stage

remove uglify stage

--- lineman/config/application.coffee
+++ lineman/config/application.coffee
@@ -16,6 +16,7 @@
   loadNpmTasks: ["grunt-angular-templates", "grunt-concat-sourcemap", "grunt-ng-annotate", "grunt-haml", 'grunt-sass', 'grunt-cucumber', 'grunt-contrib-copy', 'grunt-exec']

   removeTasks:
+    dist: ['uglify']
     common: ["handlebars", "jst", 'less', 'pages', 'concat_sourcemap', 'pages:dev']

   prependTasks:


squash! Incremental updates to fix UI

squash! Incremental updates to fix UI

--- picoCTF-web/web/coffee/navbar.coffee
+++ picoCTF-web/web/coffee/navbar.coffee
@@ -54,7 +54,6 @@
   About: "/about"
   Scoreboard: "/scoreboard"
   FAQ: "/faq"
-  Learn: "/learn"
   Teachers: "/teachers"
   Login: "/login"


Added the ability to select columns to return from find()

--- server/common/crudModelFactory.coffee
+++ server/common/crudModelFactory.coffee
@@ -59,6 +59,9 @@
       command = Resource.find(query).sort(sort).skip(skipFrom).limit(max)
       if options.populate?
         command.populate populate
+
+      if options.select?
+        command.select options.select

       command.exec (err, results) ->
         if err


imagecache: remove rebuild parameter

imagecache: remove rebuild parameter

--- servers/lib/server/image_cache.coffee
+++ servers/lib/server/image_cache.coffee
@@ -15,7 +15,7 @@
 fs.mkdir imagePath, null, ->

 module.exports = (req, res) ->
-  {endpoint, grow, width, height, url, rebuild} = req.query
+  {endpoint, grow, width, height, url} = req.query

   fullUrl = "https://i.embed.ly/1/display/#{endpoint}?" +
              "width=#{width}&" +
@@ -54,8 +54,7 @@

     res.writeHead(200, { 'Content-Type': mimeType })

-  # rebuild param forces cache to fetch image from embedly
-  if fs.existsSync filename and rebuild?
+  if fs.existsSync filename
     return serveFile filename, res

   http.get fullUrl, (response)->


Correct actions for teacher student store

--- src/api.coffee
+++ src/api.coffee
@@ -166,7 +166,8 @@
     LearningGuide.Teacher.actions.loaded, 'GET', (id) -> url: "/api/courses/#{id}/teacher_guide"

   apiHelper LearningGuide.TeacherStudent.actions, LearningGuide.TeacherStudent.actions.load,
-    LearningGuide.Teacher.actions.loaded, 'GET', (id, roleId) -> url: "/api/courses/#{id}/guide/role/#{roleId}"
+    LearningGuide.TeacherStudent.actions.loaded, 'GET', (id, {roleId}) ->
+      url: "/api/courses/#{id}/guide/role/#{roleId}"

   apiHelper PerformanceActions, PerformanceActions.load, PerformanceActions.loaded, 'GET', (id) ->
     url: "/api/courses/#{id}/performance"


be more selective to allow multiple ajax sources

--- src/app.coffee
+++ src/app.coffee
@@ -22,11 +22,12 @@
     o

   $.ajaxPrefilter (options, originalOptions, jqXHR) ->
-    options.url = "http://researchdb.warwick.ac.uk/liverpool/api" + options.url
     if options.url.split(":")[0] != "http"
+      options.url = "http://researchdb.warwick.ac.uk/liverpool/api" + options.url
     return

   Address = Backbone.RelationalModel.extend()
+
   Addresses = Backbone.Collection.extend(
     model: Address
   )
@@ -164,10 +165,6 @@
     model: Country
   )

-
-
-
-
   PeopleListView = Backbone.View.extend(
     el: "#page"
     initialize: ->


Use consistent naming.

Use consistent naming.

--- src/app.coffee
+++ src/app.coffee
@@ -1,6 +1,6 @@
 Route = require './route'

-class Application
+class App
   constructor: (state = {}) ->
     @state   = state
     @_routes = {}
@@ -51,4 +51,4 @@
   delete: (k) ->
     delete @state[k]

-module.exports = Application
+module.exports = App


Fixed a typ

Fixed a typ

--- src/configure.coffee
+++ src/configure.coffee
@@ -95,7 +95,7 @@

   @param basedir Directory `file` is in.
   @param file Name of the file to parse.
-  @return {Promise<Object?} Resulting merged configuration.
+  @return {Promise<Object>} Resulting merged configuration.
   @private
 ###
 process = (basedir, file) ->


use hx.unique

use hx.unique

--- modules/data-table/main/index.coffee
+++ modules/data-table/main/index.coffee
@@ -35,7 +35,7 @@
     options[name]

 toCriteriaItems = (list) ->
-  new hx.Set(list).values().map (item) ->
+  hx.unique(list).map (item) ->
     {
       value: item,
       text: hx.userFacingText('dataTable', item)


Add raw string to "Text" help

Add raw string to "Text" help

LaTeX needs r" (raw strings) to properly escape \ tokens.

--- salvus/page/buttonbar.coffee
+++ salvus/page/buttonbar.coffee
@@ -660,7 +660,7 @@
         plot_random_walk:
             insert : "stats.TimeSeries(1000).randomize('normal').sums().plot()"
         plot_text:
-            insert : 'text("Text and LaTeX: $\\alpha^3 + 1$", (1,1), color="black", fontsize=15, rotation=30)'
+            insert : 'text(r"Text and LaTeX: $\\alpha^3 + 1$", (1,1), color="black", fontsize=15, rotation=30)'
         plot_points:
             insert : "show(points([(1,0), (sqrt(2)/2,sqrt(2)/2), (0,1), (1/2,1/2)], color='darkgreen', pointsize=50), aspect_ratio=1)"
         plot3d:


Fixed close of MySQL driver.

Fixed close of MySQL driver.

For PostgreSQL we allow the user to send a query prior to closing the
connection, to perform some sort of clean up. I can't recall the
motivation for this option, I can't recall why a PostgreSQL connection
cannot simply be disconnected. Hopefully I'll remember before I set out
to document the API.

The MySQL driver close now has the same signature.

--- src/lib/mysql.coffee
+++ src/lib/mysql.coffee
@@ -51,12 +51,10 @@

   sql: (query, parameters, callback) ->
     try
-      say "HERE"
       @_client.query query, parameters, callback
     catch error
       console.error "CLOSING"
-      @close()
-      callback error
+      @close null, -> callback error

   close: (terminator, callback) ->
     @_client.destroy()


preferences: indent the locale tests so they read nicer

--- modules/preferences/test/spec.coffee
+++ modules/preferences/test/spec.coffee
@@ -5,46 +5,47 @@
       expect(hx.preferences.supportedLocales(list)).toEqual(hx.preferences)
       expect(hx.preferences.supportedLocales()).toEqual(list)

-    it 'locale: should not be possible to explicitly clear the locale', ->
-      # sanity check
-      expect(hx.preferences.locale()).toBeDefined()
+    describe 'locale', ->
+      it 'should not be possible to explicitly clear the locale', ->
+        # sanity check
+        expect(hx.preferences.locale()).toBeDefined()

-      hx.preferences.locale undefined
-      expect(hx.preferences.locale()).toBeDefined()
+        hx.preferences.locale undefined
+        expect(hx.preferences.locale()).toBeDefined()

+      it 'setter/getter', ->
+        expect(hx.preferences.locale('vi')).toEqual(hx.preferences)
+        expect(hx.preferences.locale()).toEqual('vi')

-    it 'locale: setter/getter', ->
-      expect(hx.preferences.locale('vi')).toEqual(hx.preferences)
-      expect(hx.preferences.locale()).toEqual('vi')
+      it 'setter/getter with alternative casing', ->
+        expect(hx.preferences.locale('en-GB')).toEqual(hx.preferences)
+        expect(hx.preferences.locale()).toEqual('en-GB')

-    it 'locale: setter/getter with alternative casing', ->
-      expect(hx.preferences.locale('en-GB')).toEqual(hx.preferences)
-      expect(hx.preferences.locale()).toEqual('en-GB')
+      it 'setter/getter should correct the casing', ->
+        expect(hx.preferences.locale('en-gb')).toEqual(hx.preferences)
+        expect(hx.preferences.locale()).toEqual('en-GB')

-    it 'locale: setter/getter should correct the casing', ->
-      expect(hx.preferences.locale('en-gb')).toEqual(hx.preferences)
-      expect(hx.preferences.locale()).toEqual('en-GB')
+      it 'dont emit when setting to the same value', ->
+        hx.preferences.locale('en-GB')
+        called = false
+        hx.preferences.on 'localechange', -> called = true
+        hx.preferences.locale('en-GB')
+        expect(called).toEqual(false)

-    it 'locale: dont emit when setting to the same value', ->
-      hx.preferences.locale('en-GB')
-      called = false
-      hx.preferences.on 'localechange', -> called = true
-      hx.preferences.locale('en-GB')
-      expect(called).toEqual(false)
+      it 'emit when setting to new value', ->
+        hx.preferences.locale('en-GB')
+        called = false
+        hx.preferences.on 'localechange', -> called = true
+        hx.preferences.locale('en-us')
+        expect(called).toEqual(true)

-    it 'locale: dont emit when setting to the same value', ->
-      hx.preferences.locale('en-GB')
-      called = false
-      hx.preferences.on 'localechange', -> called = true
-      hx.preferences.locale('en-us')
-      expect(called).toEqual(true)
+      it 'setter/getter for non supported value', ->
+        spyOn(hx, 'consoleWarning')
+        expect(hx.preferences.locale('vi')).toEqual(hx.preferences)
+        expect(hx.preferences.locale('lemon')).toEqual(hx.preferences)
+        expect(hx.preferences.locale()).toEqual('vi')
+        expect(hx.consoleWarning).toHaveBeenCalled()

-    it 'locale: setter/getter for non supported value', ->
-      spyOn(hx, 'consoleWarning')
-      expect(hx.preferences.locale('vi')).toEqual(hx.preferences)
-      expect(hx.preferences.locale('lemon')).toEqual(hx.preferences)
-      expect(hx.preferences.locale()).toEqual('vi')
-      expect(hx.consoleWarning).toHaveBeenCalled()
     describe 'timezone', ->
       it 'setter/getter', ->
         expect(hx.preferences.timezone('UTC+01:00')).toEqual(hx.preferences)


rename preDraw to beforeDraw to follow naming conventions

--- test/engine.coffee
+++ test/engine.coffee
@@ -74,7 +74,7 @@
 test "draw events", 2, ->
   engine = Engine()

-  engine.bind "preDraw", ->
+  engine.bind "beforeDraw", ->
     ok true

   engine.bind "draw", ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -183,7 +183,7 @@
 test "#setState", ->
   engine = Engine()

-  nextState =
+  nextState = GameState()

   engine.setState


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -21,6 +21,7 @@
   fastEnemy = GameObject
     x: 50
     y: 100
-    width:
+    width: 10
+    height: 10

 module()


dededug

dededug

--- test/lib/user.iced
+++ test/lib/user.iced
@@ -170,18 +170,12 @@
     unless (acct = config().get_dummy_account which)?
       await athrow (new Error "No dummy accounts available for '#{which}'"), esc defer()
     await eng.expect { pattern : (new RegExp "Your username on #{which}: ", "i") }, esc defer()
-    console.log "got username"
     await eng.sendline acct.username, esc defer()
-    console.log "sent accounT"
-    console.log acct
     await eng.expect { pattern : (new RegExp "Check #{which} now\\? \\[Y/n\\] ", "i") }, esc defer data
-    console.log "check now!"
     if (m = data.toString('utf8').match search_regex)?
       proof = m[1]
     else
       await athrow (new Error "Didn't get a #{which} text from the CLI"), esc defer()
-    console.log "got proof ->"
-    console.log proof
     await http_action acct, proof, esc defer proof_id
     await eng.sendline "y", esc defer()
     await eng.wait defer rc


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -85,7 +85,7 @@

     matrix.skew(0.125.turns, 0)

-
+    p = Point()

     equals(matrix.transformPoint(p))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -84,6 +84,8 @@
     matrix = Matrix()

     matrix.skew(0.125.turns, 0)
+
+    equals(tran)

   test ".rotation", ->
     matrix = Matrix.rotation(Math.PI / 2)


Testing Pepper with odd characters

Testing Pepper with odd characters

--- src/lunchtime.coffee
+++ src/lunchtime.coffee
@@ -66,7 +66,7 @@
       msg.send 'Total reviews: ' + randomBusiness.review_count
       msg.send randomBusiness.url
     else
-      msg.send "Couldn't find a place to eat there. Try again.ß"
+      msg.send "Couldn't find a place to eat there. Try again."

 module.exports = (robot) ->
   robot.respond /lunchtime\W*(near (.*) thats (.*)|near (.*)|thats (.*))?/i, (msg) ->


fixed typo in models test

fixed typo in models test

--- test/models.coffee
+++ test/models.coffee
@@ -71,7 +71,7 @@
       , Error
       done()

-    it "should throw an error when call getter whit non provider", (done) ->
+    it "should throw an error when getProvider is used before one is set", (done) ->
       dataprovider.providers = dataprovider.instances = {}
       assert.throws ->
         dataprovider.getProvider()


Re-enable display marker tests now that folding is implemented

--- spec/display-marker-layer-spec.coffee
+++ spec/display-marker-layer-spec.coffee
@@ -103,7 +103,7 @@
       updateEventCount is 4

   describe "findMarkers(params)", ->
-    markerLayer = null
+    [markerLayer, displayLayer] = []

     beforeEach ->
       buffer = new TextBuffer(text: SampleText)
@@ -121,38 +121,38 @@

     # TODO: Enable these tests once DisplayLayer supports folding so we actually
     # test row translation fully.
-    xit "allows the startScreenRow and endScreenRow to be specified", ->
+    it "allows the startScreenRow and endScreenRow to be specified", ->
       marker1 = markerLayer.markBufferRange([[6, 0], [7, 0]], class: 'a')
       marker2 = markerLayer.markBufferRange([[9, 0], [10, 0]], class: 'a')
-      markerLayer.createFold(4, 7)
+      displayLayer.foldBufferRange([[4, 0], [7, 0]])
       expect(markerLayer.findMarkers(class: 'a', startScreenRow: 6, endScreenRow: 7)).toEqual [marker2]

-    xit "allows intersectsBufferRowRange to be specified", ->
+    it "allows intersectsBufferRowRange to be specified", ->
       marker1 = markerLayer.markBufferRange([[5, 0], [5, 0]], class: 'a')
       marker2 = markerLayer.markBufferRange([[8, 0], [8, 0]], class: 'a')
-      markerLayer.createFold(4, 7)
+      displayLayer.foldBufferRange([[4, 0], [7, 0]])
       expect(markerLayer.findMarkers(class: 'a', intersectsBufferRowRange: [5, 6])).toEqual [marker1]

-    xit "allows intersectsScreenRowRange to be specified", ->
+    it "allows intersectsScreenRowRange to be specified", ->
       marker1 = markerLayer.markBufferRange([[5, 0], [5, 0]], class: 'a')
       marker2 = markerLayer.markBufferRange([[8, 0], [8, 0]], class: 'a')
-      markerLayer.createFold(4, 7)
+      displayLayer.foldBufferRange([[4, 0], [7, 0]])
       expect(markerLayer.findMarkers(class: 'a', intersectsScreenRowRange: [5, 10])).toEqual [marker2]

-    xit "allows containedInScreenRange to be specified", ->
+    it "allows containedInScreenRange to be specified", ->
       marker1 = markerLayer.markBufferRange([[5, 0], [5, 0]], class: 'a')
       marker2 = markerLayer.markBufferRange([[8, 0], [8, 0]], class: 'a')
-      markerLayer.createFold(4, 7)
+      displayLayer.foldBufferRange([[4, 0], [7, 0]])
       expect(markerLayer.findMarkers(class: 'a', containedInScreenRange: [[5, 0], [7, 0]])).toEqual [marker2]

-    xit "allows intersectsBufferRange to be specified", ->
+    it "allows intersectsBufferRange to be specified", ->
       marker1 = markerLayer.markBufferRange([[5, 0], [5, 0]], class: 'a')
       marker2 = markerLayer.markBufferRange([[8, 0], [8, 0]], class: 'a')
-      markerLayer.createFold(4, 7)
+      displayLayer.foldBufferRange([[4, 0], [7, 0]])
       expect(markerLayer.findMarkers(class: 'a', intersectsBufferRange: [[5, 0], [6, 0]])).toEqual [marker1]

-    xit "allows intersectsScreenRange to be specified", ->
+    it "allows intersectsScreenRange to be specified", ->
       marker1 = markerLayer.markBufferRange([[5, 0], [5, 0]], class: 'a')
       marker2 = markerLayer.markBufferRange([[8, 0], [8, 0]], class: 'a')
-      markerLayer.createFold(4, 7)
+      displayLayer.foldBufferRange([[4, 0], [7, 0]])
       expect(markerLayer.findMarkers(class: 'a', intersectsScreenRange: [[5, 0], [10, 0]])).toEqual [marker2]


Adds empty line at EOF

Adds empty line at EOF

--- coffee/turbase.coffee
+++ coffee/turbase.coffee
@@ -66,3 +66,4 @@
     collection.remove {'_id':mongodb.ObjectID(req.params.id), 'eier': req.eier}, {safe: true}, (err, status) ->
       res.jsonp "{'_id':#{req.params.id}, 'action':'delete', 'status':#{status}}" if not err
       res.jsonp JSON.stringify err if err
+


Minor fix in API.token.

Minor fix in API.token.

--- src/api/token.coffee
+++ src/api/token.coffee
@@ -12,7 +12,7 @@
   @throw 'UNAUTHORIZED' if not userId? or @session.has 'token_id'

   # Creates the token.
-  token = $wait @xo.tokens.generate userId
+  token = $wait @tokens.generate userId

   # Returns its identifier.
   token.id
@@ -24,11 +24,11 @@
   }

   # Gets the token.
-  token = $wait @xo.tokens.first tokenId
+  token = $wait @tokens.first tokenId
   @throw 'NO_SUCH_OBJECT' unless token?

   # Deletes the token.
-  $wait @xo.tokens.remove tokenId
+  $wait @tokens.remove tokenId

   # Returns true.
   true


update cli to use new selenium.start interface (aka, fix it)

--- src/cli.coffee
+++ src/cli.coffee
@@ -37,6 +37,7 @@
   logDirectory: logDirectory
   browser: browserName
   screenshotDirectory: "#{logDirectory}/screenshots"
+  seleniumServer: 'http://localhost:4444/wd/hub'

 csrepl = require 'coffee-script-redux/lib/repl'
 selenium = require './selenium'
@@ -65,7 +66,7 @@

   browser

-selenium.start logDirectory, 80, (error) ->
+selenium.start null, logDirectory, 80, (error) ->
   throw error if error?

   require 'coffee-script-redux/register'


Update cli.

Update cli.

--- src/cli.coffee
+++ src/cli.coffee
@@ -1,25 +1,26 @@
 program = require 'jade/node_modules/commander'
 version = require('../package.json').version

-list = (val) -> val.split ','
+module.exports = ->
+  list = (val) -> val.split ','

-program
-  .version(version)
-  .usage('--entry <input> [-output <output>, --libs <lib1,lib2>]')
-  .option('-e, --entry <entry-point>', 'entry point to your code')
-  .option('-o, --output <file>', 'where to compile your code to')
-  .option('-a, --after <files>', 'files which should be appended to compiled output', list)
-  .option('-b, --before <files>', 'files which should be prepended to compiled output', list)
-  .option('-p, --no-prelude', 'Do not include prelude')
-  .option('-n, --no-require-entry', 'Do not automatically require entry')
-  .option('-h, --no-compiler-hooks', 'Do not include scripts injected by compilers (i.e., jade runtime)')
-  .option('-m, --minify', 'minify bundled code')
-  .parse(process.argv)
+  program
+    .version(version)
+    .usage('--entry <input> [-output <output>, --libs <lib1,lib2>]')
+    .option('-e, --entry <entry-point>', 'entry point to your code')
+    .option('-o, --output <file>', 'where to compile your code to')
+    .option('-a, --after <files>', 'files which should be appended to compiled output', list)
+    .option('-b, --before <files>', 'files which should be prepended to compiled output', list)
+    .option('-p, --no-prelude', 'Do not include prelude')
+    .option('-n, --no-require-entry', 'Do not automatically require entry')
+    .option('-h, --no-compiler-hooks', 'Do not include scripts injected by compilers (i.e., jade runtime)')
+    .option('-m, --minify', 'minify bundled code')
+    .parse(process.argv)

-help = ->
-  console.log program.helpInformation()
-  process.exit()
+  help = ->
+    console.log program.helpInformation()
+    process.exit()

-help() unless program.entry
+  help() unless program.entry

-require('./build')(program)
+  require('./build')(program)


refactor(cli): move the int->tab.repeat out of the output function

--- src/cli.coffee
+++ src/cli.coffee
@@ -43,6 +43,10 @@

 module.exports = ->
+  # If the Tab argument is an integer, convert it to spaces
+  if cli.argv.tab is String parseInt cli.argv.tab
+    cli.argv.tab = ' '.repeat cli.argv.tab
+
   outputPath = cli.argv.output
   outputPath = path.resolve outputPath if outputPath

@@ -52,7 +56,6 @@
     JSON.stringify tokens, null, tab

   output = (outputPath, tokens, tab) ->
-    tab = ' '.repeat tab if tab is String parseInt tab
     tokensString = stringify tokens, tab
     if outputPath
       fs.writeFileSync outputPath, tokensString