gut the javascript generation code from nodes

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -29,38 +29,6 @@
 # being requested by the surrounding function), information about the current
 # scope, and indentation level.
 exports.Base = class Base
-
-  # Common logic for determining whether to wrap this node in a closure before
-  # compiling it, or to compile directly. We need to wrap if this node is a
-  # *statement*, and it's not a *pureStatement*, and we're not at
-  # the top level of a block (which would be unnecessary), and we haven't
-  # already been asked to return the result (because statements know how to
-  # return results).
-  compile: (o, lvl) ->
-    o        = extend {}, o
-    o.level  = lvl if lvl
-    node     = this
-    node.tab = o.indent
-    if o.level is LEVEL_TOP or not node.isStatement(o)
-      node.compileNode o
-    else
-      node.compileClosure o
-
-  # Statements converted into expressions via closure-wrapping share a scope
-  # object with their parent closure, to preserve the expected lexical scope.
-  compileClosure: (o) ->
-    if @jumps()
-      throw SyntaxError 'cannot use a pure statement in an expression.'
-    o.sharedScope = yes
-    Closure.wrap(this).compileNode o
-
-  # Compile to a source/variable pair suitable for looping.
-  compileLoopReference: (o, name) ->
-    src = tmp = @compile o, LEVEL_LIST
-    unless -Infinity < +src < Infinity or IDENTIFIER.test(src) and o.scope.check(src, yes)
-      src = "#{ tmp = o.scope.freeVariable name } = #{src}"
-    [src, tmp]
-
   # Construct a node that returns the current node's result.
   # Note that this is overridden for smarter behavior for
   # many statement nodes (e.g. If, For)...
@@ -192,90 +160,8 @@
     this

   # A **Block** is the only node that can serve as the root.
-  compile: (o = {}, level) ->
-    if o.scope then super o, level else @compileRoot o
-
-  # Compile all expressions within the **Block** body. If we need to
-  # return the result, and it's an expression, simply return it. If it's a
-  # statement, ask the statement to do so.
-  compileNode: (o) ->
-    @tab  = o.indent
-    top   = o.level is LEVEL_TOP
-    codes = []
-    for node in @expressions
-      node = node.unwrapAll()
-      if node instanceof Block
-        # This is a nested block.  We don't do anything special here like enclose
-        # it in a new scope; we just compile the statements in this block along with
-        # our own
-        codes.push node.compileNode o
-      else if top
-        node.front = true
-        code = node.compile o
-        unless node.isStatement o
-          code = "#{@tab}#{code};"
-          code = "#{code}\n" if node instanceof Literal
-        codes.push code
-      else
-        codes.push node.compile o, LEVEL_LIST
-    if top
-      if @spaced
-        return "\n#{codes.join '\n\n'}\n"
-      else
-        return codes.join '\n'
-    code = codes.join(', ') or 'void 0'
-    if codes.length > 1 and o.level >= LEVEL_LIST then "(#{code})" else code
-
-  # If we happen to be the top-level **Block**, wrap everything in
-  # a safety closure, unless requested not to.
-  # It would be better not to generate them in the first place, but for now,
-  # clean up obvious double-parentheses.
-  compileRoot: (o) ->
-    o.indent  = if o.bare then '' else TAB
-    o.scope   = new Scope null, this, null
-    o.level   = LEVEL_TOP
-    @spaced   = yes
-    prelude   = ""
-    unless o.bare
-      preludeExps = for exp, i in @expressions
-        break unless exp.unwrap() instanceof Comment
-        exp
-      rest = @expressions[preludeExps.length...]
-      @expressions = preludeExps
-      prelude = "#{@compileNode merge(o, indent: '')}\n" if preludeExps.length
-      @expressions = rest
-    code = @compileWithDeclarations o
-    return code if o.bare
+  compile: () ->
     "set [0x8ffe], 0\n"
-
-  # Compile the expressions body for the contents of a function, with
-  # declarations of all inner variables pushed up to the top.
-  compileWithDeclarations: (o) ->
-    code = post = ''
-    for exp, i in @expressions
-      exp = exp.unwrap()
-      break unless exp instanceof Comment or exp instanceof Literal
-    o = merge(o, level: LEVEL_TOP)
-    if i
-      rest = @expressions.splice i, 9e9
-      [spaced, @spaced] = [@spaced, no]
-      [code  , @spaced] = [(@compileNode o), spaced]
-      @expressions = rest
-    post = @compileNode o
-    {scope} = o
-    if scope.expressions is this
-      declars = o.scope.hasDeclarations()
-      assigns = scope.hasAssignments
-      if declars or assigns
-        code += '\n' if i
-        code += "#{@tab}var "
-        if declars
-          code += scope.declaredVariables().join ', '
-        if assigns
-          code += ",\n#{@tab + TAB}" if declars
-          code += scope.assignedVariables().join ",\n#{@tab + TAB}"
-        code += ';\n'
-    code + post

   # Wrap up the given nodes as a **Block**, unless it already happens
   # to be one.
@@ -309,17 +195,6 @@
     return this if @value is 'break' and not (o?.loop or o?.block)
     return this if @value is 'continue' and not o?.loop

-  compileNode: (o) ->
-    code = if @isUndefined
-      if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0'
-    else if @value is 'this'
-      if o.scope.method?.bound then o.scope.method.context else @value
-    else if @value.reserved
-      "\"#{@value}\""
-    else
-      @value
-    if @isStatement() then "#{@tab}#{code};" else code
-
   toString: ->
     ' "' + @value + '"'

@@ -336,13 +211,6 @@
   isStatement:     YES
   makeReturn:      THIS
   jumps:           THIS
-
-  compile: (o, level) ->
-    expr = @expression?.makeReturn()
-    if expr and expr not instanceof Return then expr.compile o, level else super o, level
-
-  compileNode: (o) ->
-    @tab + "return#{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]};"

 #### Value

@@ -393,15 +261,6 @@
   unwrap: ->
     if @properties.length then this else @base

-  # We compile a value to JavaScript by compiling and joining each property.
-  compileNode: (o) ->
-    @base.front = @front
-    props = @properties
-    code  = @base.compile o, if props.length then LEVEL_ACCESS else null
-    code  = "#{code}." if (@base instanceof Parens or props.length) and SIMPLENUM.test code
-    code += prop.compile o for prop in props
-    code
-
 #### Comment

 # pass through block comments as JavaScript block comments
@@ -411,11 +270,6 @@

   isStatement:     YES
   makeReturn:      THIS
-
-  compileNode: (o, level) ->
-    code = '/*' + multident(@comment, @tab) + "\n#{@tab}*/\n"
-    code = o.indent + code if (level or o.level) is LEVEL_TOP
-    code

 #### Call

@@ -471,52 +325,6 @@
           obj = null
     nodes

-  # Compile a vanilla function call.
-  compileNode: (o) ->
-    @variable?.front = @front
-    if code = Splat.compileSplattedArray o, @args, true
-      return @compileSplat o, code
-    args = @filterImplicitObjects @args
-    args = (arg.compile o, LEVEL_LIST for arg in args).join ', '
-    if @isSuper
-      @superReference(o) + ".call(this#{ args and ', ' + args })"
-    else
-      (if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})"
-
-  # `super()` is converted into a call against the superclass's implementation
-  # of the current function.
-  compileSuper: (args, o) ->
-    "#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})"
-
-  # If you call a function with a splat, it's converted into a JavaScript
-  # `.apply()` call to allow an array of arguments to be passed.
-  # If it's a constructor, then things get real tricky. We have to inject an
-  # inner constructor in order to be able to pass the varargs.
-  compileSplat: (o, splatArgs) ->
-    return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper
-    if @isNew
-      idt = @tab + TAB
-      return """
-        (function(func, args, ctor) {
-        #{idt}ctor.prototype = func.prototype;
-        #{idt}var child = new ctor, result = func.apply(child, args), t = typeof result;
-        #{idt}return t == "object" || t == "function" ? result || child : child;
-        #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function(){})
-      """
-    base = new Value @variable
-    if (name = base.properties.pop()) and base.isComplex()
-      ref = o.scope.freeVariable 'ref'
-      fun = "(#{ref} = #{ base.compile o, LEVEL_LIST })#{ name.compile o }"
-    else
-      fun = base.compile o, LEVEL_ACCESS
-      fun = "(#{fun})" if SIMPLENUM.test fun
-      if name
-        ref = fun
-        fun += name.compile o
-      else
-        ref = 'null'
-    "#{fun}.apply(#{ref}, #{splatArgs})"
-
 #### Extends

 # Node to extend an object's prototype with an ancestor object.
@@ -527,10 +335,6 @@

   children: ['child', 'parent']

-  # Hooks one constructor into another's prototype chain.
-  compile: (o) ->
-    new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o
-
 #### Access

 # A `.` access into a property of a value, or the `::` shorthand for
@@ -541,10 +345,6 @@

   children: ['name']

-  compile: (o) ->
-    name = @name.compile o
-    if IDENTIFIER.test name then ".#{name}" else "[#{name}]"
-
   isComplex: NO

 #### Index
@@ -554,9 +354,6 @@
   constructor: (@index) ->

   children: ['index']
-
-  compile: (o) ->
-    "[#{ @index.compile o, LEVEL_PAREN }]"

   isComplex: ->
     @index.isComplex()
@@ -573,32 +370,6 @@
   constructor: (@from, @to, tag) ->
     @exclusive = tag is 'exclusive'
     @equals = if @exclusive then '' else '='
-
-  # Compiles the range's source variables -- where it starts and where it ends.
-  # But only if they need to be cached to avoid double evaluation.
-  compileVariables: (o) ->
-    o = merge o, top: true
-    [@fromC, @fromVar]  =  @from.cache o, LEVEL_LIST
-    [@toC, @toVar]      =  @to.cache o, LEVEL_LIST
-    [@step, @stepVar]   =  step.cache o, LEVEL_LIST if step = del o, 'step'
-    [@fromNum, @toNum]  = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
-    @stepNum            = @stepVar.match(SIMPLENUM) if @stepVar
-
-  # When compiled normally, the range returns the contents of the *for loop*
-  # needed to iterate over the values in the range. Used by comprehensions.
-  compileNode: (o) ->
-    @compileVariables o unless @fromVar
-    return @compileArray(o) unless o.index
-
-    # Set up endpoints.
-    known    = @fromNum and @toNum
-    idx      = del o, 'index'
-    idxName  = del o, 'name'
-    namedIndex = idxName and idxName isnt idx
-    varPart  = "#{idx} = #{@fromC}"
-    varPart += ", #{@toC}" if @toC isnt @toVar
-    varPart += ", #{@step}" if @step isnt @stepVar
-    [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]

     # Generate the condition.
     condPart = if @stepNum
@@ -631,28 +402,6 @@
     "#{varPart}; #{condPart}; #{stepPart}"

-  # When used as a value, expand the range into the equivalent array.
-  compileArray: (o) ->
-    if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
-      range = [+@fromNum..+@toNum]
-      range.pop() if @exclusive
-      return "[#{ range.join(', ') }]"
-    idt    = @tab + TAB
-    i      = o.scope.freeVariable 'i'
-    result = o.scope.freeVariable 'results'
-    pre    = "\n#{idt}#{result} = [];"
-    if @fromNum and @toNum
-      o.index = i
-      body    = @compileNode o
-    else
-      vars    = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
-      cond    = "#{@fromVar} <= #{@toVar}"
-      body    = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
-    post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
-    hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey
-    args   = ', arguments' if hasArgs(@from) or hasArgs(@to)
-    "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"
-
 #### Slice

 # An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
@@ -665,23 +414,6 @@
   constructor: (@range) ->
     super()

-  # We have to be careful when trying to slice through the end of the array,
-  # `9e9` is used because not all implementations respect `undefined` or `1/0`.
-  # `9e9` should be safe because `9e9` > `2**32`, the max array length.
-  compileNode: (o) ->
-    {to, from} = @range
-    fromStr    = from and from.compile(o, LEVEL_PAREN) or '0'
-    compiled   = to and to.compile o, LEVEL_PAREN
-    if to and not (not @range.exclusive and +compiled is -1)
-      toStr = ', ' + if @range.exclusive
-        compiled
-      else if SIMPLENUM.test compiled
-        "#{+compiled + 1}"
-      else
-        compiled = to.compile o, LEVEL_ACCESS
-        "#{compiled} + 1 || 9e9"
-    ".slice(#{ fromStr }#{ toStr or '' })"
-
 #### Obj

 # An object literal, nothing fancy.
