converted tab indentation to spaces

converted tab indentation to spaces

--- index.coffee
+++ index.coffee
@@ -1,14 +1,14 @@
 # inspired by assetgraph
-#	https://github.com/One-com/assetgraph
+# https://github.com/One-com/assetgraph
 #
 # uses these libraries for html,css,js parsing/minification
-# 	https://github.com/fb55/htmlparser2
-#	https://github.com/visionmedia/css
-#	https://github.com/mishoo/UglifyJS2
+#   https://github.com/fb55/htmlparser2
+# https://github.com/visionmedia/css
+# https://github.com/mishoo/UglifyJS2
 #
 # might be interesting points for further research
-# 	https://github.com/kangax/html-minifier
-# 	https://github.com/GoalSmashers/enhance-css
+#   https://github.com/kangax/html-minifier
+#   https://github.com/GoalSmashers/enhance-css

 htmlparser = require 'htmlparser2'
 css        = require 'css'
@@ -22,516 +22,516 @@
 shell      = require 'shelljs'

 # NOTE: renaming assets based on MD5 hash must be done in order because any
-#		assets that depend on that renamed file will mean the
-#		reference to that asset has to be updated, which will cascade through
-#		the graph! the solution is to traverse through all dependencies, and
-#		rename leaf nodes first, then work backwards.
+#   assets that depend on that renamed file will mean the
+#   reference to that asset has to be updated, which will cascade through
+#   the graph! the solution is to traverse through all dependencies, and
+#   rename leaf nodes first, then work backwards.

 requireJS = null

 class Asset