@@ -691,41 +423,6 @@

   children: ['properties']

-  compileNode: (o) ->
-    props = @properties
-    propNames = []
-    for prop in @properties
-      prop = prop.variable if prop.isComplex()
-      if prop?
-        propName = prop.unwrapAll().value.toString()
-        if propName in propNames
-          throw SyntaxError "multiple object literal properties named \"#{propName}\""
-        propNames.push propName
-    return (if @front then '({})' else '{}') unless props.length
-    if @generated
-      for node in props when node instanceof Value
-        throw new Error 'cannot have an implicit value in an implicit object'
-    idt         = o.indent += TAB
-    lastNoncom  = @lastNonComment @properties
-    props = for prop, i in props
-      join = if i is props.length - 1
-        ''
-      else if prop is lastNoncom or prop instanceof Comment
-        '\n'
-      else
-        ',\n'
-      indent = if prop instanceof Comment then '' else idt
-      if prop instanceof Value and prop.this
-        prop = new Assign prop.properties[0].name, prop, 'object'
-      if prop not instanceof Comment
-        if prop not instanceof Assign
-          prop = new Assign prop, prop, 'object'
-        (prop.variable.base or prop.variable).asKey = yes
-      indent + prop.compile(o, LEVEL_TOP) + join
-    props = props.join ''
-    obj   = "{#{ props and '\n' + props + '\n' + @tab }}"
-    if @front then "(#{obj})" else obj
-
   assigns: (name) ->
     for prop in @properties when prop.assigns name then return yes
     no
@@ -740,17 +437,6 @@
   children: ['objects']

   filterImplicitObjects: Call::filterImplicitObjects
-
-  compileNode: (o) ->
-    return '[]' unless @objects.length
-    o.indent += TAB
-    objs = @filterImplicitObjects @objects
-    return code if code = Splat.compileSplattedArray o, objs
-    code = (obj.compile o, LEVEL_LIST for obj in objs).join ', '
-    if code.indexOf('\n') >= 0
-      "[\n#{o.indent}#{code}\n#{@tab}]"
-    else
-      "[#{code}]"

   assigns: (name) ->
     for obj in @objects when obj.assigns name then return yes
@@ -863,38 +549,6 @@
     @ctor.klass    = null
     @ctor.noReturn = yes

-  # Instead of generating the JavaScript string directly, we build up the
-  # equivalent syntax tree and compile that, in pieces. You can see the
-  # constructor, property assignments, and inheritance getting built out below.
-  compileNode: (o) ->
-    decl  = @determineName()
-    name  = decl or '_Class'
-    name = "_#{name}" if name.reserved
-    lname = new Literal name
-
-    @hoistDirectivePrologue()
-    @setContext name
-    @walkBody name, o
-    @ensureConstructor name
-    @body.spaced = yes
-    @body.expressions.unshift @ctor unless @ctor instanceof Code
-    @body.expressions.push lname
-    @body.expressions.unshift @directives...
-    @addBoundFunctions o
-
-    call  = Closure.wrap @body
-
-    if @parent
-      @superClass = new Literal o.scope.freeVariable 'super', no
-      @body.expressions.unshift new Extends lname, @superClass
-      call.args.push @parent
-      params = call.variable.params or call.variable.base.params
-      params.push new Param @superClass
-
-    klass = new Parens call, yes
-    klass = new Assign @variable, klass if @variable
-    klass.compile o
-
 #### Assign

 # The **Assign** is used to assign a local variable to value, or to set the
@@ -915,142 +569,6 @@
   assigns: (name) ->
     @[if @context is 'object' then 'value' else 'variable'].assigns name

-  # Compile an assignment, delegating to `compilePatternMatch` or
-  # `compileSplice` if appropriate. Keep track of the name of the base object
-  # we've been assigned to, for correct internal references. If the variable
-  # has not been seen yet within the current scope, declare it.
-  compileNode: (o) ->
-    if isValue = @variable instanceof Value
-      return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
-      return @compileSplice       o if @variable.isSplice()
-      return @compileConditional  o if @context in ['||=', '&&=', '?=']
-    name = @variable.compile o, LEVEL_LIST
-    unless @context
-      unless (varBase = @variable.unwrapAll()).isAssignable()
-        throw SyntaxError "\"#{ @variable.compile o }\" cannot be assigned."
-      unless varBase.hasProperties?()
-        if @param
-          o.scope.add name, 'var'
-        else
-          o.scope.find name
-    if @value instanceof Code and match = METHOD_DEF.exec name
-      @value.klass = match[1] if match[1]
-      @value.name  = match[2] ? match[3] ? match[4] ? match[5]
-    val = @value.compile o, LEVEL_LIST
-    return "#{name}: #{val}" if @context is 'object'
-    val = name + " #{ @context or '=' } " + val
-    if o.level <= LEVEL_LIST then val else "(#{val})"
-
-  # Brief implementation of recursive pattern matching, when assigning array or
-  # object literals to a value. Peeks at their properties to assign inner names.
-  # See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring)
-  # for details.
-  compilePatternMatch: (o) ->
-    top       = o.level is LEVEL_TOP
-    {value}   = this
-    {objects} = @variable.base
-    unless olen = objects.length
-      code = value.compile o
-      return if o.level >= LEVEL_OP then "(#{code})" else code
-    isObject = @variable.isObject()
-    if top and olen is 1 and (obj = objects[0]) not instanceof Splat
-      # Unroll simplest cases: `{v} = x` -> `v = x.v`
-      if obj instanceof Assign
-        {variable: {base: idx}, value: obj} = obj
-      else
-        if obj.base instanceof Parens
-          [obj, idx] = new Value(obj.unwrapAll()).cacheReference o
-        else
-          idx = if isObject
-            if obj.this then obj.properties[0].name else obj
-          else
-            new Literal 0
-      acc   = IDENTIFIER.test idx.unwrap().value or 0
-      value = new Value value
-      value.properties.push new (if acc then Access else Index) idx
-      if obj.unwrap().value in RESERVED
-        throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{value.compile o}"
-      return new Assign(obj, value, null, param: @param).compile o, LEVEL_TOP
-    vvar    = value.compile o, LEVEL_LIST
-    assigns = []
-    splat   = false
-    if not IDENTIFIER.test(vvar) or @variable.assigns(vvar)
-      assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{vvar}"
-      vvar = ref
-    for obj, i in objects
-      # A regular array pattern-match.
-      idx = i
-      if isObject
-        if obj instanceof Assign
-          # A regular object pattern-match.
-          {variable: {base: idx}, value: obj} = obj
-        else
-          # A shorthand `{a, b, @c} = val` pattern-match.
-          if obj.base instanceof Parens
-            [obj, idx] = new Value(obj.unwrapAll()).cacheReference o
-          else
-            idx = if obj.this then obj.properties[0].name else obj
-      if not splat and obj instanceof Splat
-        name = obj.name.unwrap().value
-        obj = obj.unwrap()
-        val = "#{olen} <= #{vvar}.length ? #{ utility 'slice' }.call(#{vvar}, #{i}"
-        if rest = olen - i - 1
-          ivar = o.scope.freeVariable 'i'
-          val += ", #{ivar} = #{vvar}.length - #{rest}) : (#{ivar} = #{i}, [])"
-        else
-          val += ") : []"
-        val   = new Literal val
-        splat = "#{ivar}++"
-      else
-        name = obj.unwrap().value
-        if obj instanceof Splat
-          obj = obj.name.compile o
-          throw new SyntaxError \
-            "multiple splats are disallowed in an assignment: #{obj}..."
-        if typeof idx is 'number'
-          idx = new Literal splat or idx
-          acc = no
-        else
-          acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
-        val = new Value new Literal(vvar), [new (if acc then Access else Index) idx]
-      if name? and name in RESERVED
-        throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{val.compile o}"
-      assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compile o, LEVEL_LIST
-    assigns.push vvar unless top or @subpattern
-    code = assigns.join ', '
-    if o.level < LEVEL_LIST then code else "(#{code})"
-
-  # When compiling a conditional assignment, take care to ensure that the
-  # operands are only evaluated once, even though we have to reference them
-  # more than once.
-  compileConditional: (o) ->
-    [left, right] = @variable.cacheReference o
-    # Disallow conditional assignment of undefined variables.
-    if not left.properties.length and left.base instanceof Literal and
-           left.base.value != "this" and not o.scope.check left.base.value
-      throw new Error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
-    if "?" in @context then o.isExistentialEquals = true
-    new Op(@context[...-1], left, new Assign(right, @value, '=') ).compile o
-
-  # Compile the assignment from an array splice literal, using JavaScript's
-  # `Array#splice` method.
-  compileSplice: (o) ->
-    {range: {from, to, exclusive}} = @variable.properties.pop()
-    name = @variable.compile o
-    [fromDecl, fromRef] = from?.cache(o, LEVEL_OP) or ['0', '0']
-    if to
-      if from?.isSimpleNumber() and to.isSimpleNumber()
-        to = +to.compile(o) - +fromRef
-        to += 1 unless exclusive
-      else
-        to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
-        to += ' + 1' unless exclusive
-    else
-      to = "9e9"
-    [valDef, valRef] = @value.cache o, LEVEL_LIST
-    code = "[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat(#{valDef})), #{valRef}"
-    if o.level > LEVEL_TOP then "(#{code})" else code
-
 #### Code

 # A function definition. This is the only node that creates a new Scope.
@@ -1069,63 +587,6 @@

   jumps: NO

-  # Compilation creates a new scope unless explicitly asked to share with the
-  # outer scope. Handles splat parameters in the parameter list by peeking at
-  # the JavaScript `arguments` object. If the function is bound with the `=>`
-  # arrow, generates a wrapper that saves the current value of `this` through
-  # a closure.
-  compileNode: (o) ->
-    o.scope         = new Scope o.scope, @body, this
-    o.scope.shared  = del(o, 'sharedScope')
-    o.indent        += TAB
-    delete o.bare
-    delete o.isExistentialEquals
-    params = []
-    exprs  = []
-    for name in @paramNames() # this step must be performed before the others
-      unless o.scope.check name then o.scope.parameter name
-    for param in @params when param.splat
-      for {name: p} in @params
-        if p.this then p = p.properties[0].name
-        if p.value then o.scope.add p.value, 'var', yes
-      splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
-                          new Value new Literal 'arguments'
-      break
-    for param in @params
-      if param.isComplex()
-        val = ref = param.asReference o
-        val = new Op '?', ref, param.value if param.value
-        exprs.push new Assign new Value(param.name), val, '=', param: yes
-      else
-        ref = param
-        if param.value
-          lit = new Literal ref.name.value + ' == null'
-          val = new Assign new Value(param.name), param.value, '='
-          exprs.push new If lit, val
-      params.push ref unless splats
-    wasEmpty = @body.isEmpty()
-    exprs.unshift splats if splats
-    @body.expressions.unshift exprs... if exprs.length
-    o.scope.parameter params[i] = p.compile o for p, i in params
-    uniqs = []
-    for name in @paramNames()
-      throw SyntaxError "multiple parameters named '#{name}'" if name in uniqs
-      uniqs.push name
-    @body.makeReturn() unless wasEmpty or @noReturn
-    if @bound
-      if o.scope.parent.method?.bound
-        @bound = @context = o.scope.parent.method.context
-      else if not @static
-        o.scope.parent.assign '_this', 'this'
-    idt   = o.indent
-    code  = 'function'
-    code  += ' ' + @name if @ctor
-    code  += '(' + params.join(', ') + ') {'
-    code  += "\n#{ @body.compileWithDeclarations o }\n#{@tab}" unless @body.isEmpty()
-    code  += '}'
-    return @tab + code if @ctor
-    if @front or (o.level >= LEVEL_ACCESS) then "(#{code})" else code
-
   # A list of parameter names, excluding those generated by the compiler.
   paramNames: ->
     names = []
@@ -1148,9 +609,6 @@
       throw SyntaxError "parameter name \"#{name}\" is not allowed"

   children: ['name', 'value']
-
-  compile: (o) ->
-    @name.compile o, LEVEL_LIST

   asReference: (o) ->
     return @reference if @reference
@@ -1214,30 +672,7 @@
   assigns: (name) ->
     @name.assigns name

-  compile: (o) ->
-    if @index? then @compileParam o else @name.compile o
-
   unwrap: -> @name
-
-  # Utility function that converts an arbitrary number of elements, mixed with
-  # splats, to a proper array.
-  @compileSplattedArray: (o, list, apply) ->
-    index = -1
-    continue while (node = list[++index]) and node not instanceof Splat
-    return '' if index >= list.length
-    if list.length is 1
-      code = list[0].compile o, LEVEL_LIST
-      return code if apply
-      return "#{ utility 'slice' }.call(#{code})"
-    args = list[index..]
-    for node, i in args
-      code = node.compile o, LEVEL_LIST
-      args[i] = if node instanceof Splat
-      then "#{ utility 'slice' }.call(#{code})"
-      else "[#{code}]"
-    return args[0] + ".concat(#{ args[1..].join ', ' })" if index is 0
-    base = (node.compile o, LEVEL_LIST for node in list[...index])
-    "[#{ base.join ', ' }].concat(#{ args.join ', ' })"

 #### While

@@ -1269,30 +704,6 @@
     for node in expressions
       return node if node.jumps loop: yes
     no
-
-  # The main difference from a JavaScript *while* is that our
-  # *while* can be used as a part of a larger expression -- while loops may
-  # return an array containing the computed result of each iteration.
-  compileNode: (o) ->
-    o.indent += TAB
-    set      = ''
-    {body}   = this
-    if body.isEmpty()
-      body = ''
-    else
-      if @returns
-        body.makeReturn rvar = o.scope.freeVariable 'results'
-        set  = "#{@tab}#{rvar} = [];\n"
-      if @guard
-        if body.expressions.length > 1
-          body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
-        else
-          body = Block.wrap [new If @guard, body] if @guard
-      body = "\n#{ body.compile o, LEVEL_TOP }\n#{@tab}"
-    code = set + @tab + "while (#{ @condition.compile o, LEVEL_PAREN }) {#{body}}"
-    if @returns
-      code += "\n#{@tab}return #{rvar};"
-    code

 #### Op

@@ -1375,56 +786,6 @@
     call.do = yes
     call

-  compileNode: (o) ->
-    isChain = @isChainable() and @first.isChainable()
-    # In chains, there's no need to wrap bare obj literals in parens,
-    # as the chained expression is wrapped.
-    @first.front = @front unless isChain
-    if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
-      throw SyntaxError 'delete operand may not be argument or var'
-    if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
-      throw SyntaxError 'prefix increment/decrement may not have eval or arguments operand'
-    return @compileUnary     o if @isUnary()
-    return @compileChain     o if isChain
-    return @compileExistence o if @operator is '?'
-    code = @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' +
-           @second.compile(o, LEVEL_OP)
-    if o.level <= LEVEL_OP then code else "(#{code})"
-
-  # Mimic Python's chained comparisons when multiple comparison operators are
-  # used sequentially. For example:
-  #
-  #     bin/swark -e 'console.log 50 < 65 > 10'
-  #     true
-  compileChain: (o) ->
-    [@first.second, shared] = @first.second.cache o
-    fst = @first.compile o, LEVEL_OP
-    code = "#{fst} #{if @invert then '&&' else '||'} #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL_OP }"
-    "(#{code})"
-
-  compileExistence: (o) ->
-    if @first.isComplex() and o.level > LEVEL_TOP
-      ref = new Literal o.scope.freeVariable 'ref'
-      fst = new Parens new Assign ref, @first
-    else
-      fst = @first
-      ref = fst
-    new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o
-
-  # Compile a unary **Op**.
-  compileUnary: (o) ->
-    if o.level >= LEVEL_ACCESS
-      return (new Parens this).compile o
-    parts = [op = @operator]
-    plusMinus = op in ['+', '-']
-    parts.push ' ' if op in ['new', 'typeof', 'delete'] or
-                      plusMinus and @first instanceof Op and @first.operator is op
-    if (plusMinus && @first instanceof Op) or (op is 'new' and @first.isStatement o)
-      @first = new Parens @first
-    parts.push @first.compile o, LEVEL_OP
-    parts.reverse() if @flip
-    parts.join ''
-
   toString: (idt) ->
     super idt, @constructor.name + ' ' + @operator

@@ -1435,32 +796,6 @@
   children: ['object', 'array']

   invert: NEGATE
-
-  compileNode: (o) ->
-    if @array instanceof Value and @array.isArray()
-      for obj in @array.base.objects when obj instanceof Splat
-        hasSplat = yes
-        break
-      # `compileOrTest` only if we have an array literal with no splats
-      return @compileOrTest o unless hasSplat
-    @compileLoopTest o
-
-  compileOrTest: (o) ->
-    return "#{!!@negated}" if @array.base.objects.length is 0
-    [sub, ref] = @object.cache o, LEVEL_OP
-    [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
-    tests = for item, i in @array.base.objects
-      (if i then ref else sub) + cmp + item.compile o, LEVEL_ACCESS
-    tests = tests.join cnj
-    if o.level < LEVEL_OP then tests else "(#{tests})"
-
-  compileLoopTest: (o) ->
-    [sub, ref] = @object.cache o, LEVEL_LIST
-    code = utility('indexOf') + ".call(#{ @array.compile o, LEVEL_LIST }, #{ref}) " +
-           if @negated then '< 0' else '>= 0'
-    return code if sub is ref
-    code = sub + ', ' + code
-    if o.level < LEVEL_LIST then code else "(#{code})"

   toString: (idt) ->
     super idt, @constructor.name + if @negated then '!' else ''
@@ -1482,27 +817,6 @@
     @recovery = @recovery.makeReturn res if @recovery
     this

-  # Compilation is more or less as you would expect -- the *finally* clause
-  # is optional, the *catch* is not.
-  compileNode: (o) ->
-    o.indent  += TAB
-    errorPart = if @error then " (#{ @error.compile o }) " else ' '
-    tryPart   = @attempt.compile o, LEVEL_TOP
-
-    catchPart = if @recovery
-      if @error.value in STRICT_PROSCRIBED
-        throw SyntaxError "catch variable may not be \"#{@error.value}\""
-      o.scope.add @error.value, 'param' unless o.scope.check @error.value
-      " catch#{errorPart}{\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}"
-    else unless @ensure or @recovery
-      ' catch (_error) {}'
-
-    ensurePart = if @ensure then " finally {\n#{ @ensure.compile o, LEVEL_TOP }\n#{@tab}}" else ''
-
-    """#{@tab}try {
-    #{tryPart}
-    #{@tab}}#{ catchPart or '' }#{ensurePart}"""
-
 #### Throw

 # Simple node to throw an exception.
@@ -1516,9 +830,6 @@

   # A **Throw** is already a return, of sorts...
   makeReturn: THIS
-
-  compileNode: (o) ->
-    @tab + "throw #{ @expression.compile o };"

 #### Existence

@@ -1531,17 +842,6 @@
   children: ['expression']

   invert: NEGATE
-
-  compileNode: (o) ->
-    @expression.front = @front
-    code = @expression.compile o, LEVEL_OP
-    if IDENTIFIER.test(code) and not o.scope.check code
-      [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
-      code = "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null"
-    else
-      # do not use strict equality here; it will break existing code
-      code = "#{code} #{if @negated then '==' else '!='} null"
-    if o.level <= LEVEL_COND then code else "(#{code})"

 #### Parens

@@ -1557,16 +857,6 @@

   unwrap    : -> @body
   isComplex : -> @body.isComplex()
-
-  compileNode: (o) ->
-    expr = @body.unwrap()
-    if expr instanceof Value and expr.isAtomic()
-      expr.front = @front
-      return expr.compile o
-    code = expr.compile o, LEVEL_PAREN
-    bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
-      (expr instanceof For and expr.returns))
-    if bare then code else "(#{code})"

 #### For

@@ -1593,68 +883,6 @@

   children: ['body', 'source', 'guard', 'step']

-  # Welcome to the hairiest method in all of swarkland. Handles the inner
-  # loop, filtering, stepping, and result saving for array, object, and range
-  # comprehensions. Some of the generated code can be shared in common, and
-  # some cannot.
-  compileNode: (o) ->
-    body      = Block.wrap [@body]
-    lastJumps = last(body.expressions)?.jumps()
-    @returns  = no if lastJumps and lastJumps instanceof Return
-    source    = if @range then @source.base else @source
-    scope     = o.scope
-    name      = @name  and @name.compile o, LEVEL_LIST
-    index     = @index and @index.compile o, LEVEL_LIST
-    scope.find(name,  immediate: yes) if name and not @pattern
-    scope.find(index, immediate: yes) if index
-    rvar      = scope.freeVariable 'results' if @returns
-    ivar      = (@object and index) or scope.freeVariable 'i'
-    kvar      = (@range and name) or index or ivar
-    kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
-    # the `_by` variable is created twice in `Range`s if we don't prevent it from being declared here
-    stepvar   = scope.freeVariable "step" if @step and not @range
-    name      = ivar if @pattern
-    varPart   = ''
-    guardPart = ''
-    defPart   = ''
-    idt1      = @tab + TAB
-    if @range
-      forPart = source.compile merge(o, {index: ivar, name, @step})
-    else
-      svar    = @source.compile o, LEVEL_LIST
-      if (name or @own) and not IDENTIFIER.test svar
-        defPart    = "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
-        svar       = ref
-      if name and not @pattern
-        namePart   = "#{name} = #{svar}[#{kvar}]"
-      unless @object
-        lvar       = scope.freeVariable 'len'
-        forVarPart = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
-        forVarPart += ", #{stepvar} = #{@step.compile o, LEVEL_OP}" if @step
-        stepPart   = "#{kvarAssign}#{if @step then "#{ivar} += #{stepvar}" else (if kvar isnt ivar then "++#{ivar}" else "#{ivar}++")}"
-        forPart    = "#{forVarPart}; #{ivar} < #{lvar}; #{stepPart}"
-    if @returns
-      resultPart   = "#{@tab}#{rvar} = [];\n"
-      returnResult = "\n#{@tab}return #{rvar};"
-      body.makeReturn rvar
-    if @guard
-      if body.expressions.length > 1
-        body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
-      else
-        body = Block.wrap [new If @guard, body] if @guard
-    if @pattern
-      body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]"
-    defPart     += @pluckDirectCall o, body
-    varPart     = "\n#{idt1}#{namePart};" if namePart
-    if @object
-      forPart   = "#{kvar} in #{svar}"
-      guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{kvar})) continue;" if @own
-    body        = body.compile merge(o, indent: idt1), LEVEL_TOP
-    body        = '\n' + body + '\n' if body
-    """
-    #{defPart}#{resultPart or ''}#{@tab}for (#{forPart}) {#{guardPart}#{varPart}#{body}#{@tab}}#{returnResult or ''}
-    """
-
   pluckDirectCall: (o, body) ->
     defs = ''
     for expr, idx in body.expressions
@@ -1696,22 +924,6 @@
     @otherwise?.makeReturn res
     this

-  compileNode: (o) ->
-    idt1 = o.indent + TAB
-    idt2 = o.indent = idt1 + TAB
-    code = @tab + "switch (#{ @subject?.compile(o, LEVEL_PAREN) or false }) {\n"
-    for [conditions, block], i in @cases
-      for cond in flatten [conditions]
-        cond  = cond.invert() unless @subject
-        code += idt1 + "case #{ cond.compile o, LEVEL_PAREN }:\n"
-      code += body + '\n' if body = block.compile o, LEVEL_TOP
-      break if i is @cases.length - 1 and not @otherwise
-      expr = @lastNonComment block.expressions
-      continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
-      code += idt2 + 'break;\n'
-    code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n" if @otherwise and @otherwise.expressions.length
-    code +  @tab + '}'
-
 #### If

 # *If/else* statements. Acts as an expression by pushing down requested returns
@@ -1747,9 +959,6 @@

   jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)

-  compileNode: (o) ->
-    if @isStatement o then @compileStatement o else @compileExpression o
-
   makeReturn: (res) ->
     @elseBody  or= new Block [new Literal 'void 0'] if res
     @body     and= new Block [@body.makeReturn res]
@@ -1758,36 +967,6 @@

   ensureBlock: (node) ->
     if node instanceof Block then node else new Block [node]