-	constructor: (@root, @filepath, @ag=null) ->
-		absPath = join @root, @filepath
-		@type     = @_determineType absPath
-		@to       = []  # Assets this references
-		@from     = []  # Assets referencing this
-		@minify   = false
-		@pending  = [] # unresolved references to other nodes
-		@obj      = @_buildObject @filepath, @type
-
-
-	move: (destination) ->
-		oldpath = JSON.parse(JSON.stringify(@filepath))
-		ext = path.extname destination
-
-		# find all references to the asset and move
-		for f in @from
-			# f is pointing at the asset to move
-			if f.asset.type is 'style'
-				f.node.value = Asset.replaceStyleSheetUrl(f.node.value, destination)
-				#f.node.attribs.href = destination
-			else if f.asset.type is 'javascript'
-				if f.inRequireJS
-					# this asset reference is in a RequireJS block
-					if f.node.value.indexOf('text!') >= 0
-						# this is a RequireJS plugin include
-						f.node.value = "text!#{destination}"
-					else
-						# omit the file extension for javascript references in RequireJS blocks
-						d = join path.dirname(destination), path.basename(destination, ext)
-						# omit the leading slash for javascript references in RequireJS blocks
-						if d.indexOf('/') is 0
-							d = d.substring 1
-						f.node.value = d
-			else if f.asset.type is 'html'
-				# the asset that requires this one is an html document
-				if (@type is 'javascript') and f.inRequireJS
-					f.node.attribs['data-main'] = join path.dirname(destination), path.basename(destination, '.js')
-				else if @type is 'style'
-					f.node.attribs.href = destination
-				else
-					f.node.attribs.src = destination
-			else if f.asset.type is 'image'
-				if @type is 'style'
-					# TODO: this needs to be smarter so it doesn't clobber other values (e.g., no-repeat, etc)
-					f.node.value = "url(#{destination})"
-			else
-				console.log 'TODO: support type', f.asset.type, f.node, 'host type', @type
-
-		# change this file's path
-		@filepath = destination
-		if @ag
-			@ag.addAsset this
-			delete @ag.nodes[oldpath]
-
-
-	resolveDependencies: ->
-		for p in @pending
-
-			if !@ag.nodes[p.filepath]
-				asset = new Asset(@root, p.filepath, @ag)
-			else
-				asset = @ag.nodes[p.filepath]
-
-			#isNew = @ag.nodes[p.filepath]?
-			#asset = @ag.nodes[p.filepath]? or new Asset(@root, p.filepath, @ag)
-			#console.log 'isNew', isNew, 'pending count', asset.pending.length
-			# only add the asset if it's object could be created (javascript ast, html dom, css style, etc)
-			if asset.obj
-				asset.from.push { asset: this, node: p.node, inRequireJS: p.inRequireJS }
-				@to.push { asset: asset, node: p.node, inRequireJS: p.inRequireJS }
-				added = @ag.addAsset asset
-
-				if added then asset.resolveDependencies()
-		@pending = []
-
-
-	writeToDisc: (destination, useHashName) ->
-		@written = true # mark this asset as written to disc first, to avoid infinite graph cycles
-
-		# renaming assets based on content hash or using a prefix will cause the
-		# references to change, so update all assets that reference this one first.
-		if useHashName or @prefix
-			for t in @to
-				if !t.asset.written
-					t.asset.writeToDisc destination, useHashName
-
-		if useHashName and @filepath isnt 'index.html'  # don't rename the index file
-			# calculate this asset's hash because children's references have changed
-			newPath = path.dirname(@filepath) + '/' + @_hash() + path.extname(@filepath)
-		else
-			newPath = destination + @filepath
-
-		if useHashName or @prefix
-			if @filepath isnt 'index.html'  # don't rename the index file
-				# a URL prefix is specified, so rename the files to include the URL prefix
-				if @prefix
-					@move('//' + @prefix + newPath)
-				else
-					@move newPath
-
-		# write this element out
-		out = @_toString()
-		if out
-			if @filepath is 'index.html'
-				fs.writeFileSync join(destination, @filepath), out
-			else
-				fs.writeFileSync join(destination, newPath), out
-
-
-	_buildObject: ->
-		result = null
-		filepath = @filepath
-		type = @type
-		absPath = join @root, filepath
-		if !fs.existsSync absPath
-			console.log "error:file #{absPath} doesn't exist."
-		else
-			file = fs.readFileSync absPath, 'utf8'
-			if type is 'html'
-				#
-				# TODO remove after html templates are supported
-				if file.indexOf('<%') >= 0
-					console.log 'warning: template directives found. Not parsing', filepath
-					return file
-				#
-				d = null
-				handler = new htmlparser.DomHandler (er, dom) =>
-					if er
-						throw new Error(er)
-					else
-						# parsing done, analyze the DOM
-						for d in dom
-							@_parseDOMNode d
-					d = dom
-
-				parser = new htmlparser.Parser handler
-				parser.parseComplete file
-				result = d
-			else if type is 'style'
-				result = @_parseStyleSheet filepath, file
-			else if type is 'javascript'
-				result = @_parseJavascript filepath, file
-			else if type is 'image'
-				result = fs.readFileSync absPath
-		result
-
-
-	_determineType: (filepath) ->
-		ext = path.extname(filepath or '').split '.'
-		ext = ext[ext.length - 1]
-		if ext is 'html' or ext is 'htm'
-			type = 'html'
-		else if ext is 'js'
-			type = 'javascript'
-		else if ext is 'css'
-			type = 'style'
-		else if ext is 'png' or ext is 'gif' or ext is 'jpg' or ext is 'jpeg' or ext is 'ico' or ext is 'svg'
-			type = 'image'
-		type
-
-
-	_foundAssetReference: (filepath, node, inRequireJS=false) ->
-		# if there's no asset graph, don't bother linking up the asset
-		if !@ag then return
-
-		if !@ag.nodes[filepath]
-			console.log 'loading ', filepath
-
-		@pending.push {
-			filepath    : filepath
-			node        : node
-			inRequireJS : inRequireJS
-		}
-
-
-	# calculate the md5 hash of this asset's contents
-	_hash: ->
-		out = @_toString()
-		md5sum = crypto.createHash 'md5'
-		md5sum.update out
-		md5sum.digest 'hex'
-
-
-	_minify: ->
-		console.log 'calling minify on', @filepath, @type
-		minified = ''
-		if @type is 'javascript'
-			@obj.figure_out_scope()
-			# https://github.com/mishoo/UglifyJS2#compressor-options
-			# http://lisperator.net/uglifyjs/compress
-			compressor = UglifyJS.Compressor { unused: false}
-			@obj.transform compressor
-			minified = @obj.print_to_string { beautify: false }
-		else if @type is 'style'
-			minified = css.stringify @obj, { compress: true }
-		else if @type is 'html'
-			# TODO remove after html templates are supported
-			if typeof(@obj) is 'string'
-				minified = @obj
-			else
-				for d in @obj
-					minified += htmlparser.DomUtils.getOuterHTML(d)
-		else if @type is 'image'
-			minified = @obj
-			ext = path.extname @filepath
-			if ext is '.jpg' or ext is '.jpeg'
-				if shell.which 'jpegtran'
-					console.log 'optimizing', @filepath, 'via jpegtran'
-					tmpfile = join os.tmpdir(), 'wag12345'
-					outfile = join os.tmpdir(), 'wag12345.out'
-					fs.writeFileSync tmpfile, @obj
-					response = shell.exec "jpegtran -copy none -optimize -perfect -progressive -outfile #{outfile} #{tmpfile}", { silent : true }
-					if response.code isnt 0
-						console.log 'something went wrong; failed to optimize', @filepath, response.output
-					else
-						minified = fs.readFileSync outfile
-					shell.rm tmpfile
-					shell.rm outfile
-				else
-					console.log 'WARN jpegtran is not installed, skipping optimizing ', @filepath
-			else if ext is '.png'
-				tmpfile = join os.tmpdir(), 'wag12345'
-				fs.writeFileSync tmpfile, @obj
-
-				if shell.which 'pngcrush'
-					console.log 'optimizing', @filepath, 'via pngcrush'
-					response = shell.exec "pngcrush -reduce -brute -ow #{tmpfile}", { silent : true }
-					if response.code isnt 0
-						console.log 'something went wrong; failed to optimize', @filepath, response.output
-				else
-					console.log 'WARN optipng is not installed, skipping optimizing ', @filepath
-
-				if shell.which 'optipng'
-					console.log 'optimizing', @filepath, 'via optipng'
-					response = shell.exec "optipng #{tmpfile}", { silent : true }
-					if response.code isnt 0
-						console.log 'something went wrong; failed to optimize', @filepath, response.output
-				else
-					console.log 'WARN optipng is not installed, skipping optimizing ', @filepath
-
-				if shell.which 'pngquant'
-					console.log 'optimizing', @filepath, 'via pngquant'
-					response = shell.exec "pngquant --force #{tmpfile}", { silent : true }
-					if response.code isnt 0
-						console.log 'something went wrong; failed to optimize', @filepath, response.output
-					else
-						shell.mv '-f', "#{tmpfile}-fs8.png", tmpfile
-				else
-					console.log 'WARN pngquant is not installed, skipping optimizing ', @filepath
-
-				minified = fs.readFileSync tmpfile
-				shell.rm tmpfile
-			else if ext is '.svg'
-				if shell.which 'svgo'
-					console.log 'optimizing', @filepath, 'via svgo'
-					tmpfile = join os.tmpdir(), 'wag12345'
-					fs.writeFileSync tmpfile, @obj
-					response = shell.exec "svgo --input #{tmpfile} --output -", { silent : true }
-					if response.code isnt 0
-						console.log 'something went wrong; failed to optimize', @filepath, response.output
-					else
-						minified = response.output
-				else
-					console.log 'WARN svgo is not installed, skipping optimizing ', @filepath
-		minified
-
-
-	# recursively parse a DOM node's structure
-	_parseDOMNode: (node) ->
-		# html relation types:
-		# 	type: tag    name: link
-		# 	type: script name: script
-		# 	type: tag    name: a
-		# 	type: tag    name: style
-		#   type: tag    name: img
-
-		newAssetPath = null
-		inRequireJS = false
-
-		if node.type is 'tag' and (node.name is 'style' or node.name is 'link')
-			newAssetPath = node.attribs.href
-		else if node.type is 'script' and node.name is 'script'
-			# if the src attribute isn't set, this must be an inline script with a body
-			if !node.attribs or !node.attribs.src
-				# TODO: parse node.children to get the actual javascript code?
-			else
-				if node.attribs['data-main']
-					console.log 'found requirejs entry point', node.attribs.src, 'main', node.attribs['data-main']
-					# found a requireJS config and we're trying to locate it, so parse it
-					newAssetPath = "#{node.attribs['data-main']}.js"
-					inRequireJS = true
-				else
-					console.log 'external script found', node.attribs.src
-					newAssetPath = node.attribs.src
-		else if node.type is 'tag' and node.name is 'img' and node.attribs.src and node.attribs.src isnt 'src'
-			console.log "found image reference '#{node.attribs.src}'"
-			newAssetPath = node.attribs.src
-
-		if newAssetPath then @_foundAssetReference(newAssetPath, node, inRequireJS)
-
-		# if this DOM node has any children, parse them for asset references
-		if node.children
-			for n in node.children
-				@_parseDOMNode n
-
-
-	_parseJavascript: (filepath, file) ->
-		toplevel = null
-		opts = {}
-		###
-		opts =
-			filename : filepath
-			toplevel : toplevel
-		###
-		toplevel = UglifyJS.parse file, opts
-
-		# track nodes already encountered to prevent graph cycles from infinitely recursing
-		#visited = {}
-
-		# recursively parse an UglifyJS AST node
-		walker = new UglifyJS.TreeWalker (node) =>
-			if node instanceof UglifyJS.AST_Call and node.start.type is 'name' and node.start.value is 'require'
-				# has the require.config
-				if node.expression.end.value is 'config'
-					requireJS = @_parseRequireJSConfig node
-				else if node.args.length is 2
-					# has the script paths to require
-					@_parseRequireStatement node
-			if node instanceof UglifyJS.AST_Call and node.start.type is 'name' and node.start.value is 'define'
-				@_parseRequireStatement node
-			false # descend through the entire tree
-
-		toplevel.walk walker
-		#console.log toplevel.print_to_string({ beautify: true })
-		toplevel
-
-
-	# parse a RequireJS configuration block and return it as JSON
-	_parseRequireJSConfig: (node) ->
-		cfg = {}
-		for k in node.args[0].properties
-			if k.key is 'baseUrl'
-				cfg.baseUrl = k.value.value
-			if k.key is 'paths'
-				cfg.paths = {}
-				for j in k.value.properties
-					cfg.paths[j.key] = j.value.value
-		cfg
-
-
-	# parse the required scripts out of the require/define statement
-	_parseRequireStatement: (node) ->
-		if !node.args[0].elements
-			return
-
-		p = if requireJS then requireJS.baseUrl else ''
-
-		# e.g., error: could not find asset text!/templates/partials/email-confirmation-banner.html
-		for scriptpath in node.args[0].elements
-			newAssetPath = null
-			if scriptpath.value
-				# some requirejs includes are html templates. e.g., text!/templates/signup.html
-				if scriptpath.value.indexOf('text!') is 0
-					newAssetPath = join p, scriptpath.value.substring(5)
-				else if fs.existsSync join(@root, p, "#{scriptpath.value}.js")
-					newAssetPath = join(p, "#{scriptpath.value}.js")
-				else if scriptpath.value is 'exports' or (requireJS and scriptpath.value is 'require')
-					# ignore exports for scriptpath.value and references to requireJS
-				else if requireJS
-					found = false
-					# see if the script corresponds to one in the RequireJS path list
-					for alias, pa of requireJS.paths
-						if alias is scriptpath.value
-							found = true
-							newAssetPath = join p, "#{pa}.js"
-					if !found
-						console.log "error 1: could not find asset #{scriptpath.value}"
-				else
-					console.log "error: could not find asset #{scriptpath.value}"
-
-			inRequireJS = true
-			if newAssetPath then @_foundAssetReference(newAssetPath, scriptpath, inRequireJS)
-
-
-	_parseStyleSheet: (filepath, file) ->
-		c = css.parse file
-		for rule in c.stylesheet.rules
-			if rule.type is 'rule'
-				for dec in rule.declarations
-					if dec.type is 'declaration' and (dec.property is 'background-image' or dec.property is 'background')
-						url = Asset.parseStyleSheetUrl dec.value
-						if url
-							if url.indexOf('/') is 0
-								# image url is absolute path
-								fullpath = url
-							else
-								# image url is relative path to the css file including it
-								fullpath = join(path.dirname(filepath), url)
-							@_foundAssetReference fullpath, dec
-		c
-
-	_toString: ->
-		if @minify then return @_minify()
-
-		if @type is 'html'
-			#
-			# TODO remove after html templates are supported
-			if typeof(@obj) is 'string'
-				updated = @obj
-			else
-				#
-				updated = ''
-				for d in @obj
-					updated += htmlparser.DomUtils.getOuterHTML(d)
-		else if @type is 'javascript'
-			updated = @obj.print_to_string({ beautify: false })
-		else if @type is 'style'
-			updated = css.stringify(@obj, { compress: false })
-		else if @type is 'image'
-			updated = @obj
-		else
-			updated = ''
-		updated
-
-	@parseStyleSheetUrl: (declaration) ->
-		b = declaration.match /url\((.+?)\)/gi
-		if b
-			pos = declaration.indexOf 'url('
-			if pos >= 0
-				# found an image url
-				pos2 = declaration.indexOf ')', pos+4
-				url = declaration.substring(pos+4, pos2).trim()
-				return url.replace /"|'/g, '' # strip quotes from URL
-		''
-
-	@replaceStyleSheetUrl: (declaration, newUrl) ->
-		currentUrl = Asset.parseStyleSheetUrl declaration
-		declaration.replace currentUrl, newUrl
+  constructor: (@root, @filepath, @ag=null) ->
+    absPath = join @root, @filepath
+    @type     = @_determineType absPath
+    @to       = []  # Assets this references
+    @from     = []  # Assets referencing this
+    @minify   = false
+    @pending  = [] # unresolved references to other nodes
+    @obj      = @_buildObject @filepath, @type
+
+
+  move: (destination) ->
+    oldpath = JSON.parse(JSON.stringify(@filepath))
+    ext = path.extname destination
+
+    # find all references to the asset and move
+    for f in @from
+      # f is pointing at the asset to move
+      if f.asset.type is 'style'
+        f.node.value = Asset.replaceStyleSheetUrl(f.node.value, destination)
+        #f.node.attribs.href = destination
+      else if f.asset.type is 'javascript'
+        if f.inRequireJS
+          # this asset reference is in a RequireJS block
+          if f.node.value.indexOf('text!') >= 0
+            # this is a RequireJS plugin include
+            f.node.value = "text!#{destination}"
+          else
+            # omit the file extension for javascript references in RequireJS blocks
+            d = join path.dirname(destination), path.basename(destination, ext)
+            # omit the leading slash for javascript references in RequireJS blocks
+            if d.indexOf('/') is 0
+              d = d.substring 1
+            f.node.value = d
+      else if f.asset.type is 'html'
+        # the asset that requires this one is an html document
+        if (@type is 'javascript') and f.inRequireJS
+          f.node.attribs['data-main'] = join path.dirname(destination), path.basename(destination, '.js')
+        else if @type is 'style'
+          f.node.attribs.href = destination
+        else
+          f.node.attribs.src = destination
+      else if f.asset.type is 'image'
+        if @type is 'style'
+          # TODO: this needs to be smarter so it doesn't clobber other values (e.g., no-repeat, etc)
+          f.node.value = "url(#{destination})"
+      else
+        console.log 'TODO: support type', f.asset.type, f.node, 'host type', @type
+
+    # change this file's path
+    @filepath = destination
+    if @ag
+      @ag.addAsset this
+      delete @ag.nodes[oldpath]
+
+
+  resolveDependencies: ->
+    for p in @pending
+
+      if !@ag.nodes[p.filepath]
+        asset = new Asset(@root, p.filepath, @ag)
+      else
+        asset = @ag.nodes[p.filepath]
+
+      #isNew = @ag.nodes[p.filepath]?
+      #asset = @ag.nodes[p.filepath]? or new Asset(@root, p.filepath, @ag)
+      #console.log 'isNew', isNew, 'pending count', asset.pending.length
+      # only add the asset if it's object could be created (javascript ast, html dom, css style, etc)
+      if asset.obj
+        asset.from.push { asset: this, node: p.node, inRequireJS: p.inRequireJS }
+        @to.push { asset: asset, node: p.node, inRequireJS: p.inRequireJS }
+        added = @ag.addAsset asset
+
+        if added then asset.resolveDependencies()
+    @pending = []
+
+
+  writeToDisc: (destination, useHashName) ->
+    @written = true # mark this asset as written to disc first, to avoid infinite graph cycles
+
+    # renaming assets based on content hash or using a prefix will cause the
+    # references to change, so update all assets that reference this one first.
+    if useHashName or @prefix
+      for t in @to
+        if !t.asset.written
+          t.asset.writeToDisc destination, useHashName
+
+    if useHashName and @filepath isnt 'index.html'  # don't rename the index file
+      # calculate this asset's hash because children's references have changed
+      newPath = path.dirname(@filepath) + '/' + @_hash() + path.extname(@filepath)
+    else
+      newPath = destination + @filepath
+
+    if useHashName or @prefix
+      if @filepath isnt 'index.html'  # don't rename the index file
+        # a URL prefix is specified, so rename the files to include the URL prefix
+        if @prefix
+          @move('//' + @prefix + newPath)
+        else
+          @move newPath
+
+    # write this element out
+    out = @_toString()
+    if out
+      if @filepath is 'index.html'
+        fs.writeFileSync join(destination, @filepath), out
+      else
+        fs.writeFileSync join(destination, newPath), out
+
+
+  _buildObject: ->
+    result = null
+    filepath = @filepath
+    type = @type
+    absPath = join @root, filepath
+    if !fs.existsSync absPath
+      console.log "error:file #{absPath} doesn't exist."
+    else
+      file = fs.readFileSync absPath, 'utf8'
+      if type is 'html'
+        #
+        # TODO remove after html templates are supported
+        if file.indexOf('<%') >= 0
+          console.log 'warning: template directives found. Not parsing', filepath
+          return file
+        #
+        d = null
+        handler = new htmlparser.DomHandler (er, dom) =>
+          if er
+            throw new Error(er)
+          else
+            # parsing done, analyze the DOM
+            for d in dom
+              @_parseDOMNode d
+          d = dom
+
+        parser = new htmlparser.Parser handler
+        parser.parseComplete file
+        result = d
+      else if type is 'style'
+        result = @_parseStyleSheet filepath, file
+      else if type is 'javascript'
+        result = @_parseJavascript filepath, file
+      else if type is 'image'
+        result = fs.readFileSync absPath
+    result
+
+
+  _determineType: (filepath) ->
+    ext = path.extname(filepath or '').split '.'
+    ext = ext[ext.length - 1]
+    if ext is 'html' or ext is 'htm'
+      type = 'html'
+    else if ext is 'js'
+      type = 'javascript'
+    else if ext is 'css'
+      type = 'style'
+    else if ext is 'png' or ext is 'gif' or ext is 'jpg' or ext is 'jpeg' or ext is 'ico' or ext is 'svg'
+      type = 'image'
+    type
+
+
+  _foundAssetReference: (filepath, node, inRequireJS=false) ->
+    # if there's no asset graph, don't bother linking up the asset
+    if !@ag then return
+
+    if !@ag.nodes[filepath]
+      console.log 'loading ', filepath
+
+    @pending.push {
+      filepath    : filepath
+      node        : node
+      inRequireJS : inRequireJS
+    }
+
+
+  # calculate the md5 hash of this asset's contents
+  _hash: ->
+    out = @_toString()
+    md5sum = crypto.createHash 'md5'
+    md5sum.update out
+    md5sum.digest 'hex'
+
+
+  _minify: ->
+    console.log 'calling minify on', @filepath, @type
+    minified = ''
+    if @type is 'javascript'
+      @obj.figure_out_scope()
+      # https://github.com/mishoo/UglifyJS2#compressor-options
+      # http://lisperator.net/uglifyjs/compress
+      compressor = UglifyJS.Compressor { unused: false}
+      @obj.transform compressor
+      minified = @obj.print_to_string { beautify: false }
+    else if @type is 'style'
+      minified = css.stringify @obj, { compress: true }
+    else if @type is 'html'
+      # TODO remove after html templates are supported
+      if typeof(@obj) is 'string'
+        minified = @obj
+      else
+        for d in @obj
+          minified += htmlparser.DomUtils.getOuterHTML(d)
+    else if @type is 'image'
+      minified = @obj
+      ext = path.extname @filepath
+      if ext is '.jpg' or ext is '.jpeg'
+        if shell.which 'jpegtran'
+          console.log 'optimizing', @filepath, 'via jpegtran'
+          tmpfile = join os.tmpdir(), 'wag12345'
+          outfile = join os.tmpdir(), 'wag12345.out'
+          fs.writeFileSync tmpfile, @obj
+          response = shell.exec "jpegtran -copy none -optimize -perfect -progressive -outfile #{outfile} #{tmpfile}", { silent : true }
+          if response.code isnt 0
+            console.log 'something went wrong; failed to optimize', @filepath, response.output
+          else
+            minified = fs.readFileSync outfile
+          shell.rm tmpfile
+          shell.rm outfile
+        else
+          console.log 'WARN jpegtran is not installed, skipping optimizing ', @filepath
+      else if ext is '.png'
+        tmpfile = join os.tmpdir(), 'wag12345'
+        fs.writeFileSync tmpfile, @obj
+
+        if shell.which 'pngcrush'
+          console.log 'optimizing', @filepath, 'via pngcrush'
+          response = shell.exec "pngcrush -reduce -brute -ow #{tmpfile}", { silent : true }
+          if response.code isnt 0
+            console.log 'something went wrong; failed to optimize', @filepath, response.output
+        else
+          console.log 'WARN optipng is not installed, skipping optimizing ', @filepath
+
+        if shell.which 'optipng'
+          console.log 'optimizing', @filepath, 'via optipng'
+          response = shell.exec "optipng #{tmpfile}", { silent : true }
+          if response.code isnt 0
+            console.log 'something went wrong; failed to optimize', @filepath, response.output
+        else
+          console.log 'WARN optipng is not installed, skipping optimizing ', @filepath
+
+        if shell.which 'pngquant'
+          console.log 'optimizing', @filepath, 'via pngquant'
+          response = shell.exec "pngquant --force #{tmpfile}", { silent : true }
+          if response.code isnt 0
+            console.log 'something went wrong; failed to optimize', @filepath, response.output
+          else
+            shell.mv '-f', "#{tmpfile}-fs8.png", tmpfile
+        else
+          console.log 'WARN pngquant is not installed, skipping optimizing ', @filepath
+
+        minified = fs.readFileSync tmpfile
+        shell.rm tmpfile
+      else if ext is '.svg'
+        if shell.which 'svgo'
+          console.log 'optimizing', @filepath, 'via svgo'
+          tmpfile = join os.tmpdir(), 'wag12345'
+          fs.writeFileSync tmpfile, @obj
+          response = shell.exec "svgo --input #{tmpfile} --output -", { silent : true }
+          if response.code isnt 0
+            console.log 'something went wrong; failed to optimize', @filepath, response.output
+          else
+            minified = response.output
+        else
+          console.log 'WARN svgo is not installed, skipping optimizing ', @filepath
+    minified
+
+
+  # recursively parse a DOM node's structure
+  _parseDOMNode: (node) ->
+    # html relation types:
+    #   type: tag    name: link
+    #   type: script name: script
+    #   type: tag    name: a
+    #   type: tag    name: style
+    #   type: tag    name: img
+
+    newAssetPath = null
+    inRequireJS = false
+
+    if node.type is 'tag' and (node.name is 'style' or node.name is 'link')
+      newAssetPath = node.attribs.href
+    else if node.type is 'script' and node.name is 'script'
+      # if the src attribute isn't set, this must be an inline script with a body
+      if !node.attribs or !node.attribs.src
+        # TODO: parse node.children to get the actual javascript code?
+      else
+        if node.attribs['data-main']
+          console.log 'found requirejs entry point', node.attribs.src, 'main', node.attribs['data-main']
+          # found a requireJS config and we're trying to locate it, so parse it
+          newAssetPath = "#{node.attribs['data-main']}.js"
+          inRequireJS = true
+        else
+          console.log 'external script found', node.attribs.src
+          newAssetPath = node.attribs.src
+    else if node.type is 'tag' and node.name is 'img' and node.attribs.src and node.attribs.src isnt 'src'
+      console.log "found image reference '#{node.attribs.src}'"
+      newAssetPath = node.attribs.src
+
+    if newAssetPath then @_foundAssetReference(newAssetPath, node, inRequireJS)
+
+    # if this DOM node has any children, parse them for asset references
+    if node.children
+      for n in node.children
+        @_parseDOMNode n
+
+
+  _parseJavascript: (filepath, file) ->
+    toplevel = null
+    opts = {}
+    ###
+    opts =
+      filename : filepath
+      toplevel : toplevel
+    ###
+    toplevel = UglifyJS.parse file, opts
+
+    # track nodes already encountered to prevent graph cycles from infinitely recursing
+    #visited = {}
+
+    # recursively parse an UglifyJS AST node
+    walker = new UglifyJS.TreeWalker (node) =>
+      if node instanceof UglifyJS.AST_Call and node.start.type is 'name' and node.start.value is 'require'
+        # has the require.config
+        if node.expression.end.value is 'config'
+          requireJS = @_parseRequireJSConfig node
+        else if node.args.length is 2
+          # has the script paths to require
+          @_parseRequireStatement node
+      if node instanceof UglifyJS.AST_Call and node.start.type is 'name' and node.start.value is 'define'
+        @_parseRequireStatement node
+      false # descend through the entire tree
+
+    toplevel.walk walker
+    #console.log toplevel.print_to_string({ beautify: true })
+    toplevel
+
+
+  # parse a RequireJS configuration block and return it as JSON
+  _parseRequireJSConfig: (node) ->
+    cfg = {}
+    for k in node.args[0].properties
+      if k.key is 'baseUrl'
+        cfg.baseUrl = k.value.value
+      if k.key is 'paths'
+        cfg.paths = {}
+        for j in k.value.properties
+          cfg.paths[j.key] = j.value.value
+    cfg
+
+
+  # parse the required scripts out of the require/define statement
+  _parseRequireStatement: (node) ->
+    if !node.args[0].elements
+      return
+
+    p = if requireJS then requireJS.baseUrl else ''
+
+    # e.g., error: could not find asset text!/templates/partials/email-confirmation-banner.html
+    for scriptpath in node.args[0].elements
+      newAssetPath = null
+      if scriptpath.value
+        # some requirejs includes are html templates. e.g., text!/templates/signup.html
+        if scriptpath.value.indexOf('text!') is 0
+          newAssetPath = join p, scriptpath.value.substring(5)
+        else if fs.existsSync join(@root, p, "#{scriptpath.value}.js")
+          newAssetPath = join(p, "#{scriptpath.value}.js")
+        else if scriptpath.value is 'exports' or (requireJS and scriptpath.value is 'require')
+          # ignore exports for scriptpath.value and references to requireJS
+        else if requireJS
+          found = false
+          # see if the script corresponds to one in the RequireJS path list
+          for alias, pa of requireJS.paths
+            if alias is scriptpath.value
+              found = true
+              newAssetPath = join p, "#{pa}.js"
+          if !found
+            console.log "error 1: could not find asset #{scriptpath.value}"
+        else
+          console.log "error: could not find asset #{scriptpath.value}"
+
+      inRequireJS = true
+      if newAssetPath then @_foundAssetReference(newAssetPath, scriptpath, inRequireJS)
+
+
+  _parseStyleSheet: (filepath, file) ->
+    c = css.parse file
+    for rule in c.stylesheet.rules
+      if rule.type is 'rule'
+        for dec in rule.declarations
+          if dec.type is 'declaration' and (dec.property is 'background-image' or dec.property is 'background')
+            url = Asset.parseStyleSheetUrl dec.value
+            if url
+              if url.indexOf('/') is 0
+                # image url is absolute path
+                fullpath = url
+              else
+                # image url is relative path to the css file including it
+                fullpath = join(path.dirname(filepath), url)
+              @_foundAssetReference fullpath, dec
+    c
+
+  _toString: ->
+    if @minify then return @_minify()
+
+    if @type is 'html'
+      #
+      # TODO remove after html templates are supported
+      if typeof(@obj) is 'string'
+        updated = @obj
+      else
+        #
+        updated = ''
+        for d in @obj
+          updated += htmlparser.DomUtils.getOuterHTML(d)
+    else if @type is 'javascript'
+      updated = @obj.print_to_string({ beautify: false })
+    else if @type is 'style'
+      updated = css.stringify(@obj, { compress: false })
+    else if @type is 'image'
+      updated = @obj
+    else
+      updated = ''
+    updated
+
+  @parseStyleSheetUrl: (declaration) ->
+    b = declaration.match /url\((.+?)\)/gi
+    if b
+      pos = declaration.indexOf 'url('
+      if pos >= 0
+        # found an image url
+        pos2 = declaration.indexOf ')', pos+4
+        url = declaration.substring(pos+4, pos2).trim()
+        return url.replace /"|'/g, '' # strip quotes from URL
+    ''
+
+  @replaceStyleSheetUrl: (declaration, newUrl) ->
+    currentUrl = Asset.parseStyleSheetUrl declaration
+    declaration.replace currentUrl, newUrl

 class AssetGraph