-
-  # Compile the `If` as a regular *if-else* statement. Flattened chains
-  # force inner *else* bodies into statement form.
-  compileStatement: (o) ->
-    child    = del o, 'chainChild'
-    exeq     = del o, 'isExistentialEquals'
-
-    if exeq
-      return new If(@condition.invert(), @elseBodyNode(), type: 'if').compile o
-
-    cond     = @condition.compile o, LEVEL_PAREN
-    o.indent += TAB
-    body     = @ensureBlock(@body)
-    ifPart   = "if (#{cond}) {\n#{body.compile(o)}\n#{@tab}}"
-    ifPart   = @tab + ifPart unless child
-    return ifPart unless @elseBody
-    ifPart + ' else ' + if @isChain
-      o.indent = @tab
-      o.chainChild = yes
-      @elseBody.unwrap().compile o, LEVEL_TOP
-    else
-      "{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}"
-
-  # Compile the `If` as a conditional operator.
-  compileExpression: (o) ->
-    cond = @condition.compile o, LEVEL_COND
-    body = @bodyNode().compile o, LEVEL_LIST
-    alt  = if @elseBodyNode() then @elseBodyNode().compile(o, LEVEL_LIST) else 'void 0'
-    code = "#{cond} ? #{body} : #{alt}"
-    if o.level >= LEVEL_COND then "(#{code})" else code

 # Faux-Nodes
 # ----------


Actually use settings to define which libraries to insert.

--- src/proto.coffee
+++ src/proto.coffee
@@ -16,6 +16,9 @@
 Jade            = require 'jade'
 Stylus          = require 'stylus'

+proto_version = "0.0.1"
+
+
 CWD = process.cwd()

@@ -33,8 +36,24 @@
         'script.coffee' : 'console.log "loaded"\n\n\n'
         'markup.jade'   : 'h1 Hello, world!\n\n\n'
         'style.styl'    : 'h1\n    font-weight 300\n    font-family Helvetica\n\n\n'
-        'settings.json' : '{}'
         'notes.md'      : "# #{ project_name }\n\n\n"
+        'settings.json' : """{
+            "version": "#{ proto_version }",
+            "script_libraries": [
+                "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.1/underscore-min.js",
+                "https://cdnjs.cloudflare.com/ajax/libs/underscore.string/2.3.0/underscore.string.min.js",
+                "https://cdnjs.cloudflare.com/ajax/libs/coffee-script/1.3.3/coffee-script.min.js",
+                "https://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js",
+                "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js",
+                "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js",
+                "https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js",
+                "https://raw.github.com/Marak/Faker.js/master/Faker.js"
+            ],
+            "style_libraries": [
+                "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery.ui.all.css"
+            ]
+        }"""
+

     project_path = "#{ CWD }/#{ project_name }"

@@ -64,6 +83,14 @@

     sys.puts("Working on #{ project_name }\n#{ project_path }")

+    loadSettings = (settings_source_file) ->
+        settings_raw = fs.readFileSync(settings_source_file)
+        settings = JSON.parse(settings_raw)
+        # TODO: Validate settings
+        if settings.version isnt proto_version
+            quitWithMsg("Error: #{ project_name }'s version (#{ settings.version }) does not match proto's (#{ proto_version })")
+        return settings
+
     compileScriptFile = (script_source_file) ->
         stamp('Compiling script')

@@ -85,21 +112,26 @@
             compiled_style = data
         return compiled_style

+    compileScriptLibraries = (settings) ->
+        script_libs = ''
+        for lib in settings.script_libraries
+            script_libs += "<script src='#{ lib }'></script>"
+        return script_libs
+
+    compileStyleLibraries = (settings) ->
+        style_libs = ''
+        for lib in settings.style_libraries
+            style_libs += "<link rel='stylesheet' href='#{ lib }' type='text/css'>"
+        return style_libs
+
     compositePage = (compiled) ->
         page = """
         <!doctype html>
         <html>
         <head>
             <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.1/underscore-min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.string/2.3.0/underscore.string.min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/coffee-script/1.3.3/coffee-script.min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.9.0/jquery-ui.min.js"></script>
-            <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
-            <script src="https://raw.github.com/Marak/Faker.js/master/Faker.js"></script>
-            <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.9/themes/base/jquery.ui.all.css">
+            #{ compiled.script_libraries }
+            #{ compiled.style_libraries }
             <style>
                 #{ compiled.style }
             </style>
@@ -116,15 +148,18 @@

     doCompilation = ->
         stamp('Compiling all the things')
+        settings = loadSettings(sources.settings)
         output = compositePage
-            style   : compileStyleFile(sources.style)
-            script  : compileScriptFile(sources.script)
-            markup  : compileMarkupFile(sources.markup)
+            style               : compileStyleFile(sources.style)
+            script              : compileScriptFile(sources.script)
+            markup              : compileMarkupFile(sources.markup)
+            script_libraries    : compileScriptLibraries(settings)
+            style_libraries     : compileStyleLibraries(settings)
         return output

     serveContent = ->
         stamp('Creating server')
-        app = express.createServer()
+        app = express()

         app.get '/', (req, res) ->
             res.send(doCompilation())


OCD ordering/spacing

OCD ordering/spacing

--- src/robot.coffee
+++ src/robot.coffee
@@ -1,10 +1,11 @@
-Fs           = require 'fs'
-Log          = require 'log'
-Path         = require 'path'
-Url          = require 'url'
-
-Brain        = require './brain'
-User         = require './user'
+Fs      = require 'fs'
+Url     = require 'url'
+Log     = require 'log'
+Path    = require 'path'
+Connect = require 'connect'
+
+User    = require './user'
+Brain   = require './brain'

 HUBOT_DEFAULT_ADAPTERS = [ "campfire", "shell" ]

@@ -23,8 +24,7 @@
     @listeners   = []
     @loadPaths   = []
     @enableSlash = false
-    connect      = require 'connect'
-    @connect     = connect()
+    @connect     = Connect()

     @logger      = new Log process.env.HUBOT_LOG_LEVEL or "info"


Sanity check arity of new middleware

Sanity check arity of new middleware

--- src/robot.coffee
+++ src/robot.coffee
@@ -203,6 +203,8 @@
   #
   # Returns nothing.
   listenerMiddleware: (middleware) ->
+    if middleware.length != 5
+      throw new Error("Incorrect number of arguments for middleware callback (expected 5, got #{middleware.length})")
     @middleware.listener.push middleware
     return undefined


Integrating scenes with Canvas and TileManager

--- src/scene.coffee
+++ src/scene.coffee
@@ -12,9 +12,9 @@
   drawCurrent: ->
     @currentScene.draw()

+  # Find the new scene by name and set it to active to start rendering
+  # ex: Kona.Scenes.setCurrent('level-2')
   setCurrent: (sceneName) ->
-    # Find the new scene by name and set it to active to start rendering
-    # ex: Kona.Scenes.setCurrent('level-2')
     @currentScene.active = false
     @currentScene = Kona.Utils.findByKey(@_scenes, 'name', sceneName)
     @currentScene.active = true
@@ -34,30 +34,29 @@
   addEntity: (entity) ->
     @entities.push(entity)

+  # TODO: definition schema here is ugly
+  #
+  # [                                 Schema
+  #   {                                 Object Definition
+  #     entity: Player,
+  #     layout: [ {opts}, {opts} ]        Options
+  #   }
+  # ]
   setLayout: (schema) ->
-    # TODO: definition schema here is ugly
-    #
-    # [                                 Schema
-    #   {                                 Object Definition
-    #     entity: Player,
-    #     layout: [ {opts}, {opts} ]        Options
-    #   }
-    # ]
-
     for definition in schema
       entity = definition.entity
       for opts in definition.layout
         @addEntity(new entity(opts))

+  update: ->
+    console.log "scene update called"

-
-  update: ->
-
+  # Render onto main canvas
   draw: ->
-    # Render onto main canvas
-    # Kona.debug "will draw: #{@name}"
-    Kona.Engine.ctx.drawImage(@background, 0, 0)
-    for entity in @entities
+    Kona.Canvas.clear()
+    Kona.Canvas.ctx.drawImage(@background, 0, 0) # Background
+    Kona.TileManager.draw(@name)                 # Tiles
+    for entity in @entities                      # Game entities
       entity.update()
       entity.draw()


strip newlines and whitespace from evaluated text
some versions of sc are choking on it

also clip the text posted to echo

--- lib/repl.coffee
+++ lib/repl.coffee
@@ -42,9 +42,16 @@
       @bus.push("
#{d}
")

   eval: (expression) ->
+    if expression.length > 80
+      echo = expression.substr(0, 80) + '...'
+    else
+      echo = expression
     # =>
-    @bus.push "
#{expression}
"
-    @sclang.write expression
+    @bus.push "
#{echo}
"
+
+    trimmed = (line.trim() for line in expression.split('\n'))
+    escaped = trimmed.join(' ') + '\n'
+    @sclang.write escaped

   recompile: ->
     @sclang?.quit()


fix example in store

fix example in store

--- src/store.coffee
+++ src/store.coffee
@@ -10,12 +10,10 @@
 	#	implementation example:
 	#
 	#	class TodoStore extends Store
-	#		actions: [
-	#			someAction, () ->
-	#				@doStuff()
-	#				@doOtherStuff()
-	#				@profit()
-	#		]
+	#		@action someAction, () ->
+	#			@doStuff()
+	#			@doOtherStuff()
+	#			@profit()
 	#
 	#		doStuff: () ->
 	#			# Do things..


JApp: Simple approve mechanism

JApp: Simple approve mechanism

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -38,7 +38,7 @@

     sharedMethods       :
       instance          : [
-        'fetchRelativeReviews', 'review', 'delete'
+        'fetchRelativeReviews', 'review', 'delete', 'approve'
       ]
       static            : [
         'create', 'one', 'some', 'each', 'some_'
@@ -291,32 +291,32 @@
     success: (client, callback)-> @remove callback

-  # approve: permit 'approve apps',
-
-  #   success: (client, state = yes, callback)->
-
-  #     # Disapprove
-  #     unless state
-
-  #       return @update $set:{approved: no}, callback
-
-  #     identifier = @getAt 'identifier'
-
-  #     JApp.count {identifier, approved:yes}, (count)=>
-
-  #       # Check if any app used same identifier and already approved
-  #       if count > 1
-  #         return callback new KodingError \
-  #                'Identifier already in use, please change it first'
-
-  #       # Check if the app has a slug, if not create one
-  #       unless @getAt 'slug'
-  #         this.createSlug (err, slug)=>
-  #           return callback err  if err
-  #           slug = slug_ = slug.slug
-  #           @update {slug, slug_, approved:yes}, callback
-  #       else
-  #         @update approved:yes, callback
+  approve: permit 'approve apps',
+
+    success: (client, state = yes, callback)->
+
+      @update $set: approved: state, callback
+
+      # identifier = @getAt 'identifier'
+
+      # JApp.count {identifier, approved:yes}, (count)=>
+
+      #   # Check if any app used same identifier and already approved
+      #   if count > 1
+      #     return callback new KodingError \
+      #            'Identifier already in use, please change it first'
+
+
+
+
+      #   # Check if the app has a slug, if not create one
+      #   unless @getAt 'slug'
+      #     this.createSlug (err, slug)=>
+      #       return callback err  if err
+      #       slug = slug_ = slug.slug
+      #       @update {slug, slug_, approved:yes}, callback
+      #   else
+      #     @update approved:yes, callback

   # JApp.one
     #   name : data.name


Client: Activity: Added comment for renderChannelInfoContainer method

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -57,6 +57,7 @@
     messagesSize        = @props.thread.get('messages').size
     reachedFirstMessage = @props.thread.getIn(['flags', 'reachedFirstMessage'])

+    # we have always at least one system message
     return null  unless reachedFirstMessage and messagesSize

     <ChannelInfoContainer


notifications are automatically read with each login/refresh bug is fixed

--- client/app/MainApp/avatararea/avatarareapopup.coffee
+++ client/app/MainApp/avatararea/avatarareapopup.coffee
@@ -4,7 +4,7 @@
     super

     @sidebar = @getDelegate()
-    @sidebar.on "NavigationPanelWillCollapse", => @hide()
+    @sidebar.on "NavigationPanelWillCollapse", => @hide() if @hasClass "active"

     @on 'ReceivedClickElsewhere', => @hide()


text: Changed reinit without snapshot text

Via Nitin's feedback

--- client/app/lib/providers/computecontroller.ui.coffee
+++ client/app/lib/providers/computecontroller.ui.coffee
@@ -187,14 +187,15 @@
           "
           button  : "Proceed"
         reinitNoSnapshot :
-          title   : "Reinitialize without Snapshot?"
-          message : "
-            The snapshot on which this VM was originally based has been
-            deleted so this VM cannot be reinitialized. Would you like
-            to reinit using a standard Koding base VM image?
-
-            You will lose all your files, workspaces and data but your VM
-            settings (VM aliases, sub-domains etc.) will not be lost.
+          title   : "Cannot proceed with reinitialization!"
+          message : "
+
The snapshot on which this VM was originally based has been
+            deleted, so the system cannot reinitialize this VM.

+
+
Instead, would you like to reinit using a standard VM image?
+            Note: reinitializing will erase all files and folders on the VM
+            right now but your VM settings (VM aliases, sub-domains etc.)
+            will not be lost.

           "
           button  : "Proceed"
         destroy   :


ComputeEventListener: request revive stack once a stack build failed

--- client/app/lib/providers/computeeventlistener.coffee
+++ client/app/lib/providers/computeeventlistener.coffee
@@ -153,6 +153,9 @@
           computeController.stateChecker.watch eventId
           # For `apply` event revive all the machines in a stack ~ GG
           computeController.triggerReviveFor eventId, asStack = type is 'apply'
+        else if event.error and event.percentage is 100 and type is 'apply'
+          # If a stack `apply` is failed we need to revive it from DB ~ GG
+          computeController.triggerReviveFor eventId, asStack = yes
         else
           computeController.stateChecker.ignore eventId


default-apps: Implemented open with app logic in app manager. Still WIP.

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -20,15 +20,16 @@

     @appControllers = {}
     @frontApp       = null
+    @on 'AppManagerWantsToShowAnApp', @bound "setFrontApp"
+
+    # temp fix, until router logic is complete
+    @on 'AppManagerWantsToShowAnApp', @bound "setMissingRoute"
+
     @defaultApps    =
       text  : "Ace"
       video : "Viewer"
       image : "Viewer"
       sound : "Viewer"
-    @on 'AppManagerWantsToShowAnApp', @bound "setFrontApp"
-
-    # temp fix, until router logic is complete
-    @on 'AppManagerWantsToShowAnApp', @bound "setMissingRoute"

   # temp fix, until router logic is complete
   setMissingRoute:(appController, appView, appOptions)->
@@ -95,7 +96,7 @@
           options.requestedFromAppManager = yes
           @open name, options, callback
         return
-      else if appOptions?.thirdParty and not options.requestedFromAppsController
+      else if appOptions?.thirdParty and not options.requestedFromAppsController and not @appControllers[appOptions.name]
         kodingAppsController.runApp (KD.getAppOptions name), callback
         return

@@ -139,16 +140,18 @@
       callback?()

-  openFile:(file)->
+  openFile: (file, app) ->

     type = FSItem.getFileType file.getExtension()

     switch type
       when 'code','text','unknown'
         log "open with a text editor"
-        @open @defaultApps.text, (appController)-> appController.openFile file
+        @open app or @defaultApps.text,  (appController = @getFrontApp()) =>
+          appController.openFile file
       when 'image'
-        log "open with an image processing app"
+        @open app or @defaultApps.image, (appController = @getFrontApp()) =>
+          appController.openFile file
       when 'video'
         log "open with a video app"
       when 'sound'
@@ -323,9 +326,6 @@
       else optionSet.lastActiveIndex = index

-
-
-
   # setGroup:-> console.log 'setGroup', arguments

   # openFileWithApplication:(file, appPath)->


ApplicationManager: implemented argument support to appManager::open <name>, [options], [callback]

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -45,7 +45,7 @@

   open: do ->

-    createOrShow = (appOptions, callback = noop)->
+    createOrShow = (appOptions, appParams, callback = noop)->

       name = appOptions?.name
       return warn "No such application!"  unless name
@@ -54,7 +54,7 @@
       appInstance = appManager.get name
       cb          = -> appManager.show appOptions, callback
       if appInstance then do cb
-      else appManager.create name, cb
+      else appManager.create name, appParams, cb

     (name, options, callback)->

@@ -65,7 +65,8 @@
       return warn "ApplicationManager::open called without an app name!"  unless name

       appOptions           = KD.getAppOptions name
-      defaultCallback      = -> createOrShow appOptions, callback
+      appParams            = options.params or {}
+      defaultCallback      = -> createOrShow appOptions, appParams, callback
       kodingAppsController = @getSingleton("kodingAppsController")

       # If app has a preCondition then first check condition in it
@@ -169,11 +170,14 @@
     if app then cb app
     else @create name, cb

-  create:(name, callback)->
-
-    AppClass   = KD.getAppClass name
-    appOptions = KD.getAppOptions name
-    @register appInstance = new AppClass($.extend {}, true, appOptions)  if AppClass
+  create:(name, params, callback)->
+
+    [callback, params] = [params, callback]  unless callback
+
+    AppClass              = KD.getAppClass name
+    appOptions            = $.extend {}, true, KD.getAppOptions name
+    appOptions.params     = params
+    @register appInstance = new AppClass appOptions  if AppClass
     @utils.defer -> callback? appInstance

   show:(appOptions, callback)->


Fix grammar error

Fix grammar error

--- scripts/views/workspace/menu/add.coffee
+++ scripts/views/workspace/menu/add.coffee
@@ -28,7 +28,7 @@
       model.loaded = true
       content.add(model)

-      # Begin editing certain media as soon as it is added.
+      # Begin editing certain media as soon as they are added.
       model.addAction?()

   return new (Marionette.CompositeView.extend


fixed error with clicking while other item was being edited

--- app/assets/javascripts/lists.js.coffee
+++ app/assets/javascripts/lists.js.coffee
@@ -106,11 +106,12 @@
     # TODO handle selected

   select: ->
-    $(".selected").removeClass("selected")
+    if App.selection()
+      App.selection().deselect()
     @item.addClass "selected"

   deselect: ->
-    @form.submit() if @$("form").length
+    @form.stopEditing() if @$("form").length
     @item.removeClass "selected"

   setBody: ->


fixed repositories not correctly fetching


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -41,7 +41,8 @@
   player.tween 30,
     x: 50
     y: 50
-    complete: "quadratic"
+    complete: ->
+
   </pre></code>

   @name tween


Added rudimentary tweening, wut!

Added rudimentary tweening, wut!

--- src/tween.coffee
+++ src/tween.coffee
@@ -36,7 +36,7 @@

   @name tween
   @methodOf Tween#
-  @param {Number} duration How long (in frames) until the object's properties reache
+  @param {Number} duration How long (in frames) until the object's properties reach their final values
   @param {Number} [frequency=3] The number of frames in between opacity changes
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###


patch IE location.origin

patch IE location.origin

--- src/utils.coffee
+++ src/utils.coffee
@@ -55,3 +55,7 @@
     offset  = element.offset()
     event.offsetX = event.pageX - offset.left
     event.offsetY = event.pageY - offset.top
+
+# IE fix for location.origin
+unless window.location.origin?
+  window.location.origin = window.location.protocol + "//" + window.location.hostname + (if window.location.port then ':' + window.location.port else '');


:wrench: Focus on click on random item within panel

--- src/notational/setup-panel.coffee
+++ src/notational/setup-panel.coffee
@@ -81,8 +81,11 @@
   headerProp = columnsProp.map (columns) ->
     header(columns)

+  focusBus = new Bacon.Bus()
   vdomTreeProp = Bacon.combineWith (contentHeader, scrollableContent, resizeHandle) ->
-    h 'div.atom-notational-panel', [
+    h 'div.atom-notational-panel', {
+      onclick: -> focusBus.push undefined
+    }, [
       search(searchBus, keyInputBus)
       contentHeader
       scrollableContent
@@ -92,7 +95,7 @@
   elementProp = vdomTreeToElement(vdomTreeProp)

   dispose = Bacon.when(
-    [selectedItemProp.changes(), elementProp], (..., el) ->
+    [selectedItemProp.changes().merge(focusBus), elementProp], (..., el) ->
       # Scroll item into the view if outside the visible border and was triggered by selectItem change
       selectedRow = el.querySelector('.is-selected')
       if selectedRow


experimental click command via console.log

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -34,13 +34,24 @@
   finish: ->
     phantom.exit @page.evaluate -> mochaPhantomJS.failures

+  handleConsoleMessage: (msg) ->
+    system.stdout.writeLine(msg)
+    m = msg.match /phantom, please click at (\d+) (\d+)/
+    if m
+      x = parseInt m[1]
+      y = parseInt m[2]
+      system.stdout.writeLine "XXX Clicking at #{x} #{y}"
+      page.sendEvent("mousemove", x, y)
+      page.sendEvent("click", x, y)
+      page.render("clicked.png")
+
   initPage: ->
     @page = webpage.create
       settings: @config.settings
     @page.customHeaders = @config.headers if @config.headers
     @page.addCookie(cookie) for cookie in @config.cookies or []
     @page.viewportSize = @config.viewportSize if @config.viewportSize
-    @page.onConsoleMessage = (msg) -> system.stdout.writeLine(msg)
+    @page.onConsoleMessage = (msg) => @handleConsoleMessage(msg)
     @page.onError = (msg, traces) =>
       for {line, file}, index in traces
         traces[index] = "  #{file}:#{line}"


log friendly messages on Resource Error

log friendly messages on Resource Error

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -41,8 +41,8 @@
     @page.addCookie(cookie) for cookie in @config.cookies or []
     @page.viewportSize = @config.viewportSize if @config.viewportSize
     @page.onConsoleMessage = (msg) -> system.stdout.writeLine(msg)
-    @page.onResourceError  = (msg) ->
-      system.stdout.writeLine(JSON.stringify(msg))
+    @page.onResourceError = (resErr) ->
+      system.stdout.writeLine "Error loading resource #{resErr.url} (#{resErr.errorCode}). Details: #{resErr.errorString}"
     @page.onError = (msg, traces) =>
       return if @page.evaluate -> window.onerror?
       for {line, file}, index in traces


:bug:

:bug:

--- lib/util.coffee
+++ lib/util.coffee
@@ -74,25 +74,26 @@
         .then (rows) ->
           resolve(sanitize(rows))

-    # Atom 1.5 to 1.6
-    # Editor state is in a storage folder
-    storageFolder = atom.getStorageFolder().path
+    else
+      # Atom 1.5 to 1.6
+      # Editor state is in a storage folder
+      storageFolder = atom.getStorageFolder().path

-    # List the storage folder
-    fs.list storageFolder, (err, filenames) ->
-      # Filter to only have filenames that start with editor-
-      filenames = _.filter filenames, (fn) ->
-        basename = path.basename(fn)
-        /^editor-/.test(basename)
+      # List the storage folder
+      fs.list storageFolder, (err, filenames) ->
+        # Filter to only have filenames that start with editor-
+        filenames = _.filter filenames, (fn) ->
+          basename = path.basename(fn)
+          /^editor-/.test(basename)

-      # Read in the JSON data from each state file
-      async.map filenames, ((filename, cb) ->
-        fs.readFile filename, 'utf8', (err, data) ->
-          return cb(err) if (err)
-          cb(null, JSON.parse(data))
-      ), (err, rows) ->
-        return reject(err) if err
-        resolve(sanitize(rows))
+        # Read in the JSON data from each state file
+        async.map filenames, ((filename, cb) ->
+          fs.readFile filename, 'utf8', (err, data) ->
+            return cb(err) if (err)
+            cb(null, JSON.parse(data))
+        ), (err, rows) ->
+          return reject(err) if err
+          resolve(sanitize(rows))

 # shim atom.packages.serialize in <= 1.6
 packageStatesSerialize = () ->
@@ -135,7 +136,7 @@
 loadState = (key) ->
   if atom.stateStore?
     # Atom 1.7+
-    atom.stateStore.load(newKey)
+    atom.stateStore.load(key)

   else
     # Atom <= 1.6


Make language present time

Make language present time

--- app/scripts/lazerscripts/stage1.coffee
+++ app/scripts/lazerscripts/stage1.coffee
@@ -389,7 +389,7 @@
         @waitingRocketStrike()
       )
       @placeSquad Game.Scripts.Stage1BossLeaving
-      @say 'General', 'He went to the military complex!\nBut we cant get through those shields now...', noise: 'low'
+      @say 'General', 'He is going to the military complex!\nBut we cant get through those shields now...', noise: 'low'
     )

   cloneEncounter: ->


Disappear notify after certain time.

Disappear notify after certain time.

--- app/assets/javascripts/app/controllers/widget/notify.coffee
+++ app/assets/javascripts/app/controllers/widget/notify.coffee
@@ -44,7 +44,7 @@
       animation:
         open:    'animated fadeInDown'
         close:   'animated fadeOutDown'
-      timeout:   false #data.timeout || 3800
+      timeout:   data.timeout || 3800
       closeWith: ['click']

   destroy: (e) ->


Improved error message if network connection got lost.

--- app/assets/javascripts/app/lib/app_post/websocket.js.coffee
+++ app/assets/javascripts/app/lib/app_post/websocket.js.coffee
@@ -147,7 +147,7 @@
     check = =>
       @log 'Websocket', 'notice', 'no websockend ping response, reconnect...'
       @close()
-    @delay check, 120000, 'websocket-ping-check', 'ws'
+    @delay check, 90000, 'websocket-ping-check', 'ws'

   pong: ->
     return if @backend is 'ajax'
@@ -183,7 +183,8 @@
       @clearDelay('websocket-no-connection-try-reconnect')
       if @error
         @error.modalHide()
-        @error = undefined
+        @error = false
+        @tryToConnect = false

       @auth()

@@ -202,7 +203,7 @@
       @_receiveMessage(pipe)

     @ws.onclose = (e) =>
-      @log 'Websocket', 'debug', "ws:onclose", e
+      @log 'Websocket', 'notice', 'close websocket connection'

       # take connection down and keep it down
       return if @connectionKeepDown
@@ -226,8 +227,12 @@
             return

           # show reconnect message
-          @error = new App.ErrorModal(
-            message: 'No connection to websocket, trying to reconnect...'
+          @error = new App.ControllerModal(
+            title:   'Lost network connection!'
+            message: 'Lost network connection to system, trying to reconnect...'
+            backdrop: false
+            keyboard: false
+            show:     true
           )
         if !@tryToConnect
           @delay message, 7000, 'websocket-no-connection-try-reconnect-message', 'ws'


Removed databag

Removed databag

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -70,14 +70,6 @@
   result = uglify.minify js, fromString:true
   result.code

-# Shallow copy attributes from `sources` (array of objects) to `recipient`.
-# Does NOT overwrite attributes already present in `recipient`.
-copy_data_to = (recipient, sources) ->
-  for obj in sources
-    for k, v of obj
-      recipient[k] = v unless recipient[k]
-  return
-
 # Flatten array recursively (copied from Express's utils.js)
 flatten = (arr, ret) ->
   ret ?= []
@@ -326,10 +318,6 @@
         next: next

       apply_helpers ctx
-
-      if app.settings['databag']
-        ctx.data = {}
-        copy_data_to ctx.data, [req.query, req.params, req.body]

       f.call ctx, req, res, next

@@ -566,10 +554,6 @@
           res.render.call res, name, opts, postrender

         apply_helpers ctx
-
-        if app.settings['databag']
-          ctx.data = {}
-          copy_data_to ctx.data, [req.query, req.params, req.body]

         if app.settings['x-powered-by']
           res.setHeader 'X-Powered-By', "Zappa #{zappa.version}"


Update HeaderCtrl to use `keywords` param instead of `search`

--- app/assets/javascripts/sprangular/controllers/header.coffee
+++ app/assets/javascripts/sprangular/controllers/header.coffee
@@ -16,7 +16,7 @@
     $scope.taxonomies = taxonomies
   $scope.account = Account
   $scope.env = Env
-  $scope.search = {text: $location.search()['search']}
+  $scope.search = {text: $location.search()['keywords']}

   $scope.goToMyAccount = ->
     $location.path '/account'
@@ -41,7 +41,7 @@
     if product
       $location.path "/products/#{product.slug}"
     else
-      $location.search('search', $scope.search.text)
+      $location.search('keywords', $scope.search.text)
       $location.path "/products"

   $scope.getProducts = (search) ->


only show Load More if total pages > 0

only show Load More if total pages > 0

--- app/assets/javascripts/tenon/features/record_list.js.coffee
+++ app/assets/javascripts/tenon/features/record_list.js.coffee
@@ -68,7 +68,7 @@

   _toggleInfiniteLoader: (pagination) =>
     $loader = $(".infinite-loader[data-record-list='##{@$list.attr('id')}']")
-    if @_currentPage < parseInt(pagination.totalPages)
+    if parseInt(pagination.totalPages) > 0
       $loader.show()
     else
       $loader.hide()


Makes partial subexpressions possible for calculator

--- support/spec_helpers.coffee
+++ support/spec_helpers.coffee
@@ -12,14 +12,14 @@

 class_mixer RegexpSpecFilter

-override_spec_filter_with = RegexpSpecFilter.build(/calc/)
+override_spec_filter_with = RegexpSpecFilter.build(/opening/)
+override_spec_filter_with = RegexpSpecFilter.build(/wip/)
 override_spec_filter_with = false

 if override_spec_filter_with
   env = jasmine.getEnv()
   env.specFilter = (spec)->
     override_spec_filter_with.forSpec spec
-

 jasmine.getFixtures().findContainer = ->
   $(document.body).find("##{@containerId}")


travis

travis

--- test-runners/standalone/src/index.coffee
+++ test-runners/standalone/src/index.coffee
@@ -1,8 +1,8 @@
 cwd = process.cwd()
-CeOperationHub = require cwd + '/../ce-operation-hub/lib/src/Server.js'
-CeDeltaHub = require cwd + '/../ce-delta-hub/lib/src/Server.js'
-CeEngine = require cwd + '/../ce-engine/lib/src/Server.js'
-CeFrontEnd = require cwd + '/../ce-front-end/lib/src/Server.js'
+CeOperationHub = require cwd + '/../../ce-operation-hub/lib/src/Server.js'
+CeDeltaHub = require cwd + '/../../ce-delta-hub/lib/src/Server.js'
+CeEngine = require cwd + '/../../ce-engine/lib/src/Server.js'
+CeFrontEnd = require cwd + '/../../ce-front-end/lib/src/Server.js'
 nconf = require 'nconf'
 step = require 'step'


returns empty Object when query is undefined

--- src/serializer/QueryString.coffee
+++ src/serializer/QueryString.coffee
@@ -7,7 +7,7 @@
     tokens.join '&'

   parse: (query)->
-    if query is ''
+    unless query
       return {}
     tokens = query.split '&'
     if tokens.length is 0


show alert when login failed

show alert when login failed

--- src/shared/component/login.coffee
+++ src/shared/component/login.coffee
@@ -44,11 +44,16 @@
           page:
             hasBeenClicked: {$set: true}
         return
-      login(that.state.page.data)
+      promise = login(that.state.page.data)
+      promise
         .then (response) ->
           console.log 'success', response
         .fail (error) ->
-          console.log 'error', error
+          # TODO extract this
+          if error.status is 422
+            that.update {page: {alert: {$set: error.response}}}
+          else
+            that.update {error: {$set: error}}
     componentDidMount: ->
       this.updateCursorFromForm()
     hasSuccess: (name) ->
@@ -78,6 +83,11 @@
               k.div {className: 'col-md-4'}, ->

                 k.h1 'Login'
+
+                if that.state.page.alert?
+                  k.div {
+                    className: 'alert alert-danger'
+                  }, that.state.page.alert

                 name = 'identifier'
                 k.form ->


Small tweak

Small tweak

--- framer/EventEmitter.coffee
+++ framer/EventEmitter.coffee
@@ -1,6 +1,4 @@
 {_} = require "./Underscore"
-
-

 class exports.EventEmitter

@@ -58,6 +56,8 @@

 		for listener in @events[event]
 			@removeListener event, listener
+
+		return

 	on: @::addListener
 	off: @::removeListener


Set Arnie to reply 100% of the time (for now). Update a few Arnie quotes.

--- scripts/arnie-quotes.coffee
+++ scripts/arnie-quotes.coffee
@@ -8,19 +8,19 @@
 #   None
 #
 # Author:
-#   Casey Lawrence <cjlaw@users.noreply.github.com>
+#   Casey Lawrence <casey.j.lawrence@gmail.com>
 #

 odds  = [1...100]

 arnie_quotes = [
     'GET TO THE CHOPPA!',
-    'Your clothes, give them to me, now!',
+    'Your clothes, give them to me, NOW!',
     'Hasta La Vista, Baby!',
     'DDDAAANNNAAAA!',
-    'You are one ugly mother******.',
+    'You are one ugly mothersucker.',
     'It`s not a tumor!',
-    'When I said you should screw yourself. I didn`t mean it literally.',
+    'When I said you should screw yourself I didn`t mean it literally.',
     'Can you hurry up. My horse is getting tired.',
     'Are these all your lunches? You mean you eat other peoples` lunches? STOP IT!!',
     'I`m the party pooper.',
@@ -30,10 +30,10 @@
 module.exports = (robot) ->
     robot.hear /(^|\s)arnie(\s|$|[\W])/ig, (msg) ->
         val = msg.random odds
-        if val > 50
+        if val > 0 # Set Arnie to reply 100% for now
             msg.send msg.random arnie_quotes

     robot.hear /(^|\s)arnold(\s|$|[\W])/ig, (msg) ->
         val = msg.random odds
-        if val > 50
+        if val > 0 # Set Arnie to reply 100% for now
             msg.send msg.random arnie_quotes


Added parameter for ephemeral logging, but this will likely go unused until I can figure out how to utilize it.

--- src/helpers/logger.coffee
+++ src/helpers/logger.coffee
@@ -20,7 +20,7 @@
     @error: (msg, error) ->
       if DEBUG
         console.error "[Ibizan] (#{new Date()}) ERROR: #{msg}", error || ''
-    @logToChannel: (msg, channel) ->
+    @logToChannel: (msg, channel, ephemeral) ->
       if DEBUG
         if robot
           robot.send {room: channel}, msg


Fixed #209

Fixed #209

--- scripts/youtube-info.coffee
+++ scripts/youtube-info.coffee
@@ -23,7 +23,7 @@
 cheerio = require 'cheerio'

 module.exports = (robot) ->
-  robot.hear /youtube\.com\/watch\?v=(.+)/i, (msg) ->
+  robot.hear /youtube\.com\/watch\?v=(.+)|youtu.be\/(.+)/i, (msg) ->
     url_parsed = url.parse(msg.match[1])
     getTitle msg, url_parsed.href


fix source click bug

fix source click bug

--- components/block_collection/client/block_view.coffee
+++ components/block_collection/client/block_view.coffee
@@ -65,14 +65,14 @@

     url = @model.getSourceUrl()

+    e.preventDefault()
+    e.stopImmediatePropagation()
+
+    console.log "@model.get('visibility')", @model.get('visibility')
+
     if @model.get('visibility') is 'private'
-      a = e.target
-      a = a.parentNode if a and a.tagName isnt 'A'
-      a.rel = 'noreferrer' if a and a.tagName is 'A'
+      window.open "#{sd.APP_URL}/go?redirect-to=#{url}",'_blank'
     else
-      e.preventDefault()
-      e.stopImmediatePropagation()
-
       window.open url,'_blank'
       return false


Remove AtomEnvironment::requireWithGlobals

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -807,28 +807,6 @@
           detail: error.message
           dismissable: true

-  # Require the module with the given globals.
-  #
-  # The globals will be set on the `window` object and removed after the
-  # require completes.
-  #
-  # * `id` The {String} module name or path.
-  # * `globals` An optional {Object} to set as globals during require.
-  requireWithGlobals: (id, globals={}) ->
-    existingGlobals = {}
-    for key, value of globals
-      existingGlobals[key] = window[key]
-      window[key] = value
-
-    require(id)
-
-    for key, value of existingGlobals
-      if value is undefined
-        delete window[key]
-      else
-        window[key] = value
-    return
-
   onUpdateAvailable: (callback) ->
     @emitter.on 'update-available', callback


Ajax options got lost on its way through the script.

--- src/backbone_offline.coffee
+++ src/backbone_offline.coffee
@@ -201,18 +201,19 @@
   # 1. clear collection and store
   # 2. load new data
   full: (options = {}) ->
-    @ajax 'read', @collection.items, success: (response, status, xhr) =>
-      @storage.clear()
-      @collection.items.reset([], silent: true)
-      @collection.items.create(item, silent: true, local: true, regenerateId: true) for item in response
-      @collection.items.trigger('reset') unless options.silent
-      options.success(response) if options.success
+    @ajax 'read', @collection.items, _.extend {}, options,
+      success: (response, status, xhr) =>
+        @storage.clear()
+        @collection.items.reset([], silent: true)
+        @collection.items.create(item, silent: true, local: true, regenerateId: true) for item in response
+        @collection.items.trigger('reset') unless options.silent
+        options.success(response) if options.success

   # @storage.sync.incremental() - incremental storage synchronization
   # 1. pull() - request data from server
   # 2. push() - send modified data to server
   incremental: ->
-    @pull success: => @push()
+    @pull _.extend {}, options, success: => @push()

   # Runs incremental sync when storage was offline
   # after current request therefore don't duplicate requests
@@ -231,10 +232,11 @@
   #
   # @storage.sync.pull()
   pull: (options = {}) ->
-    @ajax 'read', @collection.items, success: (response, status, xhr) =>
-      @collection.destroyDiff(response)
-      @pullItem(item) for item in response
-      options.success() if options.success
+    @ajax 'read', @collection.items, _.extend {}, options,
+      success: (response, status, xhr) =>
+        @collection.destroyDiff(response)
+        @pullItem(item) for item in response
+        options.success() if options.success

   pullItem: (item) ->
     local = @collection.get(item.id)


add semi colons to static, remove whitespace

--- snippets.cson
+++ snippets.cson
@@ -40,13 +40,13 @@
     const { Component, PropTypes } = React;

     class $1 extends Component {
-      static displayName = '$1'
+      static displayName = '$1';

       static propTypes = {
         $2
-      }
+      };

-      render () {
+      render() {
         return null;
       }
     }


Move mobile buttons lower

Move mobile buttons lower

--- source/assets/javascripts/app.js.coffee
+++ source/assets/javascripts/app.js.coffee
@@ -147,11 +147,11 @@
       }
     }
     scale = width/200
-    height = (250 * 3 + 16) * scale
+    height = (250 * 3 + 64) * scale
     svg.css({ height: height })
     Snap.load '/assets/images/mobile-buttons.svg', (canvas) ->
       buttons = canvas.select 'g#Buttons'
-      top = 16 * scale
+      top = 32 * scale
       left = parseInt svg.css('padding-left')
       $.each config, (name, item) ->
         button = buttons.select "g##{item.id}"


Improve highlighting of HTML and plain-text chunks

--- grammars/language-eml.cson
+++ grammars/language-eml.cson
@@ -4,8 +4,9 @@
 patterns: [
   {include: "#addresses"}
   {include: "#headers"}
+  {include: "#boundary"}
   {include: "#encodedWord"}
-  {include: "#specials"}
+  {include: "#encodingTypes"}
   {include: "#uuid"}
   {include: "#base64"}
   {include: "#html"}
@@ -101,6 +102,46 @@
       (?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$
     """