-	constructor: (@root) ->
-		@nodes     = {}
-		@indexNode = null
-
-	addCacheManifest: ->
-		# TODO
-
-	loadAssets: (filepath) ->
-		# TODO: detect graph cycles to prevent infinite loop
-		absPath = join @root, filepath
-		if fs.statSync(absPath).isDirectory()
-			files = fs.readdirSync absPath
-			for f in files
-				@loadAssets join(filepath, f)
-		else
-			@_load filepath
-
-	minifyAssets: ->
-		for p, a of @nodes
-			a.minify = true
-
-	moveAssets: (destination) ->
-		for p,a of @nodes
-			if p isnt 'index.html'
-				newpath = join '/', destination, '/', path.basename(p)
-				a.move newpath
-
-	setUrlPrefix: (prefix) ->
-		for p,a of @nodes
-			if p isnt 'index.html'
-				a.prefix = prefix
-
-	writeAssetsToDisc: (destination, useHashName=false) ->
-		# mark all of the assets as unwritten
-		for p, a of @nodes
-			a.written = false
-
-		for p, a of @nodes
-			a.writeToDisc destination, useHashName
-
-
-	_load: (filepath) ->
-		absPath = join @root, filepath
-		if !fs.existsSync absPath
-			console.log "file #{absPath} not found"
-			return
-
-		a = new Asset @root, filepath, this
-
-		# the first loaded asset is considered the index node
-		@indexNode or= a
-		@addAsset a
-		a.resolveDependencies()
-
-	addAsset: (asset) ->
-		# only add graph assets that are new and have type set
-		if !@nodes[asset.filepath] and asset.type?
-			@nodes[asset.filepath] = asset
-			return true
-		false
+  constructor: (@root) ->
+    @nodes     = {}
+    @indexNode = null
+
+  addCacheManifest: ->
+    # TODO
+
+  loadAssets: (filepath) ->
+    # TODO: detect graph cycles to prevent infinite loop
+    absPath = join @root, filepath
+    if fs.statSync(absPath).isDirectory()
+      files = fs.readdirSync absPath
+      for f in files
+        @loadAssets join(filepath, f)
+    else
+      @_load filepath
+
+  minifyAssets: ->
+    for p, a of @nodes
+      a.minify = true
+
+  moveAssets: (destination) ->
+    for p,a of @nodes
+      if p isnt 'index.html'
+        newpath = join '/', destination, '/', path.basename(p)
+        a.move newpath
+
+  setUrlPrefix: (prefix) ->
+    for p,a of @nodes
+      if p isnt 'index.html'
+        a.prefix = prefix
+
+  writeAssetsToDisc: (destination, useHashName=false) ->
+    # mark all of the assets as unwritten
+    for p, a of @nodes
+      a.written = false
+
+    for p, a of @nodes
+      a.writeToDisc destination, useHashName
+
+
+  _load: (filepath) ->
+    absPath = join @root, filepath
+    if !fs.existsSync absPath
+      console.log "file #{absPath} not found"
+      return
+
+    a = new Asset @root, filepath, this
+
+    # the first loaded asset is considered the index node
+    @indexNode or= a
+    @addAsset a
+    a.resolveDependencies()
+
+  addAsset: (asset) ->
+    # only add graph assets that are new and have type set
+    if !@nodes[asset.filepath] and asset.type?
+      @nodes[asset.filepath] = asset
+      return true
+    false

 module.exports.AssetGraph = AssetGraph


Add github-hook-manager to list of scripts

--- index.coffee
+++ index.coffee
@@ -1,7 +1,10 @@
 fs = require 'fs'
 path = require 'path'

-scripts = ['github-event-announcer.coffee']
+scripts = [
+  'github-event-announcer.coffee'
+  'github-hook-manager.coffee'
+]

 module.exports = (robot) ->
   scriptsPath = path.resolve(__dirname, 'src')


fix overlook to update spec.

fix overlook to update spec.

--- spec/project-folder-spec.coffee
+++ spec/project-folder-spec.coffee
@@ -351,6 +351,10 @@

       beforeEach ->
         setConfig('gitProjectDirectories', [gitRoot])
+
+        # By changing showGroupOnRemoveListCondition from default 'never' to
+        # 'some-member-was-loaded', we can test removal of group.
+        setConfig('showGroupOnRemoveListCondition', 'some-member-was-loaded')
         loadUserConfig()

       it "add/remove set of project defined in groups", ->


worksheet -- remove section; fix a mistake

--- salvus/page/worksheet.coffee
+++ salvus/page/worksheet.coffee
@@ -84,8 +84,11 @@

                     section.find(".salvus-worksheet-section-title-user").blur () ->
                         t = $(@)
-                        if $.trim(t.text()) == "" and t.find(".salvus-cell").length == 0
-                            section.remove()
+                        if $.trim(t.text()) == ""
+                            if section.find(".salvus-cell").length == 0
+                                section.remove()
+                            else
+                                t.text("...")

                     section.insertBefore(group[0].element)
                     section_cells = section.find(".salvus-worksheet-section-cells")


added scripts to mini-rt.tar path

added scripts to mini-rt.tar path

--- web/doppio-jvm/scripts/demo/doppio-compile.coffee
+++ web/doppio-jvm/scripts/demo/doppio-compile.coffee
@@ -26,7 +26,7 @@

 load_mini_rt = ->
     try
-        data = node.fs.readFileSync("/home/doppio/mini-rt.tar")
+        data = node.fs.readFileSync("/home/doppio/scripts/demo/mini-rt.tar")
     catch e
         console.error e
     if ! data?
@@ -51,8 +51,8 @@
     console.log cname
     contents = editor.getSession().getValue()
     root.saveFile fname, contents
-    msg = '' ;
-    stdout = (str) ->
+    msg = '' ;
+    stdout = (str) ->
         msg += str
         console.log str
         $('#messages').text msg
@@ -70,7 +70,7 @@
     JavaMode = require("ace/mode/java").Mode
     editor.getSession().setMode(new JavaMode)
     editor.getSession().setValue ("public class Student {\n  public static void main(String[]args) {\n    System.out.println(\"Args=\"+args[0]);\n  }\n}")
-
+
 root.preload = ->
     load_mini_rt()
     init_editor()
@@ -80,14 +80,14 @@

 $(document).ready ->
     root.preload()
-
-
+
+
 root.saveFile = (fname,contents) ->
     contents += '\n' unless contents[contents.length-1] == '\n'
     node.fs.writeFileSync(fname, contents)
     console.log("File saved as '#{fname}'.")