+  # Multipart MIME boundary
+  boundary:
+    name:  'meta.multi-part.chunk.eml'
+    begin: '^(--(?!>).*)'
+    end:   '^(?=\\1)'
+    beginCaptures:
+      0: name: 'keyword.control.boundary.eml'
+    patterns: [{
+      # Content-Type: text/html;
+      name:  "meta.embedded.html.eml"
+      begin: "^(?i)(Content-Type:)\\s*(text/html(?=[\\s;+]).*)"
+      end:   "^(?=--(?!>))"
+      patterns: [
+        {include: "#boundaryHeaders"}
+        {include: 'text.html.basic'}
+      ]
+      contentName: "meta.embedded.html"
+      beginCaptures:
+        1: patterns: [include: "#headers"]
+        2: patterns: [include: "$self"]
+    },{
+      # Plain text (no header highlighting)
+      name:  "meta.embedded.text.eml"
+      begin: "^(?i)(Content-Type:)\\s*((?!text/html(?=[\\s;+]))\\S+.*)"
+      end:   "^(?=--(?!>))"
+      contentName: "markup.raw.html"
+      beginCaptures:
+        1: patterns: [include: "#headers"]
+        2: patterns: [include: "$self"]
+      patterns: [include: "#boundaryHeaders"]
+    },{
+      # Other headers unrelated to content-type
+      include: "$self"
+    }]
+
+  # Additional headers following Content-Type, but before body
+  boundaryHeaders:
+    begin: "\\G"
+    end: "^(?=\\s*)$"
+    patterns: [include: "$self"]

   # Header fields
   headers:
@@ -137,13 +178,6 @@
     name: 'keyword.control.encoded-word.eml'
     match: '(?i)=\\?utf-8\\?B\\?(.*)\\?='

-  # Content-Type: text/html;
-  html:
-    name:  "meta.embedded.html.eml"
-    begin: "(?i)^(?=<html[\\s>])"
-    end:   "(?<=</html>)"
-    patterns: [include: 'text.html.basic']
-
   # IPv4
   ipv4:
     name: 'variable.other.ipv4.eml'
@@ -176,21 +210,16 @@
       0: name: "punctuation.definition.comment.quote.eml"

   # Specials
-  specials:
-    patterns: [{
-      name: 'keyword.operator.special.eml'
-      match: """(?xi)
-        ( base64
-        | multipart\\/.*:
-        | image\\/.*;
-        | text\\/.*
-        | boundary=.*
-        )
-      """
-    },{
-      name: 'keyword.control.eml'
-      match: '^--(?!>).*',
-    }]
+  encodingTypes:
+    name: 'keyword.operator.special.eml'
+    match: """(?xi)
+      ( base64
+      | multipart\\/.*:
+      | image\\/.*;
+      | text\\/.*
+      | boundary=.*
+      )
+    """

   # UUID
   uuid:


Closes #1691; Fiz a grouping error in messages from history

--- packages/rocketchat-ui-message/message/message.coffee
+++ packages/rocketchat-ui-message/message/message.coffee
@@ -128,18 +128,29 @@

 			if previousDataset.date isnt currentDataset.date
 				$currentNode.addClass('new-day').removeClass('sequential')
+			else
+				$currentNode.removeClass('new-day')

-			if previousDataset.groupable is 'false' or previousDataset.username isnt currentDataset.username or parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
+			if previousDataset.groupable is 'false'
 				$currentNode.removeClass('sequential')
+			else
+				if previousDataset.username isnt currentDataset.username or parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
+					$currentNode.removeClass('sequential')
+				else
+					$currentNode.addClass('sequential')

 		if nextNode?.dataset?
 			nextDataset = nextNode.dataset

 			if nextDataset.date isnt currentDataset.date
 				$nextNode.addClass('new-day').removeClass('sequential')
+			else
+				$nextNode.removeClass('new-day')

 			if nextDataset.username isnt currentDataset.username or parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000
 				$nextNode.removeClass('sequential')
+			else
+				$nextNode.addClass('sequential')

 			if not nextNode?
 				templateInstance = view.parentView.parentView.parentView.parentView.parentView.templateInstance?()


fix(tests): remove unnecessary failing test

--- spec-nylas/buffered-process-spec.coffee
+++ spec-nylas/buffered-process-spec.coffee
@@ -29,19 +29,19 @@
           expect(errorSpy).toHaveBeenCalled()
           expect(errorSpy.mostRecentCall.args[0].error.message).toContain 'spawn bad-command-nope ENOENT'

-    describe "when there is not an error handler specified", ->
-      it "calls the error handler and does not throw an exception", ->
-        spyOn(process, "nextTick").andCallFake (fn) -> fn()
-
-        try
-          p = new BufferedProcess
-            command: 'bad-command-nope'
-            args: ['nothing']
-            options: {stdout: 'ignore'}
-
-        catch error
-          expect(error.message).toContain 'Failed to spawn command `bad-command-nope`'
-          expect(error.name).toBe 'BufferedProcessError'
+    # describe "when there is not an error handler specified", ->
+    #   it "calls the error handler and does not throw an exception", ->
+    #     spyOn(process, "nextTick").andCallFake (fn) -> fn()
+    #
+    #     try
+    #       p = new BufferedProcess
+    #         command: 'bad-command-nope'
+    #         args: ['nothing']
+    #         options: {stdout: 'ignore'}
+    #
+    #     catch error
+    #       expect(error.message).toContain 'Failed to spawn command `bad-command-nope`'
+    #       expect(error.name).toBe 'BufferedProcessError'

   describe "on Windows", ->
     originalPlatform = null


Fix Readme expectation

Fix Readme expectation

--- spec/installed-package-view-spec.coffee
+++ spec/installed-package-view-spec.coffee
@@ -129,5 +129,5 @@
         expect(pack.metadata.readme).toBe normalizePackageDataReadmeError

         view = new PackageDetailView(pack, new PackageManager())
-        expect(view.sections.packageReadme).not.toBe normalizePackageDataReadmeError
+        expect(view.sections.find('.package-readme').text()).not.toBe normalizePackageDataReadmeError
         expect(view.sections.find('.package-readme').text().trim()).toBe 'I am a Readme!'


Forgot to refactor method name.

Forgot to refactor method name.

--- lib/annotation/abstract-provider.coffee
+++ lib/annotation/abstract-provider.coffee
@@ -12,7 +12,7 @@

         atom.workspace.observeTextEditors (editor) =>
             editor.onDidSave (event) =>
-                @rescanAnnotations(editor)
+                @rescan(editor)

             @registerAnnotations editor
             @registerEvents editor


Support TypeScript test suites.

Support TypeScript test suites.

--- test/conf/karma-conf.coffee
+++ test/conf/karma-conf.coffee
@@ -3,45 +3,38 @@
     # The base path to resolve files is the top-level qiprofile
     # directory.
     basePath: '../..'
+
+    # The karma adapter frameworks to use.
+    frameworks: ['jspm', 'traceur', 'mocha', 'chai']

-    # The karma adapter frameworks to use.
-    frameworks: ['mocha', 'requirejs', 'chai']
+    # By default, karma loads node_modules sibling karma-* plug-ins.
+    # However, note that the plug-ins must be installed as siblings
+    # of the launched karma node context. In particular, a globally
+    # installed karma will not find locally installed karma plugins.
+    # For both this reason and to ensure version consistency, it is
+    # recommended that karma be installed locally in the project and
+    # executed on the command line using the following command:
+    #   ./node_modules/karam/bin/karma start
+    #plugins: ['karma-jspm', 'karma-mocha', 'karma-chai', 'karma-phantomjs-launcher']
+
+    # The karma-jspm option specifying the files to dynamically
+    # load via SystemJS.
+    jspm:
+      loadFiles: ['test/unit/**/*.spec.*'],
+      serveFiles: ['src/**/*.*'],
+      stripExtension: false

     # Print messages.
     client:
       captureConsole: true

-    # The test specs can be written in coffescript.
-    preprocessors: '**/*.coffee': 'coffee'
+    # The test specs can be written in CoffeeScript or TypeScript.
+    preprocessors:
+      '**/*.coffee': 'coffee'
+      '**/*.ts': 'typescript'

-    # The files to load. The included flag defers loading in deference
-    # to the requirejs loader. public/main.js is excluded
-    # below, since that is the web app requirejs config. Testing uses
-    # its own main config, which is the last file specified and is
-    # loaded immediately. The pattern declarations below configure
-    # karma to recognize the libraries. The libraries are then loaded
-    # by test/unit/main.
-    files: [
-      # The web app libraries.
-      {pattern: 'public/**/*.js', included: false}
-      # The test libraries.
-      {pattern: 'node_modules/angular-mocks/angular-mocks.js', included: false}
-      {pattern: 'node_modules/chai-as-promised/lib/chai-as-promised.js', included: false}
-      {pattern: 'node_modules/pako/dist/pako.js', included: false}
-      {pattern: 'node_modules/text-encoding/**/*.js', included: false}
-      # The test specs.
-      {pattern: 'test/unit/*Spec.coffee', included: false}
-      # The chai eventually assertion helper.
-      {pattern: 'test/unit/helpers/expect.coffee', included: false}
-      # Lastly, the test requirejs main config.
-      'test/unit/main.coffee'
-    ]
-
-    # The requirejs configuration is given by test/unit/main.coffee,
-    # not the application public/main.js file.
-    exclude: [
-      'public/main.js'
-    ]
+    # The files to load is empty since karma-jspm assumes that responsibility.
+    files: ['node_modules/traceur/bin/traceur.js']

     # The test results reporter.
     # Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