-root.compile = (stdout, fname,finish_cb) ->
+root.compile = (stdout, fname,finish_cb) ->
     $('#messages').text "Compiling #{fname} ..."
     start_compile=(new Date).getTime()
     jvm.set_classpath '/home/doppio/vendor/classes/', './:/home/doppio'
@@ -100,7 +100,7 @@
         console.log "javac took a total of #{end_compile-start_compile}ms."
         $('#messages').text 'Compilation complete'
         finish_cb()
-    jvm.run_class rs, 'classes/util/Javac', args, my_cb
+    jvm.run_class rs, 'classes/util/Javac', args, my_cb
     return

 root.exec = (stdout,stdin, class_name,class_args, finish_cb) ->


Cached AbstractHttp::sessionStore

Cached AbstractHttp::sessionStore

--- server/packages/express/index.coffee
+++ server/packages/express/index.coffee
@@ -29,6 +29,15 @@

 		# } Register middleware.
 		@registerMiddleware()
+
+		# } Cache the session store.
+		@_sessionStore = switch @_config.sessions.db
+			when 'redis'
+
+				module = require 'connect-redis/node_modules/redis'
+
+				RedisStore = require('connect-redis') express
+				new RedisStore client: module.createClient()

 		# } Handlebars!
 		# } `TODO`: Do we really need to use Express's theme system..?
@@ -138,18 +147,8 @@
 	# ### ::sessionStore
 	#
 	# *The session store.*
-	#
-	# `TODO`: Cache this!
-	sessionStore: ->
+	sessionStore: -> @_sessionStore

-		switch @_config.sessions.db
-			when 'redis'
-
-				module = require 'connect-redis/node_modules/redis'
-
-				RedisStore = require('connect-redis') express
-				new RedisStore client: module.createClient()
-
 # ## Implements hook `initialize`
 exports.$initialize = (config) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -20,7 +20,7 @@
     frequency: 20

   equals player.I.flickerAlpha, 0.5
-  equals player.I.flickerFrequency, 20
+  equals player.I.flickerFrequency, 0.1
   equals player.I.flickerDuration, 2

   enemy = GameObject()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -23,7 +23,9 @@
   equals player.I.flickerFrequency, 20
   equals player.I.flickerDuration, 30

-
+  enemy = GameObject()
+
+

 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject


Optionally render original (wrong) spreads with `?render-wrong-spread=1`

--- app/controllers/fan_tool.coffee
+++ app/controllers/fan_tool.coffee
@@ -32,6 +32,7 @@
       spread: new Kinetic.Line $.extend {points: [{x: 0, y: 0}]}, style.dash
       bounding: new Kinetic.Path $.extend {name: 'bounding'}, style.line
       hiddenBound: new Kinetic.Path $.extend {name: 'hiddenBound'}, style.hiddenLine
+      wrongAngle: new Kinetic.Path $.extend({name: 'bounding'}, style.line, {stroke: 'red'})

     @group = new Kinetic.Group

@@ -130,7 +131,32 @@
       left: "#{(@mark.source.x + distancePosition.x) / 2}px"
       top: "#{(@mark.source.y + distancePosition.y) / 2}px"

+    if location.search.indexOf('render-wrong-spread=1') isnt -1
+      rightSpread = Math.atan(spreadRadius / spreadX) * 2 * (180 / PI)
+      wrongSpread = @renderWrongSpread()
+
+      console?.log "#{rightSpread.toFixed 4} means #{wrongSpread.toFixed 4} #{(wrongSpread / rightSpread).toFixed 4}"
+
     super
+
+  renderWrongSpread: ->
+    adjacent = @mark.distance
+    hypotenuse = adjacent / cos @mark.spread / (180 / PI)
+    opposite = hypotenuse * sin @mark.spread / (180 / PI)
+
+    inradius = (adjacent * opposite) / (adjacent + opposite + hypotenuse)
+    toSpreadHandle = inradius * 2
+    spreadHandleX = @mark.distance - toSpreadHandle
+
+    @lines.wrongAngle.setData """
+      M 0 0
+      L #{spreadHandleX} #{-toSpreadHandle}
+      A 1 1 0 1 1 #{spreadHandleX} #{+toSpreadHandle}
+      L 0 0
+      Z
+    """
+
+    Math.atan(toSpreadHandle / spreadHandleX) * 2 * (180 / PI)

   select: ->
     dot.show() for _, dot of @dots


less verbosity on whitespace regexp

less verbosity on whitespace regexp

--- src/models/torrents.coffee
+++ src/models/torrents.coffee
@@ -26,9 +26,7 @@
 }

 Torrent.method 'generatePermalink', (callback) ->
-<<<<<<< HEAD
-  re = new RegExp(' ', 'g')
-  baseurl = @title.substring(0, 75).replace(re, '_')
+  baseurl = @title.substring(0, 75).replace(`/ /g`, '_')
   # check for collisions
   checkFunc = (base, endno) =>
     if endno > 0


Add `--warn` to Atom Elmjutsu make path

Add `--warn` to Atom Elmjutsu make path

--- atom/config.cson
+++ atom/config.cson
@@ -52,7 +52,7 @@
     autocompleteFuzzyFilteringEnabled: true
     autocompleteSnippetsEnabled: true
     cacheDirectory: "/var/folders/zl/k42vwnzn4k304yvfxwztjsl00000gn/T/elmjutsu18500B070yRkXpcaW"
-    elmMakeExecPath: "node_modules/.bin/elm-make"
+    elmMakeExecPath: "node_modules/.bin/elm-make --warn"
     elmPackageExecPath: "node_modules/.bin/elm-package"
     elmReplExecPath: "node_modules/.bin/elm-repl"
     globalAutocompleteEnabled: true


Remove

Remove

Former-commit-id: f775734d5bfffa2b8e687b3e54b9192718f5f505 [formerly 319cd0310115ff81889cfca31656238a1532995a]
Former-commit-id: 5aabe34e2ae0822198e00f165dfe4281fc2186b5

--- atom/config.cson
+++ atom/config.cson
@@ -4,10 +4,6 @@
   core:
     autoHideMenuBar: true
     openEmptyEditorOnStart: false
-    themes: [
-      "outlander-ui"
-      "atom-material-syntax-dark"
-    ]
   editor:
     fontFamily: "DejaVu Sans Mono"
     fontSize: 16


use vcs coloring for tabs on atom

use vcs coloring for tabs on atom

--- atom/config.cson
+++ atom/config.cson
@@ -30,3 +30,6 @@
     _analyticsUserId: "a6a0720b-ef9b-4635-a6bf-67af0c531216"
   "go-plus":
     goPath: "/home/edgard/Documents/code/go"
+  tabs:
+    enableVcsColoring: true
+  "status-bar": {}


Disable the metrics package

Disable the metrics package

--- atom/config.cson
+++ atom/config.cson
@@ -1,5 +1,8 @@
 "*":
   core:
+    disabledPackages: [
+      "metrics"
+    ]
     themes: [
       "atom-dark-ui"
       "solarized-light-syntax"


Added Dockblockr to Atom

Added Dockblockr to Atom

--- atom/config.cson
+++ atom/config.cson
@@ -7,6 +7,8 @@
     ]
   "exception-reporting":
     userId: "dd51b682-a7e5-2b3b-c172-243b2cf41df7"
+  "linter-eslint":
+    useGlobalEslint: true
 ".shell.source":
   editor:
     scrollPastEnd: true


Tweak atom init script

Tweak atom init script

Wed Nov 18 17:34:25 PST 2015

--- atom/init.coffee
+++ atom/init.coffee
@@ -13,7 +13,8 @@
   console.log(pane)

 fixTreeView = ->
-  try document.querySelector(".tree-view-resizer").style.display = null
+  treeViewResizer = document.querySelector(".tree-view-resizer")
+  if treeViewResizer? then treeViewResizer.style.display = null
   setTimeout(fixTreeView, 600000)

 setTimeout(fixTreeView, 600)


fix atom keymap

fix atom keymap

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -30,3 +30,6 @@
 # If you are unfamiliar with CSON, you can read more about it in the
 # Atom Flight Manual:
 # https://atom.io/docs/latest/using-atom-basic-customization#cson
+'atom-workspace atom-text-editor:not([mini])':
+  'shift-alt-up': 'editor:add-selection-above'
+  'shift-alt-down': 'editor:add-selection-below'


Added github panes as a scope to the keybindings.

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -7,7 +7,8 @@
 'atom-text-editor.vim-mode-plus':
   'f d': 'vim-mode-plus:reset-normal-mode'

-'atom-text-editor.vim-mode-plus:not(.insert-mode), atom-dock, .settings-view':
+'atom-text-editor.vim-mode-plus:not(.insert-mode), atom-dock, .settings-view,
+ .github-PaneView':
   'space space': 'command-palette:toggle'
   'space tab': 'awe:cycle-pane'
   'space /': 'project-find:toggle'


autoload

autoload

--- public/Gruntfile.coffee
+++ public/Gruntfile.coffee
@@ -25,8 +25,8 @@
       files: [ 'ts/*.ts' ]
       tasks: [ 'compile' ]

-  grunt.loadNpmTasks('grunt-bower-task');
+  for taskName of pkg.devDependencies when taskName.substring(0, 6) is 'grunt-'
+    grunt.loadNpmTasks taskName
+
   grunt.registerTask('default', ['bower:install']);
-  grunt.loadNpmTasks('grunt-typescript');
   grunt.registerTask('compile', ['typescript']);
-  grunt.loadNpmTasks('grunt-contrib-watch');