@@ -77,8 +70,14 @@
     # If the browser does not capture output in the given number of milliseconds,
     # then kill it.
     captureTimeout: 5000
+
+    # Allowing empty test suites is useful when commenting out tests. The
+    # default is true.
+    failOnEmptyTestSuite: false

     # The Continuous Integration mode.
     # If true, then karma will capture the browsers, run the tests and exit.
-    # This value is set in the grunt config file.
-    # singleRun: true
+    # This value is overridden in the Gruntfile as follows:
+    # * false if grunt is run with the --debug flag, true otherwise
+    # The default is false.
+    singleRun: true


Serve the tsconfig and typings.

Serve the tsconfig and typings.

--- test/conf/karma-conf.coffee
+++ test/conf/karma-conf.coffee
@@ -61,16 +61,14 @@
     jspm:
       config: 'jspm.config.js'
       loadFiles: ['src/**/*.spec.*']
-      serveFiles: ['src/**/!(*.spec).*']
+      #serveFiles: ['src/**/!(*.spec).*']
       # Include config and typings for TypeScript?
-      #serveFiles: ['src/**/*!(.spec).*', 'tsconfig.json', 'typings/**/*.d.ts']
+      serveFiles: ['src/**/*!(.spec).*', 'tsconfig.json', 'typings/**/*.d.ts']
       stripExtension: false

     # The test specs can be written in CoffeeScript or TypeScript.
     preprocessors:
       'src/**/*.js': ['babel', 'sourcemap']
-      #'src/**/*.coffee': ['coffee']
-      'src/**/*.ts': ['typescript', 'sourcemap']

     # The test suite language pre-processors.
     # CoffeeScript is supported out of the box.


More Atom packages

More Atom packages

--- atom/packages.cson
+++ atom/packages.cson
@@ -17,6 +17,8 @@
   "elm-test-runner"
   "elmjutsu"
   "file-icons"
+  "goto"
+  "goto-last-edit"
   "hasklig"
   "html-to-elm"
   "hyperclick"
@@ -33,5 +35,6 @@
   "pigments"
   "pure-syntax"
   "single-click-open"
+  "teletype"
   "vim-mode-plus"
 ]


Add JSX grammar to Atom

Add JSX grammar to Atom

--- atom/packages.cson
+++ atom/packages.cson
@@ -21,6 +21,7 @@
   "language-har"
   "language-hosts"
   "language-ini"
+  "language-javascript-jsx"
   "language-latex"
   "language-liquid"
   "language-lisp"


Shorten 'continue' snippet prefix to 'cont'

Having the prefix as 'continue' conflicts with my 'coutendl' snippet.

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -34,7 +34,7 @@
     'body': 'break;'

   'continue':
-    'prefix': 'continue'
+    'prefix': 'cont'
     'body': 'continue;'

   # Switch constructs


add viewModel component to track ab_test definition changes

--- js/inner.coffee
+++ js/inner.coffee
@@ -1422,7 +1422,10 @@
     @refreshing_projects = ko.observable(false);
     @max_possible_parallelism = ko.observable(24);
     @parallelism_options = ko.observableArray([1..@max_possible_parallelism()])
+    @ab_tests = ko.observable(window.circle_ab_tests)
     observableCount += 8 # are we still doing this?
+
+    @setup_ab_tests_sync()

     @setupPusher()

@@ -1435,6 +1438,14 @@
           "&filters%5B0%5D%5Battr%5D=custom_data.pr-followed" +
           "&filters%5B0%5D%5Bcomparison%5D=contains&filters%5B0%5D%5Bvalue%5D=" +
           path[1]
+
+  setup_ab_tests_sync: () =>
+    $(document).bind "circle_ab_tests_updated", =>
+      @ab_tests(window.circle_ab_tests)
+
+    # This may do something weird. No infinite loops, but maybe race conditions
+    @ab_tests.subscribe (newValue) ->
+      window.circle_ab_tests = newValue

   setupPusher: () =>
     key = switch renderContext.env


More failsafe patching

More failsafe patching

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -1,10 +1,11 @@
 intercept = ->
   R = require 'mocha'
     .Runner
+  emit = R::emit
   R::emit = ->
     delete R::emit
     listen @
-    @emit.apply @, arguments
+    emit.apply @, arguments

 do intercept if api = process?.env.APPVEYOR_API_URL


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -46,7 +46,7 @@
 test "#unbind namespaced", ->
   o = Object.extend({}, Bindable())

-  o.bind "test", ->
+  o.bind "Test.test", ->

 module()


Fix a clearCache test

Fix a clearCache test

--- test/lib/updateDevice-spec.coffee
+++ test/lib/updateDevice-spec.coffee
@@ -54,7 +54,7 @@
         @sut @uuid, {name: 'shakunetsu'}, done, @dependencies

       it 'should call clearCache with uuid', ->
-        expect(@clearCache).to.be.calledWith 'DEVICE_' + @uuid
+        expect(@clearCache).to.be.calledWith @uuid

     describe 'when update is called with that uuid and different name', ->
       beforeEach (done) ->


Make Ractive tests more readable.

Make Ractive tests more readable.

--- test/ractive_computed_test.coffee
+++ test/ractive_computed_test.coffee
@@ -11,7 +11,7 @@
     Name = Ento()
       .attr('first')
       .attr('last')
-      .attr('full', ['first','last'], -> [@first, @last].join(' '))
+      .attr('full', ['first', 'last'], -> [@first, @last].join(' '))

   beforeEach 'instanciate it', ->
     user = new Name(first: "Jack", last: "Frost")
@@ -30,29 +30,27 @@
     user.last = "Johnson"
     expect(document.body.innerHTML).eql '
Jack Johnson
'

-  it 'ractive.set should propagate to the model', ->
-    view.set('user.last', 'Crawford')
-    expect(user.full).eql 'Jack Crawford'
+  describe 'ractive.set', ->
+    beforeEach ->
+      view.set('user.last', 'Crawford')

-  it 'ractive.set should rerender computed fields', ->
-    view.set('user.last', 'Black')
-    expect(document.body.innerHTML).eql '
Jack Black
'
+    it 'should propagate to the model', ->
+      expect(user.full).eql 'Jack Crawford'

-  it 'ractive reset', ->
-    view.set('user', { first: 'Harry', last: 'Lemon' })
-    expect(document.body.innerHTML).eql '
Harry Lemon
'
+    it 'should rerender computed fields', ->
+      expect(document.body.innerHTML).eql '
Jack Crawford
'

-  it 'ractive reset with an ento object', ->
-    view.set('user', new Name(first: 'Harry', last: 'Lemon'))
-    expect(document.body.innerHTML).eql '
Harry Lemon
'
+  describe 'ractive reset', ->
+    it 'should work with set(object)', ->
+      view.set('user', { first: 'Harry', last: 'Lemon' })
+      expect(document.body.innerHTML).eql '
Harry Lemon
'

-  describe 'teardown should unbind event handlers', ->
-    it 'remove the change handler', ->
-      expect(user._events.change).not.be.undefined
-      view.teardown()
-      expect(user._events.change).be.undefined
+    it 'should work with set(ento object)', ->
+      view.set('user', new Name(first: 'Harry', last: 'Lemon'))
+      expect(document.body.innerHTML).eql '
Harry Lemon
'

-    it 'leave another change handler unharmed', ->
+  describe 'teardown', ->
+    it 'unbind change handlers', ->
       user.on 'change', ->
       expect(user._events.change).have.length 2
       view.teardown()


Interpolate pathToCreate instead of path

This regressed as part of the migration to using helpers from
path instead of fs-utils.

Close #608

--- src/packages/tree-view/lib/tree-view.coffee
+++ src/packages/tree-view/lib/tree-view.coffee
@@ -278,7 +278,7 @@
         try
           if fsUtils.exists(pathToCreate)
             pathType = if fsUtils.isFileSync(pathToCreate) then "file" else "directory"
-            dialog.showError("Error: A #{pathType} already exists at path '#{path}'. Try a different path.")
+            dialog.showError("Error: A #{pathType} already exists at path '#{pathToCreate}'. Try a different path.")
           else if endsWithDirectorySeparator
             fsUtils.makeTree(pathToCreate)
             dialog.cancel()


Implement clean test

Implement clean test

--- test/node_gyp.coffee
+++ test/node_gyp.coffee
@@ -108,8 +108,14 @@
 				if err then done() else done(new Error 'expected build to fail')

 	describe 'clean', ->
-		it 'should remove the build directory', ->
-			;
+		it 'should remove the build directory', (done) ->
+			execGruntTask 'clean', (err) ->
+				return done(err) if err
+
+				if fs.existsSync './build/'
+					return done(new Error 'expected build directory to be removed')
+
+				done()

 	describe 'rebuild', ->
 		it 'should rebuild a release build by default', ->


OPCE4: fixed missing spaces and bad index in tests + added test for deltas

--- test/rte-test.coffee
+++ test/rte-test.coffee
@@ -28,9 +28,9 @@

   it 'insert words correctly', ->
     rte1 = new Rte "is"
-    rte1.insertWords(0, ["This"])
+    rte1.insertWords(0, ["This "])
     rte1.val().should.equal "This is"
-    rte1.insertWords(2, ["sparta", "!"])
+    rte1.insertWords(2, [" sparta ", "!"])
     rte1.val().should.equal "This is sparta !"

   it 'delete words correctly', ->
@@ -44,9 +44,9 @@

     rte1 = new Rte "There is a mistake in this sentence! there"
     rte1.deleteWords(7, 8)
-    rte1.val().should.equal "There is a mistake in this sentence!"
+    rte1.val().should.equal "There is a mistake in this sentence! "

-  it 'insert characters correctly at relative position', ->
+  it 'insert characters correctly at relative positions', ->
     rte1 = new Rte "I lot the gam"
     rte1.insert({startPos: {word:1, pos:2}}, 's')
     rte1.val().should.equal "I lost the gam"
@@ -88,6 +88,27 @@
   #   sel = new Selection({word:1, pos:0}, {word:0, pos:0})
   #   expect(rte1.deleteSel(sel)).to.throw Error("Invalid selection")

+  it 'should accept deltas (insert)', ->
+    delta = { ops:[
+      { insert: 'Gandalf', attributes: { bold: true } },
+      { insert: ' the ' },
+      { insert: 'Grey', attributes: { color: '#ccc' } }
+      ] }
+    rte1 = new Rte ""
+    rte1.delta delta
+    rte1.val().should.equal "Gandalf the Grey"
+    rte1._rte.words.length.should.equal 3
+    rte1._rte.words[0].word.should.equal "Gandalf "
+
+  it 'should accept deltas (retain & delete)', ->
+    delta = { ops:[
+      { retain: 7, attributes: { bold: true } },
+      { delete: 4},
+      ] }
+    rte1 = new Rte "Gandalf the Grey"
+    rte1.delta delta
+    rte1.val().should.equal "Gandalf Grey"
+
 describe 'Selection object should', ->
   sel = rte = null
   it 'be initialized with two parameters', ->
@@ -101,15 +122,15 @@

   it 'be initialized with three parameters', ->
     rte = new Rte "Zero One two three four five"
-    sel = new Selection 0, 1, rte
+    sel = new Selection 1, 7, rte

     sel.should.have.property('startPos')
     sel.should.have.deep.property('startPos.word', 0)
-    sel.should.have.deep.property('startPos.pos', 0)
+    sel.should.have.deep.property('startPos.pos', 1)

     sel.should.have.property('endPos')
-    sel.should.have.deep.property('endPos.word', 0)
-    sel.should.have.deep.property('endPos.pos', 1)
+    sel.should.have.deep.property('endPos.word', 1)
+    sel.should.have.deep.property('endPos.pos', 2)

   it 'be initialized with four parameters', ->
     sel = new Selection 0, 1, 2, 3