Update de-DE.coffee (#5972)

Update de-DE.coffee (#5972)

Some lines from the top landing page

Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -61,15 +61,16 @@
 #    for_leaders_subtitle4: "Real-World Skills"
 #    for_leaders_subblurb4: "Students build grit and develop a growth mindset through coding challenges that prepare them for the 500K+ open computing jobs."
     for_teachers_title: "Für Lehrer"
-#    for_teachers_blurb: "Tools to Unlock Student Potential"
+    for_teachers_blurb: "Wie Sie das Potential Ihrer Schüler freisetzen"
     for_teachers_subtitle1: "Projektbasiertes Lernen"
-#    for_teachers_subblurb1: "Promote creativity, problem-solving, and confidence in project-based courses where students develop their own games and webpages."
-#    for_teachers_subtitle2: "Teacher Dashboard"
-#    for_teachers_subblurb2: "View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
-#    for_teachers_subtitle3: "Built-in Assessments"
-#    for_teachers_subblurb3: "Personalize instruction and ensure students understand core concepts with formative and summative assessments."
-#    for_teachers_subtitle4: "Automatic Differentiation"
-#    for_teachers_subblurb4: "Engage all learners in a diverse classroom with practice levels that adapt to each student's learning needs."
+    for_teachers_subblurb1: "Fördern Sie Kreativität, Problemlösungskompetenzen und Selbstvertrauen in Projektkursen, in denen Schüler ihre eigenen Spiele und Webseiten entwickeln."
+    for_teachers_subtitle2: "Lehrer Dashboard"
+    for_teachers_subblurb2: "Sehen Sie, welche Fortschritte Ihre Schülerinnen und Schüler machen, entdecken Sie Unterichtsmaterialien und greifen Sie auf direkte Unterstützung zu, um Ihren Schülerinnen und Schüler erfolgreiches Lernen zu ermöglichen."
+#View data on student progress, discover curriculum resources, and access real-time support to empower student learning."
+    for_teachers_subtitle3: "Vorgefertigte Prüfungen"
+    for_teachers_subblurb3: "Passen Sie die Aufgabenstellungen individuell an und stellen Sie mit formativer und summativer Beurteilung sicher, dass die Schülerinnen und Schüler die Grundkonzepte wirklich verstehen."
+    for_teachers_subtitle4: "Individuelle Differenzierung"
+    for_teachers_subblurb4: "Integrieren Sie Schülerinnen und Schüler in heterogenen Klassen mit Übungen, die sich an die individuellen Bedürfnisse der Lernenden anpassen."
 #    game_based_blurb: "CodeCombat is a game-based computer science program where students type real code and see their characters react in real time."
 #    get_started: "Get started"
 #    global_title: "Join Our Global Community of Learners and Educators"
@@ -91,8 +92,8 @@
 #    teachers_love_codecombat_blurb2: "Would recommend CodeCombat to other computer science teachers"
 #    teachers_love_codecombat_blurb3: "Say that CodeCombat helps them support students’ problem solving abilities"
 #    teachers_love_codecombat_subblurb: "In partnership with McREL International, a leader in research-based guidance and evaluations of educational technology."
-#    top_banner_blurb: "Parents, give your child the gift of coding and personalized instruction with our live teachers!"
-#    try_the_game: "Try the game"
+    top_banner_blurb: "Eltern können hier die ersten Online-Kurse kostenfrei buchen."
+    try_the_game: "Das Spiel ausprobieren"
     classroom_edition: "Klassenraum Edition:"
     learn_to_code: "Lerne zu Programmieren:"
     play_now: "Jetzt spielen"
@@ -156,16 +157,16 @@
     view_profile: "Zeige mein Profil"
     view_progress: "Fortschritt ansehen"
     go_to_courses: "Zu meinen Kursen"
-    want_coco: "Wollen Sie CodeCombat an Ihrer Schule?"
-#    educator: "Educator"
-#    student: "Student"
-#    our_coding_programs: "Our Coding Programs"
-#    codecombat: "CodeCombat"
-#    ozaria: "Ozaria"
-#    codecombat_blurb: "Our original coding game. Recommended for parents, individuals, educators, and students who want to experience one of the most-loved coding games in the world."
-#    ozaria_blurb: "An adventure game and Computer Science program where students master the lost magic of coding to save their world. Recommended for educators and students."
-#    try_codecombat: "Try CodeCombat"
-#    try_ozaria: "Try Ozaria"
+    want_coco: "Wollen Sie CodeCombat an Ihrer Schule einsetzen?"
+    educator: "Lehrkraft"
+    student: "Schüler"
+    our_coding_programs: "Unsere Programme zum Programmieren lernen"
+    codecombat: "CodeCombat"
+    ozaria: "Ozaria"
+    codecombat_blurb: "Das Original. Ein Spiel zum Programmieren lernen. Empfehlenswert für Eltern, Individuen, Lehrkräfte und Schüler, die in eines der weltweit beliebtesten Spiele zum Programmieren eintauchen möchten."
+    ozaria_blurb: "Ein Adventure und Informatik-Lernprogramm, in dem Schüler die verlorene Magie des Programmierens meistern um ihre Welt zu retten. Empfohlen für Lehrkräfte und Schüler."
+    try_codecombat: "CodeCombat testen"
+    try_ozaria: "Ozaria testen"

   nav:
 #    educators: "Educators"
@@ -185,12 +186,12 @@
     legal: "Rechtliches"
     privacy: "Datenschutz"
     about: "Über"
-#    impact: "Impact"
+    impact: "Referenzen" #impact - oder Leuchtturm-Projekte
     contact: "Kontakt"
     twitter_follow: "Folge uns auf Twitter"
     my_classrooms: "Meine Klassen"
     my_courses: "Meine Kurse"
-#    my_teachers: "My Teachers"
+    my_teachers: "Meine Lehrer"
     careers: "Karriere mit CodeCombat!"
     facebook: "Facebook"
     twitter: "Twitter"
@@ -211,7 +212,7 @@
     apcsp: "AP CS Prinzipien"
     parent: "Eltern"
 #    esports: "Esports"
-#    browser_recommendation: "For the best experience we recommend using the latest version of Chrome. Download the browser here!"
+    browser_recommendation: "Für die beste Funktionalität empfehlen wir als Browser die aktuelle Version von Chrome. Hier herunterladen!"

   modal:
     close: "Schließen"


Update nl-NL.coffee

Update nl-NL.coffee

Reduced some translations so they fit better in the menu.

--- app/locale/nl-NL.coffee
+++ app/locale/nl-NL.coffee
@@ -469,8 +469,8 @@

   subscribe:
     comparison_blurb: "Verbeter je vaardigheden met een abonement op CodeCombat!"
-    feature1: "meer dan 110+ basislevels over 4 werelden"
-    feature2: "10 sterke nieuwe helden met unieke vaardigheden!"
+    feature1: "110+ basislevels over 4 werelden"
+    feature2: "10 nieuwe helden met unieke vaardigheden!"
     feature3: "70+ bonuslevels" # {change}
     feature4: "{{gems}} bonus edelstenen elke maand!"
     feature5: "Video cursussen"
@@ -488,7 +488,7 @@
     thank_you: "Bedankt dat je CodeCombat steunt."
     sorry_to_see_you_go: "Jammer dat je weggaat! Laat ons alsjeblieft weten wat we beter hadden kunnen doen."
     unsubscribe_feedback_placeholder: "Oh, wat hebben we gedaan?"
-    parent_button: "Vraag het aan je ouder(s)"
+    parent_button: "Vraag ouder(s)"
     parent_email_description: "We zullen ze een e-mail sturen zodat zij een CodeCombat-abonnement voor je kunnen kopen."
     parent_email_input_invalid: "E-mailadress ongeldig."
     parent_email_input_label: "E-mailadres van ouder(s)"
@@ -506,7 +506,7 @@
     payment_methods_title: "Geaccepteerde betaalmethoden"
     payment_methods_blurb1: "Momenteel worden alleen credit cards, Alipay en iDEAL geaccepteerd." # {change}
     payment_methods_blurb2: "Als u op een andere manier wilt betalen, neem dan alstublieft contact op"
-    sale_button: "Koopje!"
+    sale_button: "Koop"
     sale_button_title: "Bespaar ${{discount}} bij aankoop van een 1-jarig abonnement" # {change}
     stripe_description: "Maandelijks abonnement"
     stripe_description_year_sale: "1-jarig abonnement (${{discount}} korting)" # {change}


Update pt-BR.coffee

Update pt-BR.coffee

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -65,7 +65,7 @@
     next: "Próximo" # Go from choose hero to choose inventory before playing a level
     change_hero: "Alterar Herói" # Go back from choose inventory to choose hero
     choose_inventory: "Equipar Items"
-#    buy_gems: "Buy Gems"
+    buy_gems: "Comprar Gems"
     older_campaigns: "Campanhas antigas"
     anonymous: "Jogador Anônimo"
     level_difficulty: "Dificuldade: "
@@ -201,7 +201,7 @@
     restart: "Reiniciar"
     goals: "Objetivos"
     goal: "Objetivo"
-#    running: "Running..."
+    running: "Rodando..."
     success: "Sucesso!"
     incomplete: "Incompleto"
     timed_out: "Tempo esgotado"
@@ -210,7 +210,7 @@
     click_to_select: "Clique em um personagem para selecioná-lo."
 #    control_bar_multiplayer: "Multiplayer"
 #    control_bar_join_game: "Join Game"
-#    reload: "Reload"
+    reload: "Recarregar"
     reload_title: "Recarregar Todo o Código?"
     reload_really: "Você tem certeza que quer reiniciar o estágio?"
     reload_confirm: "Recarregar Tudo"
@@ -248,12 +248,12 @@
     tome_your_skills: "Suas habilidades"
 #    tome_current_method: "Current Method"
 #    hud_continue_short: "Continue"
-#    code_saved: "Code Saved"
+    code_saved: "Código Salvo"
     skip_tutorial: "Pular (esc)"
     keyboard_shortcuts: "Teclas de atalho"
     loading_ready: "Pronto!"
     loading_start: "Iniciar fase"
-#    problem_alert_title: "Fix Your Code"
+    problem_alert_title: "Altere seu Código"
     time_current: "Agora:"
     time_total: "Máximo:"
     time_goto: "Ir para:"
@@ -304,7 +304,7 @@
     options_caption: "Configurar preferências"
     guide_caption: "Documentos e dicas"
     multiplayer_caption: "Jogue com seus amigos!"
-#    auth_caption: "Save your progress."
+    auth_caption: "Salve seu progresso."

   inventory:
     choose_inventory: "Equipar itens"
@@ -346,10 +346,10 @@
 #    attack: "Damage" # Can also translate as "Attack"
     health: "Vida"
     speed: "Velocidade"
-#    regeneration: "Regeneration"
-#    range: "Range" # As in "attack or visual range"
+    regeneration: "Cura"
+    range: "Ataque" # As in "attack or visual range"
 #    blocks: "Blocks" # As in "this shield blocks this much damage"
-#    skills: "Skills"
+    skills: "Habilidades"

 #  skill_docs:
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this


Generate relative paths for the Navigation doc

--- scripts/gh-book/utils.coffee
+++ scripts/gh-book/utils.coffee
@@ -20,6 +20,35 @@
       parts.join '/'

+    # Given 2 paths that have the same root
+    # generate a path that is relative from `context` to `relPath`.
+    # For example: `A/B/cntx` and `A/C/D/file.txt` should yield `../C/D/file.txt`
+    relativePath: (contextPath, targetPath) ->
+      return targetPath if contextPath.search('/') < 0
+      contextParts = contextPath.split('/')
+      targetParts = targetPath.split('/')
+
+      # Pop the end of both so we only deal with directories
+      contextParts.pop()
+      targetName = targetParts.pop()
+
+      sameUntil = contextParts.length
+      for part, i in contextParts
+        if part != targetParts[i]
+          sameUntil = i
+          break
+
+      if sameUntil == contextParts.length
+        parts = []
+      else
+        parts = ('..' for i in [sameUntil..contextParts.length-1])
+
+      # We have all the '..'; now add in the rest of parts
+      parts = parts.concat targetParts.slice(sameUntil)
+      parts.push targetName
+      parts.join '/'
+
+
     elementAttributes: ($el) ->
       attrs = {}
       $.each $el[0].attributes, (index, attr) =>


github-notify: break line before URL in message lines.

--- scripts/github-notify.coffee
+++ scripts/github-notify.coffee
@@ -133,13 +133,13 @@
       else
         mentioned_line = ''

-      cb "PR が作成されました \"#{data.pull_request.title}\" by #{data.pull_request.user.login}: #{data.pull_request.html_url}#{mentioned_line}\n#{ellipsisize(data.pull_request.body, 4)}"
+      cb "PR が作成されました \"#{data.pull_request.title}\" by #{data.pull_request.user.login}\n#{data.pull_request.html_url}#{mentioned_line}\n#{ellipsisize(data.pull_request.body, 4)}"

     when "synchronize"
-      cb "PR にコミットが追加pushされました \"#{data.pull_request.title}\": #{data.pull_request.html_url}"
+      cb "PR にコミットが追加pushされました \"#{data.pull_request.title}\"\n#{data.pull_request.html_url}"

     when "closed"
-      cb "PR が close されました \"#{data.pull_request.title}\" by #{data.pull_request.merged_by?.login}: #{data.pull_request.html_url}"
+      cb "PR が close されました \"#{data.pull_request.title}\" by #{data.pull_request.merged_by?.login}\n#{data.pull_request.html_url}"

 announcePullRequestReviewComment = (robot, data, cb) ->
   switch data.action
@@ -151,7 +151,7 @@
       else
         mentioned_line = ''

-      cb "\"#{data.pull_request.title}\" コメント追加 by #{data.comment.user.login}: #{data.comment.html_url}#{mentioned_line}\n#{ellipsisize(data.comment.body, 4)}"
+      cb "\"#{data.pull_request.title}\" コメント追加 by #{data.comment.user.login}\n#{data.comment.html_url}#{mentioned_line}\n#{ellipsisize(data.comment.body, 4)}"

 announceIssue = (robot, data, cb) ->
   switch data.action
@@ -163,10 +163,10 @@
       else
         mentioned_line = ''

-      cb "Issue が作成されました \"#{data.issue.title}\" by #{data.issue.user.login}: #{data.issue.html_url}#{mentioned_line}\n#{ellipsisize(data.issue.body, 4)}"
+      cb "Issue が作成されました \"#{data.issue.title}\" by #{data.issue.user.login}\n#{data.issue.html_url}#{mentioned_line}\n#{ellipsisize(data.issue.body, 4)}"

     when "closed"
-      cb "Issue が close されました \"#{data.issue.title}\" by #{data.sender.login}: #{data.issue.html_url}"
+      cb "Issue が close されました \"#{data.issue.title}\" by #{data.sender.login}\n#{data.issue.html_url}"

 announceIssueComment = (robot, data, cb) ->
   switch data.action
@@ -176,4 +176,4 @@
         mentioned_line = "\nMentioned: #{mentioned.join(", ")}"
       else
         mentioned_line = ''
-      cb "\"#{data.issue.title}\" コメント追加 by #{data.comment.user.login}: #{data.comment.html_url}#{mentioned_line}\n#{ellipsisize(data.comment.body, 4)}"
+      cb "\"#{data.issue.title}\" コメント追加 by #{data.comment.user.login}\n#{data.comment.html_url}#{mentioned_line}\n#{ellipsisize(data.comment.body, 4)}"


Server: minor code styling

Server: minor code styling

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -14,7 +14,7 @@
   social
   broker
   recaptcha
-}       = KONFIG
+} = KONFIG

 webPort = argv.p ? webserver.port
 koding  = require './bongo'


spec_helper stuff

spec_helper stuff

--- test/spec_helper.coffee
+++ test/spec_helper.coffee
@@ -1,3 +1,9 @@
 path = require 'path'
-global.should = require("chai").should()
-global.ROOT = path.normalize(path.join(__dirname,"..")) + "/"
+chai = require("chai")
+global.should = chai.should()
+global.assert = chai.assert
+root = path.normalize(path.join(__dirname,"..")) + "/"
+
+test = root + "test/"
+global.SRC = root + "src/"
+global.FIXTURES = test + "fixtures"


'Binding::onError()' added

'Binding::onError()' added

--- index.coffee
+++ index.coffee
@@ -193,11 +193,7 @@
 	setValue: (val) ->
 		throw "Not implemented"

-	FAIL = utils.uid 16
-
-	onFail = (err) ->
-		# log.warn "Binding error '#{err}'"
-		FAIL
+	onError: (err) ->

 	update: ->
 		unless @args
@@ -214,8 +210,9 @@
 			@updateLoop = 0
 		`//</development>`

-		result = utils.tryFunction @func, @ctx, @args, onFail
-		if result is FAIL
+		result = utils.tryFunction @func, @ctx, @args
+		if result instanceof Error
+			@onError result
 			result = @getDefaultValue()

 		`//<development>`


fix rm buffer copy

fix rm buffer copy

--- index.coffee
+++ index.coffee
@@ -182,30 +182,31 @@
       hooks.buffs = data
       callback()
   wf.push (hooks, callback) ->
-    last = null
     iter = (frag, fin) ->
       par =
         uri: hooks.session.uploadUrl
         headers:
           'Content-Range': "bytes #{frag.rangeF}-#{frag.rangeT}/#{hooks.buffs.length}"
-          'Content-Length': frag.buf.length
+          'Content-Length': frag.size
         method: 'PUT'
-        body: frag.buf
+        body: hooks.buffs.slice frag.rangeF, frag.size
       logger.trace '%j', _.omit par, 'body'
       request par, (err, resp, body) ->
-        logger.error err if err
-        logger.debug body if body.error
+        return logger.error err if err
+        logger.warn body if body.error
         logger.debug body
-        fin()
-    size = +options.size * 1024 * 1024
-    list = _.chunk hooks.buffs, size
-    list = _.map list, (e, i) ->
+        fin null, body
+    size = +options.size * Math.pow(2, 20)
+    list = _.range hooks.buffs.length / size
+    list = _.map list, (i) ->
       rangeF: f = i*size
-      rangeT: Math.min f + size - 1, hooks.buffs.length - 1
-      buf: Buffer.from e
-    async.eachLimit list, 1, iter
-
-  wf.exec (err) ->
+      size: s = Math.min size, hooks.buffs.length - f
+      rangeT: f + s - 1
+    async.mapLimit list, 1, iter, (err, arr) ->
+      hooks.item = _.find arr, (e) -> e.id and e.name
+      callback()
+  wf.exec (err, item) ->
+    logger.info 'put success', hooks.item

 defaultOptions =


fix html template

fix html template

--- index.coffee
+++ index.coffee
@@ -128,7 +128,7 @@
       return jade.render template_source, view_data

     else if engine == 'html'
-      return _.template(template_source) view_data
+      return _.template(template_source.toString()) view_data

     else
       throw new Error 'Unknown Engine'


Try waiting 5 seconds

Try waiting 5 seconds

--- index.coffee
+++ index.coffee
@@ -56,16 +56,19 @@
     spawn {cmd, args}, (error, result, code) ->
       return done(error) if error?

-      nupkgPath = path.join(outputDirectory, "#{metadata.name}.#{metadata.version}.nupkg")
+      f = ->
+        nupkgPath = path.join(outputDirectory, "#{metadata.name}.#{metadata.version}.nupkg")

-      cmd = path.resolve(__dirname, '..', 'vendor', 'Update.com')
-      args = [
-        '--releasify'
-        nupkgPath
-        '--releaseDir'
-        outputDirectory
-        '--loadingGif'
-        loadingGif
-      ]
+        cmd = path.resolve(__dirname, '..', 'vendor', 'Update.com')
+        args = [
+          '--releasify'
+          nupkgPath
+          '--releaseDir'
+          outputDirectory
+          '--loadingGif'
+          loadingGif
+        ]

-      spawn {cmd, args}, (error, result, code) -> done(error)
+        spawn {cmd, args}, (error, result, code) -> done(error)
+
+      setTimeout(f, 5000)


Attach local login error message in response.

--- index.coffee
+++ index.coffee
@@ -50,13 +50,20 @@
 initApp = ->
   app.use passport.initialize()
   app.use passport.session()
-  app.post opts.loginPath, passport.authenticate('local')
+  app.post opts.loginPath, localAuth
   app.post opts.signupPath, signup, passport.authenticate('local')
   app.get opts.twitterPath, socialAuth('twitter')
   app.get opts.facebookPath, socialAuth('facebook')
   app.get opts.twitterCallbackPath, socialAuth('twitter'), socialSignup('twitter')
   app.get opts.facebookCallbackPath, socialAuth('facebook'), socialSignup('facebook')
   app.use addLocals
+
+localAuth = (req, res, next) ->
+  passport.authenticate('local', (err, user, info) ->
+    return req.login(user, next) if user
+
+    res.authError = info; next()
+  )(req, res, next)

 socialAuth = (provider) ->
   (req, res, next) ->
@@ -168,19 +175,29 @@
     # Create a user from the response, or throw any internal errors or error sent
     # from the API.
     err = (err?.toString() or res?.body.error_description or res?.body.error)
-    unless err?.match?('no account linked')
-      done(err, new opts.CurrentUser(accessToken: res?.body.access_token))
-      return
+
+    # Success
+    unless err?
+      return done(null, new opts.CurrentUser(accessToken: res?.body.access_token))

     # If there's no user linked to this account, create the user via the POST /user API.
     # Then pass on an empty user so our signup middleware can catch it, login, and move on.
-    params.xapp_token = artsyXappToken
-    request
-      .post(opts.SECURE_ARTSY_URL + '/api/v1/user')
-      .send(params)
-      .end (err, res) ->
-        err = (err or res?.body.error_description or res?.body.error)
-        done err or 'artsy-passport: created user from social'
+    if err?.match?('no account linked')
+      params.xapp_token = artsyXappToken
+      request
+        .post(opts.SECURE_ARTSY_URL + '/api/v1/user')
+        .send(params)
+        .end (err, res) ->
+          err = (err or res?.body.error_description or res?.body.error)
+          done err or 'artsy-passport: created user from social'
+
+    # Invalid email or password
+    else if err.match?('invalid email or password')
+      done null, false, err
+
+    # Other errors
+    else
+      done err

 #
 # Serialize user by fetching and caching user data in the session.
@@ -193,4 +210,4 @@
     error: (m, e) -> done e.text

 deserializeUser = (userData, done) ->
-  done null, new opts.CurrentUser(userData)+  done null, new opts.CurrentUser(userData)


handle the other kind of error

handle the other kind of error

--- index.coffee
+++ index.coffee
@@ -188,7 +188,8 @@
       oauth_token: accessToken
       access_token: req.user.get 'accessToken'
     ).end (res) ->
-      done (if res.error then res.body.error + ': Facebook' else ''), req.user
+      err = res.body.error or res.body.message + ': Facebook' if res.error
+      done err, req.user
   else
     request.get("#{opts.SECURE_ARTSY_URL}/oauth2/access_token").query(
       client_id: opts.ARTSY_ID
@@ -209,7 +210,8 @@
       oauth_token_secret: tokenSecret
       access_token: req.user.get 'accessToken'
     ).end (res) ->
-      done (if res.error then res.body.error + ': Twitter' else ''), req.user
+      err = res.body.error or res.body.message + ': Twitter' if res.error
+      done err, req.user
   else
     request.get("#{opts.SECURE_ARTSY_URL}/oauth2/access_token").query(
       client_id: opts.ARTSY_ID


Removed jQuery for Atom 1.16.0

Removed jQuery for Atom 1.16.0

--- index.coffee
+++ index.coffee
@@ -135,9 +135,13 @@
     @disposables.add atom.commands.add "atom-workspace", "term3:pipe-path", @pipeTerm.bind(this, 'path')
     @disposables.add atom.commands.add "atom-workspace", "term3:pipe-selection", @pipeTerm.bind(this, 'selection')

-    atom.packages.activatePackage('tree-view').then (treeViewPkg) =>
+    @disposables.add atom.packages.onDidActivatePackage((pkg) ->
+      return unless pkg.name == 'tree-view'
       node = new ListView()
-      treeViewPkg.mainModule.treeView.find(".tree-view-scroller").prepend node
+      treeView = pkg.mainModule.treeView.element
+      el = treeView.querySelector('.tree-view-scroller')
+      el.insertBefore(node, el.firstChild)
+    )

   service_0_1_3: () ->
     {


only gzip specific file types

only gzip specific file types

--- index.coffee
+++ index.coffee
@@ -8,7 +8,8 @@
     files = []

     fileHandler = (root, stat, next) ->
-      files.push "#{root}/#{stat.name}"
+      if stat.name.match /\.(ico|html|js|css|json|xml|ttf|eot|ott|woff|svg)$/
+        files.push "#{root}/#{stat.name}"
       next()

     endHandler = ->


changed client_id

changed client_id

--- index.coffee
+++ index.coffee
@@ -4,7 +4,7 @@
 Podcast = require 'podcast'
 request = require 'request'

-client_id = '02gUJC0hH2ct1EGOcYXQIzRFU91c72Ea'
+client_id = 'fDoItMDbsbZz8dY16ZzARCZmzgHBPotA'
 app = express()
 server = null

@@ -27,6 +27,7 @@
       qs: req.query
       json: yes
     , (err, res1, songs) ->
+      console.log arguments
       return res.send err if err?
       if songs.errors?
         res.set 'Content-Type', 'text/plain'


Update to use new upnp-device API

Update to use new upnp-device API

--- index.coffee
+++ index.coffee
@@ -1,12 +1,22 @@
-upnp = require 'upnp'
-config =
-    app:
-        name: 'Bragi'
-        version: '0.0.1'
-        url: 'http://'
-    device:
-        type: 'MediaServer'
-        version: '1.0'
+fs = require 'fs'
+upnp = require 'upnp-server'
+Tag = require('taglib').Tag

-upnp.start config, ->
-    console.log 'Bragi running! :-)'
+mediaServer = upnp.createDevice 'MediaServer', 'Bragi', (err, device, msg) ->
+    throw err if err
+    device.start (err) ->
+        throw err if err
+        fs.readdir dir, (err, files) ->
+            for file in files
+                try
+                    t = new Tag(dir + file)
+                catch err
+                    console.log err
+                finally
+                    media.children.push(
+                        title: t.title
+                        location: dir + file
+                    )
+
+            device.addMedia 0, media, (err, id) ->
+                console.log id


:racehorse: Use require to read/parse package.json

This file is really large and should be read via require so it is cached

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -276,9 +276,9 @@
   getPackageDependencies: ->
     unless @packageDependencies?
       try
-        metadataPath = path.join(@resourcePath, 'package.json')
-        {@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
-      @packageDependencies ?= {}
+        @packageDependencies = require('../package.json')?.packageDependencies ? {}
+      catch error
+        @packageDependencies = {}

     @packageDependencies


Freq anaylsis

Freq anaylsis

--- src/messages/index.coffee
+++ src/messages/index.coffee
@@ -12,9 +12,7 @@
 dir = secret.messages.dir
 files = fs.readdirSync dir

-files = files.slice 10, 16
-
-messages = []
+# files = files.slice 10, 16

 readReqs = _.reduce files, ((memo, file) ->
     memo[file] = (cb) ->
@@ -28,6 +26,20 @@
     memo)
 , {}

+messages = {}
+
+freqAnalysis = (data) ->
+    _.each data, (ms, i) ->
+        _.merge messages, ms.getTimestamps()
+    freq = _.chain(messages)
+        .map((timestamps, name) ->
+            last = moment.unix(_.last(timestamps)).fromNow()
+            [name, timestamps.length, last])
+        .sortBy('1')
+        .reverse()
+        .value()
+    console.log freq
+
 console.time 'read'
 async.parallel readReqs, (err, data) ->
     console.timeEnd 'read'
@@ -35,6 +47,5 @@
     if err
         console.log err
     else
-        _.each data, (ms, i) ->
-            # console.log ms.get 1366416725
-            console.log ms.getTimestamps()
+        freqAnalysis(data)
+


fix(delta): add N1 token to delta stream connection

--- internal_packages/worker-sync/lib/nylas-long-connection.coffee
+++ internal_packages/worker-sync/lib/nylas-long-connection.coffee
@@ -1,5 +1,5 @@
 {Emitter} = require 'event-kit'
-{DatabaseStore} = require 'nylas-exports'
+{IdentityStore, DatabaseStore} = require 'nylas-exports'
 url = require 'url'
 _ = require 'underscore'

@@ -98,7 +98,7 @@
       return if @status is NylasLongConnection.Status.Ended

       options = url.parse("#{@_api.APIRoot}/delta/streaming?cursor=#{cursor}&exclude_folders=false&exclude_metadata=false&exclude_account=false")
-      options.auth = "#{token}:"
+      options.auth = "#{token}:#{IdentityStore.identity().token}"

       if @_api.APIRoot.indexOf('https') is -1
         lib = require 'http'


Promisified init script.

Promisified init script.

This commit also removes the obsolete `--fix-attributes` option.

--- init.coffee
+++ init.coffee
@@ -16,19 +16,21 @@

 'use strict'

-config = require './config'
-lib = require './lib.coffee'
-sync = require 'sync'
+{async, await} = require 'asyncawait'
+Promise = require 'bluebird'
+fs = require 'fs'
 dashdash = require 'dashdash'
 chalk = require 'chalk'
 sugar = require 'sugar'
-fs = require 'fs'
+lib = require './lib.coffee'
+config = require './config'

 anyDB = null
 createAnyDBConnection = (url) ->
-	anyDB = require 'any-db' unless anyDB?
-	anyDB.createConnection(url)
+	unless anyDB?
+		anyDB = require 'any-db'
+	return Promise.promisifyAll(anyDB.createConnection(url))

 createQueryUtils = (url) ->
 	lib.query_utils.getFor createAnyDBConnection url
@@ -84,7 +86,7 @@
 	help: 'Migrate to the latest revision.'
 ,
 	names: [
-		'optimize-tables'
+		'optimize'
 		'o'
 	]
 	type: 'bool'
@@ -159,13 +161,13 @@
 #  'drop', 'd', 'Drop all tables and set revision to -1'
 # [--database] [--tables] [--unify-validate] [--unify-export] [--optimize] [--test-monsters] [--drop]

-help = ->
+help = async ->
 	console.log "\nUsage: coffee init.coffee <commands>\n\n#{parser.help(includeEnv: true).trimRight()}"

-info = ->
-	dbConnection = createAnyDBConnection(config.DATABASE_URL)
-	current = lib.migration.getCurrentRevision.sync(null, dbConnection)
+info = async ->
+	dbConnection = createAnyDBConnection(config.DATABASE_URL)
+	current = await lib.migration.getCurrentRevision(dbConnection)
 	newest = lib.migration.getNewestRevision()

 	status = 'up to date'
@@ -181,18 +183,18 @@
 	console.log "Latest revision is #{chalk.magenta(newest)}, current is #{color(current)} (#{color(status)})."

-createDatabase = (arg) ->
+createDatabase = async (arg) ->
 	checkArgs arg, ['main', 'test', 'both']

 	console.log chalk.magenta 'Creating databases...'

-	create = (db_url) ->
+	create = async (db_url) ->
 		[_, db_path, db_name] = db_url.match(/(.+)\/(.+)/)
 		process.stdout.write " `#{db_name}`... "
 		db_path += '/postgres'  # PostgreSQL requires database to be specified.
 		conn = createAnyDBConnection(db_path)
 		try
-			conn.query.sync(conn, "CREATE DATABASE #{db_name}", [])
+			await conn.queryAsync("CREATE DATABASE #{db_name}", [])
 			console.log chalk.green 'ok'
 		catch error
 			if error.code is 'ER_DB_CREATE_EXISTS' or error.code is '42P04'  # MySQL, PostgreSQL
@@ -200,22 +202,22 @@
 			else
 				throw error

-	create config.DATABASE_URL if arg in ['main', 'both']
-	create config.DATABASE_URL_TEST if arg in ['test', 'both']
-
-
-dropDatabase = (arg) ->
+	await create config.DATABASE_URL if arg in ['main', 'both']
+	await create config.DATABASE_URL_TEST if arg in ['test', 'both']
+
+
+dropDatabase = async (arg) ->
 	checkArgs opts.drop_database, ['main', 'test', 'both']

 	console.log chalk.magenta 'Dropping databases...'

-	drop = (db_url, callback) ->
+	drop = async (db_url, callback) ->
 		[_, db_path, db_name] = db_url.match(/(.+)\/(.+)/)
 		process.stdout.write " `#{db_name}`... "
 		db_path += '/postgres'  # PostgreSQL requires database to be specified.
 		conn = createAnyDBConnection(db_path)
 		try
-			conn.query.sync(conn, "DROP DATABASE #{db_name}", [])
+			await conn.queryAsync("DROP DATABASE #{db_name}", [])
 			console.log chalk.green 'ok'
 		catch error
 			if error.code is 'ER_DB_DROP_EXISTS' or error.code is '3D000'  # MySQL, PostgreSQL
@@ -223,24 +225,23 @@
 			else
 				throw error

-	drop config.DATABASE_URL if arg in ['main', 'both']
-	drop config.DATABASE_URL_TEST if arg in ['test', 'both']
-
-
-migrateTables = ->
+	await drop config.DATABASE_URL if arg in ['main', 'both']
+	await drop config.DATABASE_URL_TEST if arg in ['test', 'both']
+
+
+migrateTables = async ->
 	console.log chalk.magenta 'Performing migrations...'
 	dbConnection = createAnyDBConnection(config.DATABASE_URL)
-	lib.migration.migrate.sync null, dbConnection, {verbose: true}
-
-
-optimize = ->
+	await lib.migration.migrate dbConnection, {verbose: true}
+
+
+optimize = async ->
 	console.log chalk.magenta 'Optimizing tables...'

 	conn = createAnyDBConnection(config.DATABASE_URL)
 	db_name = config.DATABASE_URL.match(/[^\/]+$/)[0]

-	result = conn.query.sync conn,
-		"SELECT table_name "+
+	result = await conn.queryAsync "SELECT table_name "+
 		"FROM information_schema.tables "+
 		"WHERE table_schema = 'public'"  # move to subquery, maybe?

@@ -248,15 +249,15 @@
 		#lib.prettyprint.action "Optimizing table `#{row.table_name}`"
 		process.stdout.write " `#{row.table_name}`... "
 		try
-			optRes = conn.query.sync conn, "VACUUM FULL ANALYZE #{row.table_name}"
+			optRes = await conn.queryAsync "VACUUM FULL ANALYZE #{row.table_name}"
 			console.log chalk.green "ok"
 		catch ex
 			console.log chalk.red.bold "fail"
 			console.trace ex

-unifyValidate = ->
-	console.log chalk.magenta 'Validating unify...'
+unifyValidate = async ->
+	console.log chalk.magenta 'Parsing unify...'
 	dbConnection = createAnyDBConnection(config.DATABASE_URL)
 	locparse = require './lib/locparse'
 	result = locparse.processDir('./unify/Кронт - kront', true)
@@ -265,15 +266,22 @@
 	#require('fs').writeFileSync('./unify.json', JSON.stringify(result))

-unifyExport = ->
-	console.log chalk.magenta 'Inserting unify data...'
+unifyExport = async ->
+	console.log chalk.magenta 'Parsing unify...'
 	dbConnection = createAnyDBConnection(config.DATABASE_URL)
 	locparse = require './lib/locparse'
 	result = locparse.processDir('./unify/Кронт - kront', true)
-	result.save(dbConnection)
-
-
-insertMonsters = ->
+	try
+		process.stdout.write chalk.magenta '  Saving parsed data... '
+		await result.save(dbConnection)
+		console.log chalk.green 'ok'
+	catch ex
+		console.log chalk.red.bold "fail"
+		console.trace ex
+
+
+
+insertMonsters = async ->
 	console.log chalk.magenta 'Inserting monsters...'
 	dbConnection = createAnyDBConnection(config.DATABASE_URL)

@@ -300,17 +308,16 @@

 	console.log chalk.green 'ok'

-	locs = dbConnection.query.sync(dbConnection, "SELECT id FROM locations").rows
+	locs = (await dbConnection.queryAsync("SELECT id FROM locations")).rows
 	if (locs.length == 0)
 		throw new Error("No locations found. Forgot unify data?")

 	process.stdout.write '  '+'Inserting monsters'+'... '

-	dbConnection.query.sync(dbConnection, "DELETE FROM characters WHERE player IS NULL", [])
+	await dbConnection.queryAsync("DELETE FROM characters WHERE player IS NULL", [])
 	for i in prototypes
 		for j in [1..5]
-			dbConnection.query.sync(
-				dbConnection
+			await dbConnection.queryAsync(
 				"INSERT INTO characters "+
 					"(name, level, power, agility, defense, intelligence, accuracy, "+
 					"health_max, mana_max, energy, "+
@@ -326,7 +333,7 @@
 	console.log chalk.green 'ok'

-insertItems = ->
+insertItems = async ->
 	console.log chalk.magenta 'Inserting test items'+'... '

 	query = createQueryUtils(config.DATABASE_URL)
@@ -341,88 +348,49 @@
 	process.stdout.write '  '+'Inserting item prototypes'+'... '
 	for proto in prototypes
 		attrs = 'id name type class kind armor_class strength_max coverage damage'.split(' ')
-		query(
+		await query(
 			"INSERT INTO items_proto (#{attrs}) "+
 			"VALUES (#{attrs.map (_,i) -> '$'+(i+1)})", attrs.map (a) -> proto[a])
 	console.log chalk.green 'ok'

 	process.stdout.write '  '+'Fetching characters'+'... '
-	characters = query.all(
+	characters = await query.all(
 		'SELECT username, characters.id AS character_id, characters.name AS character_name '+
 		'FROM uniusers, characters WHERE uniusers.id = characters.player')
 	console.log chalk.green "found #{characters.length}"
 	for char in characters
 		process.stdout.write '  '+"Giving some items to #{char.character_name}"+'... '
 		for item in prototypes
-			query 'INSERT INTO items (prototype, owner, strength, equipped) '+
+			await query 'INSERT INTO items (prototype, owner, strength, equipped) '+
 				'VALUES ($1, $2, $3, false)', [item.id, char.character_id, item.strength_max]
 		console.log chalk.green 'ok'

-fixAttrs = ->
-	# ['uniusers', 'changeDefault', 'health',       1000],
-	# ['uniusers', 'changeDefault', 'health_max',   1000],
-	# ['uniusers', 'changeDefault', 'mana',         500],
-	# ['uniusers', 'changeDefault', 'mana_max',     500],
-	# ['uniusers', 'changeDefault', 'energy',       100],
-	# ['uniusers', 'changeDefault', 'power',        50],
-	# ['uniusers', 'changeDefault', 'defense',      50],
-	# ['uniusers', 'changeDefault', 'agility',      50],
-	# ['uniusers', 'changeDefault', 'accuracy',     50],
-	# ['uniusers', 'changeDefault', 'intelligence', 50],
-	# ['uniusers', 'changeDefault', 'initiative',   50],
-	process.stdout.write chalk.magenta 'Setting predefined attributes'+'... '
-	dbConnection = createAnyDBConnection(config.DATABASE_URL)
-	dbConnection.query.sync(
-		dbConnection
-		'UPDATE uniusers SET '+
-			'health = 1000, '+
-			'health_max = 1000, '+
-			'mana = 500, '+
-			'mana_max = 500, '+
-			'energy = 100, '+
-			'power = 50, '+
-			'defense = 50, '+
-			'agility = 50, '+
-			'accuracy = 50, '+
-			'intelligence = 50, '+
-			'initiative = 50'
-	)
-	console.log chalk.green 'ok'
-
-
-fixEnergy = ->
+fixEnergy = async ->
 	process.stdout.write chalk.magenta 'Setting predefined energy'+'... '
 	dbConnection = createAnyDBConnection(config.DATABASE_URL)
-	dbConnection.query.sync(
-		dbConnection
-		'UPDATE characters SET energy = 220, energy_max = 220'
-	)
-	console.log chalk.green 'ok'
-
-
-
-sync(
-	->
-		if opts.help
-			help()
-			process.exit 2
-
-		if opts.info
-			info()
-			process.exit 0
-
-		dropDatabase(opts.drop_database) if opts.drop_database
-		createDatabase(opts.create_database) if opts.create_database
-		migrateTables() if opts.migrate_tables
-		unifyValidate() if opts.unify_validate
-		unifyExport() if opts.unify_export
-		insertMonsters() if opts.monsters
-		fixAttrs() if opts.fix_attributes
-		fixEnergy() if opts.fix_energy
-		insertItems() if opts.items
-		optimize() if opts.optimize_tables # must always be the last
+	await dbConnection.queryAsync 'UPDATE characters SET energy = 220, energy_max = 220'
+	console.log chalk.green 'ok'
+
+
+### main ###
+(async ->
+	if opts.help
+		await help()
+		process.exit 2
+
+	if opts.info
+		await info()
 		process.exit 0
-	(ex) ->
-		if ex? then throw ex
-)
+
+	await dropDatabase(opts.drop_database) if opts.drop_database
+	await createDatabase(opts.create_database) if opts.create_database
+	await migrateTables() if opts.migrate_tables
+	await unifyValidate() if opts.unify_validate
+	await unifyExport() if opts.unify_export
+	await insertMonsters() if opts.monsters
+	await fixEnergy() if opts.fix_energy
+	await insertItems() if opts.items
+	await optimize() if opts.optimize_tables # must always be the last
+	process.exit 0
+)()


Make IR integration only on client

Make IR integration only on client

This is a pb if you have serverside routes because onRun runs for those as well and  Meteor.userId() throws an error in that context.

--- init.coffee
+++ init.coffee
@@ -85,3 +85,4 @@
       log "Tracking #{eventName} for #{Meteor.userId()}"
       g.analytics.track eventName
     @next()
+    ,{where : 'client'}


Update amd_tamer step

Update amd_tamer step

--- grunt/amd_tamer.coffee
+++ grunt/amd_tamer.coffee
@@ -3,17 +3,17 @@
 module.exports =
   options:
     namespace: '<%= package.name %>'
-    base: 'src/'
+    base: 'tmp/'
   all:
     files:
-      'dist/<%= package.name %>.all.js': 'src/**/*.js'
+      'dist/<%= package.name %>.all.js': 'tmp/**/*.js'
   base:
     files:
-      'dist/files/<%= package.name %>.js': ['src/**/*.js', '!src/renderer/**/*.js']
+      'dist/files/<%= package.name %>.js': ['tmp/**/*.js', '!tmp/renderer/**/*.js']
   graphics:
     files: [{
       expand: true
-      cwd: 'src/renderer/'
+      cwd: 'tmp/renderer/'
       src: '*.js'
       dest: 'dist/files/'
       rename: (dest, src) ->


Remove redundancy from dealwithit script

Hubot was firing twice on respond

--- src/scripts/dealwithit.coffee
+++ src/scripts/dealwithit.coffee
@@ -13,12 +13,6 @@
   "http://i.imgur.com/Wj3Do.gif"
 ]

-deal_with_it = (msg) ->
-  msg.send msg.random deal
-
 module.exports = (robot) ->
   robot.hear /deal with it/i, (msg)->
-    deal_with_it msg
-
-  robot.respond /deal with it/i, (msg)->
-    deal_with_it msg+    msg.send msg.random deal


simplify a little keys dom

simplify a little keys dom

--- src/scripts/graph/Keys.coffee
+++ src/scripts/graph/Keys.coffee
@@ -15,7 +15,7 @@

       dragmove = (d) ->
         sourceEvent = d3.event.sourceEvent
-        propertyObject = this.parentNode.parentNode
+        propertyObject = this.parentNode
         lineObject = propertyObject.parentNode.parentNode
         propertyData = d3.select(propertyObject).datum()
         lineData = d3.select(lineObject).datum()
@@ -60,8 +60,6 @@
       keys.enter()
         .append('g')
         .attr('class', 'key')
-        .append('g')
-        .attr('class', 'key__item')
         .call(drag)
         .on('click', selectKey)
         .append('rect')
@@ -71,10 +69,9 @@
         .attr('class', 'line--key')
         .attr('transform', 'rotate(45)')

-      keys.selectAll('.key__item')
-        .attr 'transform', (d) ->
-          dx = self.timeline.x(d.time * 1000) + 3
-          dy = 9
-          return "translate(" + dx + "," + dy + ")"
+      keys.attr 'transform', (d) ->
+        dx = self.timeline.x(d.time * 1000) + 3
+        dy = 9
+        return "translate(" + dx + "," + dy + ")"

       keys.exit().remove()


Making the hubot love script better

Making the hubot love script better

--- src/scripts/i-love-you.coffee
+++ src/scripts/i-love-you.coffee
@@ -13,8 +13,8 @@

 module.exports = (robot) ->

-  robot.respond /i love you/i, (msg) ->
+  robot.respond /(.*) love(s?) you/i, (msg) ->
     if msg.message.user.id is robot.name
       return

-    msg.reply "I love you too http://thumbs.dreamstime.com/x/gold-robot-character-holding-heart-both-hands-create-d-humanoid-robot-series-30164419.jpg"
+    msg.reply "Right back at ya! http://thumbs.dreamstime.com/x/gold-robot-character-holding-heart-both-hands-create-d-humanoid-robot-series-30164419.jpg"


make color picker reveal its color code

make color picker reveal its color code

--- demo.coffee
+++ demo.coffee
@@ -74,8 +74,14 @@
   h = parent.firstChild
   h.appendChild text(' ')
   h.appendChild s
+  sp = create 'span'
+  sp.innerHTML = color
+  sp.style.fontWeight = 'normal'
+  h.appendChild text(' ')
+  h.appendChild sp
   s.onchange = ->
     object[label] = s.value
+    sp.innerHTML = s.value
 makeCheckbox = (label, obj, parent) ->
 makeSlider = (label, object, parent) ->
   values = object[label]


Restore min/max bounds in range control from 11b677ee

--- src/coffee/cilantro/ui/field/controls/range.coffee
+++ src/coffee/cilantro/ui/field/controls/range.coffee
@@ -68,11 +68,9 @@
         # are read, the bounds will be updated if they need to be so that the
         # min/max are reflected in the UI in the input boxes when needed.
         readMinMaxStats: =>
-            statsMin = @model.stats.find (stat) ->
-                stat.get('label') is 'Min'
-
-            statsMax = @model.stats.find (stat) ->
-                return stat.get('label') is 'Max'
+            statsMin = @model.stats.findWhere key: 'min'
+
+            statsMax = @model.stats.findWhere key: 'max'

             if statsMin?
                 @minLowerBound = @parseMinStat(statsMin.get('value'))


Fix Coffeescript indentation error

Fix Coffeescript indentation error

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -315,13 +315,15 @@
     div.append(remove_link)

   isEventSupported = (eventName, element) ->
-    TAGNAMES = {'select': 'input',
-                'change': 'input',
-                'submit': 'form',
-                'reset': 'form',
-                'error': 'img',
-                'load': 'img',
-                'abort': 'img'}
+    TAGNAMES = {
+                "select": "input",
+                "change": "input",
+                "submit": "form",
+                "reset": "form",
+                "error": "img",
+                "load": "img",
+                "abort": "img"
+              }
     element = element || document.createElement(TAGNAMES[eventName] || 'div')
     eventName = 'on' + eventName
     isSupported = eventName in element


Update bower path

Update bower path

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -1,6 +1,6 @@
 #= require jquery/dist/jquery
 #= require jquery-ujs/src/rails
-#= require turbolinks/lib/assets/javascripts/turbolinks
+#= require rails-turbolinks/lib/assets/javascripts/turbolinks
 #= require modernizr/modernizr
 #= require foundation/js/foundation
 #= require select2/select2


Enable jQuery noConflict mode

Enable jQuery noConflict mode

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -34,6 +34,7 @@
     if e
       Effect.Fade e, duration: 5.0

+jQuery.noConflict()
 jQuery.datepicker.setDefaults jQuery.datepicker.regional['fr-CH']
 jQuery ->
   jQuery('.datepicker').datepicker


make iframe index high

make iframe index high

--- app/assets/javascripts/bookmarklet.js.coffee
+++ app/assets/javascripts/bookmarklet.js.coffee
@@ -12,5 +12,5 @@

 e = document.createElement("iframe")
 e.src = url
-e.setAttribute("style", "border: 1px solid red; position: fixed; top: 10px; right: 10px; width: 640px; height: 240px;")
+e.setAttribute("style", "border: 1px solid red; position: fixed; top: 10px; right: 10px; width: 640px; height: 240px; z-index: 9999;")
 document.body.appendChild(e)


svg min inplace to make less embed code

svg min inplace to make less embed code

--- app/templates/_gruntfile.coffee
+++ app/templates/_gruntfile.coffee
@@ -250,9 +250,9 @@
       dist:
         files: [
           expand: true
-          cwd: "<%%= yeoman.dist %>/images"
+          cwd: "<%%= yeoman.app %>/images"
           src: "{,*/}*.svg"
-          dest: "<%%= yeoman.dist %>/images"
+          dest: "<%%= yeoman.app %>/images"
         ]

@@ -406,9 +406,9 @@
     "uglify:dist"
     "autoprefixer:dist"
     "tinypng:dist"
+    "svgmin"
     "copy"
     "imagemin"
-    "svgmin"
     "cssmin"
     "docs"
     "notify:dist"


commented an unusual construction

commented an unusual construction

--- draw.coffee
+++ draw.coffee
@@ -85,6 +85,7 @@
 		createNote( lastDefinedChord song ).clean().name,
 		'C'
 	]
+	#return first from possibleKeys where key is in validKeys
 	_.find possibleKeys, (key) -> key in validKeys

 #this is some tricky JSON-specific logic and it's ugly and i hate it


naming

naming

--- src/protomixin.spec.coffee
+++ src/protomixin.spec.coffee
@@ -18,7 +18,7 @@
     Function::mixinto_proto = 'bar'
     expect(_.isFunction Function::mixinto_proto).toBe true

-  it 'should raise an error when mixing non-Mixins', ->
+  it 'should throw an error when mixing non-Mixins', ->
     for non_Mixin in [1, 'String', [], {}]
       expect(->
         class Example
@@ -53,7 +53,7 @@
     beforeEach ->
       @mixin = MIXINS.schematized_protomixin()

-    it 'should raise an error when mixing in Mixins with non-function hooks', ->
+    it 'should throw an error when mixing in Mixins with non-function hooks', ->
       @mixin.premixing_hook = 1

       expect(=>
@@ -175,6 +175,8 @@
         Example.mixinto_proto @mixin, omits: ['bar', 'baz']
       ).toThrow new MixinUtils.ArgumentError "Found nothing to mix in!"

+    it 'should throw an error when supplying a mixin-method hook'
+
     it 'should support pre-mixin hooks', ->
       throw new Error 'Write me!'


use only local resources in dev

use only local resources in dev

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -7,6 +7,9 @@
       @get("name") || @get("username")

     gravatar: ->
-      url = "https://www.gravatar.com/avatar/"
-      email = $.trim(@get("email"))
-      return url + md5(email) + "?d=mm"
+      if Bixby.app.dev
+        return "/images/gravatar_dev.png"
+      else
+        url = "https://www.gravatar.com/avatar/"
+        email = $.trim(@get("email"))
+        return url + md5(email) + "?d=mm"


Fix relationships in user

Fix relationships in user

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -3,8 +3,8 @@
   name: DS.attr('string')
   nickname: DS.attr('string')
   admin: DS.attr('boolean')
-  permissions: DS.hasMany('Review.Permission' )
-  person: DS.belongsTo('Review.Author')
+  permissions: DS.hasMany('permission' )
+  person: DS.belongsTo('author')
   token: DS.attr('string')

   isMe: (()->


improve logging & remove dead code

improve logging & remove dead code

--- server/models/mailbox.coffee
+++ server/models/mailbox.coffee
@@ -705,9 +705,17 @@
         step = new RefreshStep()
         step.limitByBox = options.limitByBox
         step.firstImport = options.firstImport
-        step.shouldNotif = false
         step.initial = true
         return step
+
+    # Public: string representation of the step, used by console.log
+    #
+    # Returns {String} an human readable summary of the step
+    inspect: ->
+        "Step{ limit:#{@limitByBox} " +
+        (if @initial then "initial" else "[#{@min}:#{@max}]") +
+        (if @firstImport then ' firstImport' else '') + '}'
+

     # Public: compute the next step.
     # The step will have a [.min - .max] range of uid
@@ -718,7 +726,7 @@
     #
     # Returns {RefreshStep} the next step
     getNext: (uidnext) ->
-        log.debug "computeNextStep", this, uidnext, @limitByBox
+        log.debug "computeNextStep", this, "next", uidnext

         if @initial
             # pretend the last step was max: INFINITY, min: uidnext
@@ -737,7 +745,6 @@
         step = new RefreshStep()
         step.firstImport = @firstImport
         step.limitByBox = @limitByBox
-        step.shouldNotif = @shouldNotif
         # new max is old min
         step.max = Math.max 1, @min - 1
         # new min is old min - range


fix(api task): dev:test:server for windows

--- helpers/jasmine.coffee
+++ helpers/jasmine.coffee
@@ -101,6 +101,7 @@
 			specFiles = @jasmine.specFiles
 			return @ unless specFiles.length
 			for file in specFiles
+				file = path.normalize file
 				moduleHelp.cache.delete file
 			@

@@ -108,6 +109,7 @@
 			helperFiles = @jasmine.helperFiles
 			return @ unless helperFiles.length
 			for file in helperFiles
+				file = path.normalize file
 				moduleHelp.cache.delete getHelperFilePath file
 			@


No need to minify coverage builds

No need to minify coverage builds

--- src/pipe/umd/index.coffee
+++ src/pipe/umd/index.coffee
@@ -65,6 +65,10 @@
             .pipe -> lib.metadata.rename({
               suffix: '.coverage'
             })
+            .pipe -> lib.metadata.data((file) ->
+              file.data.coverage = true
+              return file.data
+            )
             .pipe -> umd()

           )()
@@ -78,6 +82,7 @@
         lib.util.gutil.noop()
         (
           lib.pipe.lazypipe()
+          .pipe -> lib.pipe.ignore((file) -> file.data.coverage == true)
           .pipe -> lib.transform.uglify(options.uglify)
           .pipe -> lib.metadata.rename((pathBits) ->
             pathBits.extname = '.min' + pathBits.extname


Preis Tierarzt gesenkt.

Preis Tierarzt gesenkt.

--- bauernhof.coffee
+++ bauernhof.coffee
@@ -96,8 +96,8 @@
                 kuehe.push new Kuh 0
                 nachricht "Die Kuh #{ kuh.name } bekommt ein Kälbchen."
             if kuh.ist_krank()
-                geld -= 30
-                nachricht "Die Kuh #{ kuh.name } ist krank. Der Tierarzt kommt. Das kostet 30 Münzen."
+                geld -= 15
+                nachricht "Die Kuh #{ kuh.name } ist krank. Der Tierarzt kommt. Das kostet 15 Münzen."
             n = kuh.ist_gestorben()
             if n
                 nachricht n


CHG: fix a bug of popover autohide

CHG: fix a bug of popover autohide

--- lib/assets/javascripts/tao/ui/popover/element.coffee
+++ lib/assets/javascripts/tao/ui/popover/element.coffee
@@ -94,8 +94,12 @@
   _enableAutoHide: ->
     $(document).on "mousedown.tao-popover-#{@taoId}", (e) =>
       return unless @active
-      target = $ e.target
-      return if target.is(@target) or @jq.has(target).length or target.is(@)
+      target = e.target
+      inTarget = @target.is(target) || @target.has(target).length
+      inTrigger = @triggerEl && (@triggerEl.is(target) || @triggerEl.has(target).length)
+      inPopover = @jq.is(target) || @jq.has(target).length
+
+      return if inTarget || inTrigger || inPopover
       @active = false
       null


Pass AJAX response as string for web to have consistent handling in kernel

--- lib/calatrava/templates/web/app/source/bridge.coffee
+++ lib/calatrava/templates/web/app/source/bridge.coffee
@@ -63,6 +63,7 @@
       showLoader()
     success: (response) ->
       goToTop()
+      response = JSON.stringify(response) if  _.isObject(response)
       options.success(response)
     error: (response) ->
       showLoader()