From 6f0940eabe80303ac39a255978062b09992cb6bd Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 15:41:46 -0500 Subject: [PATCH 001/167] some refactoring to get a first try at file versioning in. Also moved some of server code out of the package.coffee file. --- src/hem.coffee | 101 +++++++++++++++++++++++++++++------------- src/package.coffee | 24 +++------- src/versioning.coffee | 42 ++++++++++++++++++ 3 files changed, 116 insertions(+), 51 deletions(-) create mode 100644 src/versioning.coffee diff --git a/src/hem.coffee b/src/hem.coffee index f764aaf..88a9099 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -5,6 +5,7 @@ connect = require('connect') httpProxy = require('http-proxy') http = require('http') compilers = require('./compilers') +versions = require('./versioning') Package = require('./package') # ------- Commandline arguments @@ -16,6 +17,7 @@ argv = optimist.usage([ ' watch :build & watch disk for changes' ' test :build and run tests' ' clean :clean compiled targets' + ' version :version the application files' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') .alias('d', 'debug').describe('d',':all compilations use debug mode') @@ -59,56 +61,61 @@ class Hem port: 9294 host: 'localhost' + errorAndExit: (error) -> + console.log "ERROR: #{error}" + process.exit(1) + constructor: (options = {}) -> @options[key] = value for key, value of options # quick check to make sure slug file exists if fs.existsSync(@slug) @options[key] = value for key, value of @readSlug() else - console.log "ERROR: Unable to find #{@slug} file in current directory" - process.exit(1) - # setup packages from options/slug - @packages = (@createPackage(name, config) for name, config of @options.packages) - # allow overrides + @errorAndExit "Unable to find #{@slug} file in current directory" + # if versioning turned on, pass in correct module to config + if @options.version + @options.version.type or= "package" + # allow overrides and set defaults @options.server.port = argv.port if argv.port @options.server.host or= "" + @options.routes or= [] + # setup packages from options/slug + @packages = (@createPackage(name, config) for name, config of @options.packages) + # TODO: move server code to server.coffee file! server: -> # create app app = connect() - + # setup dynamic targets first for pkg in @packages when not argv.n # determine url if its not already set - pkg.url or= @determinePackageUrl(pkg) + pkg.url or= @determineUrlFromRoutes(pkg) # exit if pkg.url isn't defined if not pkg.url - console.log "ERROR: Unable to determine url mapping for package: #{pkg.name}" - process.exit(1) - # set route + @errorAndExit "Unable to determine url mapping for package: #{pkg.name}" console.log "Map package '#{pkg.name}' to #{pkg.url}" if argv.v - app.use(pkg.url, pkg.middleware(argv.debug)) + + # setup middleware to server packages + app.use(@middleware(argv.debug)) # setup static routes - @options.routes or= [] for route in @options.routes url = Object.keys(route)[0] value = route[url] if (typeof value is 'string') # make sure path exists - if fs.existsSync(value) - console.log "Map directory '#{value}' to #{url}" if argv.v - app.use(url, connect.static(value)) - else - console.log "ERROR: The folder #{value} does not exist." - process.exit(1) + if not fs.existsSync(value) + @errorAndExit "The folder #{value} does not exist." + console.log "Map directory '#{value}' to #{url}" if argv.v + app.use(url, connect.static(value)) else if value.host # setup proxy - console.log "Proxy requests from #{url} to #{value.host}" if argv.v + console.log "Proxy '#{url}' to #{value.host}:#{value.port}#{value.hostPath}" if argv.v app.use(url, @createRoutingProxy(value)) @patchServerResponseForRedirects(@options.server.port, value) if value.patchRedirect else - throw new Error("Invalid route configuration for #{url}") + @errorAndExit "Invalid route configuration for #{url}" # start server http.createServer(app).listen(@options.server.port, @options.server.host) @@ -122,6 +129,17 @@ class Hem @clean() @buildTargets(argv.targets) + version: -> + # make sure version settings are present + if not @options.version + console.error "ERROR: Versioning not enabled in slug.json" + return + # find targets inside file and replace + type = versions[@options.version.type] + if not type + @errorAndExit "Incorrect type value for versioning (#{@options.version.type})" + type.updateFiles(@options.version.files, @packages) + watch: -> targets = argv.targets @buildTargets(targets) @@ -137,7 +155,6 @@ class Hem exec: (command = argv.command) -> return help() unless @[command] - # TODO: make sure argv.targets exist before proceeding?? switch command when 'build' then console.log 'Build application' when 'watch' then console.log 'Watching application' @@ -157,17 +174,13 @@ class Hem buildTargets: (targets = []) -> buildAll = targets.length is 0 - # TODO: preset a versionAddOn var if argv.setVersion - #if argv.setVersion and argv.version is null - #version = new Date().getUTCMilliseconds() #or something like this... pkg.build(not argv.debug) for pkg in @packages when pkg.name in targets or buildAll - # TODO: add auto build of an application.cache file with relevant target files pre-filled - #cache = pkg.compileCache() createRoutingProxy: (options = {}) -> proxy = new httpProxy.RoutingProxy() # additional options options.hostPath or= "" + options.port or= 80 # return function used by connect to access proxy return (req, res, next) -> req.url = "#{options.hostPath}#{req.url}" @@ -214,17 +227,41 @@ class Hem fileList.push pkg.target for pkg in @packages when pkg.isJavascript() return fileList - determinePackageUrl: (pkg) -> - # loop over server paths and see which one is the best match with the pkg.target + middleware: (debug) => + (req, res, next) => + # only deal with js/css files + url = require("url").parse(req.url)?.pathname.toLowerCase() or "" + if not url.match(/\.js|\.css/) + next() + return + # strip out any potential versioning + if versions + url = versions.trimVersionFromUrl(url) + console.log "hmmmm", url + # loop over pkgs and call compile + console.log url + for pkg in @packages + if url is pkg.url + # TODO: keep (and return) in memory build if there hasn't been any changes?? + str = pkg.compile(not debug) + res.charset = 'utf-8' + res.setHeader('Content-Type', pkg.contentType) + res.setHeader('Content-Length', Buffer.byteLength(str)) + res.end((req.method is 'HEAD' and null) or str) + return + # no matches, go to next middleware + next() + + determineUrlFromRoutes: (pkg) -> bestMatch = {} for route in @options.routes - url = Object.keys(route) - dir = route[url] - # TODO: should convert to full path before attempting match + url = Object.keys(route) + dir = route[url] + # compare against package target if pkg.target.indexOf(dir) == 0 and (!bestMatch.url or bestMatch.dir.length < dir.length) bestMatch.url = url + pkg.target.slice(dir.length) bestMatch.dir = dir - bestMatch.url + bestMatch.url.toLowerCase() module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index a50a7d3..2dfcabe 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -11,6 +11,7 @@ class Package constructor: (name, config = {}, argv = {}) -> @name = name @argv = argv + # set config values @identifier = config.identifier @target = config.target @@ -18,10 +19,12 @@ class Package @paths = toArray(config.paths || []) @modules = toArray(config.modules || []) @jsAfter = config.jsAfter or "" - @url = config.url or "" + @url = config.url # TODO: sanity checkes on config values?? + # determine content type based on target file name @contentType = mime.lookup(@target) + # set correct compiler based on mime type, set @compile = javascriptCompiler if @isJavascript() @compile = @compileJavascript @@ -60,11 +63,6 @@ class Package catch ex @handleCompileError(ex) - compileCache: (packages, versionAddOn)-> - #start with a date header - #define content... - #fs.writeFileSync('app.cache', content) if content - handleCompileError: (ex) -> console.error ex.message console.error ex.path if ex.path @@ -81,10 +79,7 @@ class Package unlink: -> fs.unlinkSync(@target) if fs.existsSync(@target) - build: (minify = false, versionAddOn) -> - # TODO: add an option to add timestamp or other string to the target js and css files - #if versionAddOn - #@target += "-#{versionAddOn}" + build: (minify = false) -> console.log "Building '#{@name}' target: #{@target}" source = @compile(minify) fs.writeFileSync(@target, source) if source @@ -92,17 +87,8 @@ class Package watch: -> console.log "Watching '#{@name}'" for dir in (path.dirname(lib) for lib in @libs).concat @paths - # TODO: handle symlink files/directories here?? continue unless fs.existsSync(dir) require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => @build() if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) - middleware: (debug) => - (req, res, next) => - str = @compile(not debug) - res.charset = 'utf-8' - res.setHeader('Content-Type', @contentType) - res.setHeader('Content-Length', Buffer.byteLength(str)) - res.end((req.method is 'HEAD' and null) or str) - module.exports = Package diff --git a/src/versioning.coffee b/src/versioning.coffee new file mode 100644 index 0000000..d83f2bb --- /dev/null +++ b/src/versioning.coffee @@ -0,0 +1,42 @@ +fs = require('fs') +path = require('path') +types = {} + +# private functions + +replaceTargetsInFiles = (files, version, pkgs) -> + for file in files + console.log "updating file #{file} with version #{version}" + data = fs.readFileSync(file, 'utf8') + # match all target in packages + for pkg in pkgs + data = replaceTargetInData(data, version, pkg) + fs.writeFileSync(file, data) + +replaceTargetInData = (data, version, pkg) -> + ext = path.extname(pkg.target) + name = path.basename(pkg.target, ext) + match = new RegExp("=(\"|')#{name}[^\"']?#{ext}(\"|')") + replace = "=$1#{name}.#{version}#{ext}$2" + data.replace(match, replace) + +# handle versioning based on package.json version (default) + +types.package = + + getVersion: -> + JSON.parse(fs.readFileSync('package.json', 'utf8')).version + + updateFiles: (files, pkgs) -> + replaceTargetsInFiles(files, @getVersion(), pkgs) + +# public methods + +types.trimVersionFromUrl = (url) -> + url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2") + +# TODO: other types that could be made +# 1) based on git commits/tags +# 2) backed on jenkinds builds + +module.exports = types From dc1653c818cd535e720fc111c66047ffd90efe3a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 15:41:55 -0500 Subject: [PATCH 002/167] compiled javascript --- lib/hem.js | 109 ++++++++++++++++++++++++++++++++++------------ lib/package.js | 24 ++-------- lib/versioning.js | 53 ++++++++++++++++++++++ 3 files changed, 138 insertions(+), 48 deletions(-) create mode 100644 lib/versioning.js diff --git a/lib/hem.js b/lib/hem.js index 6093fbc..e6fb7f2 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,6 +1,7 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Hem, Package, argv, compilers, connect, fs, help, http, httpProxy, optimist, path, + var Hem, Package, argv, compilers, connect, fs, help, http, httpProxy, optimist, path, versions, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; path = require('path'); @@ -17,9 +18,11 @@ compilers = require('./compilers'); + versions = require('./versioning'); + Package = require('./package'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('d', 'debug').describe('d', ':all compilations use debug mode').alias('t', 'test').describe('t', ':run testacular while using watch').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('b', 'browser').describe('b', ':run testacular using the supplied browser[s]').alias('n', 'noBuild').describe('n', ':turn off dynamic builds during server mode').describe('v', ':make hem more talkative(verbose)').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('d', 'debug').describe('d', ':all compilations use debug mode').alias('t', 'test').describe('t', ':run testacular while using watch').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('b', 'browser').describe('b', ':run testacular using the supplied browser[s]').alias('n', 'noBuild').describe('n', ':turn off dynamic builds during server mode').describe('v', ':make hem more talkative(verbose)').argv; argv.command = argv._[0]; @@ -59,12 +62,18 @@ } }; + Hem.prototype.errorAndExit = function(error) { + console.log("ERROR: " + error); + return process.exit(1); + }; + function Hem(options) { - var config, key, name, value, _base, _ref; + var config, key, name, value, _base, _base1, _base2, _ref; if (options == null) { options = {}; } + this.middleware = __bind(this.middleware, this); for (key in options) { value = options[key]; this.options[key] = value; @@ -76,9 +85,16 @@ this.options[key] = value; } } else { - console.log("ERROR: Unable to find " + this.slug + " file in current directory"); - process.exit(1); + this.errorAndExit("Unable to find " + this.slug + " file in current directory"); + } + if (this.options.version) { + (_base = this.options.version).type || (_base.type = "package"); + } + if (argv.port) { + this.options.server.port = argv.port; } + (_base1 = this.options.server).host || (_base1.host = ""); + (_base2 = this.options).routes || (_base2.routes = []); this.packages = (function() { var _ref1, _results; @@ -90,14 +106,10 @@ } return _results; }).call(this); - if (argv.port) { - this.options.server.port = argv.port; - } - (_base = this.options.server).host || (_base.host = ""); } Hem.prototype.server = function() { - var app, pkg, route, url, value, _base, _i, _j, _len, _len1, _ref, _ref1; + var app, pkg, route, url, value, _i, _j, _len, _len1, _ref, _ref1; app = connect(); _ref = this.packages; @@ -106,42 +118,38 @@ if (!(!argv.n)) { continue; } - pkg.url || (pkg.url = this.determinePackageUrl(pkg)); + pkg.url || (pkg.url = this.determineUrlFromRoutes(pkg)); if (!pkg.url) { - console.log("ERROR: Unable to determine url mapping for package: " + pkg.name); - process.exit(1); + this.errorAndExit("Unable to determine url mapping for package: " + pkg.name); } if (argv.v) { console.log("Map package '" + pkg.name + "' to " + pkg.url); } - app.use(pkg.url, pkg.middleware(argv.debug)); } - (_base = this.options).routes || (_base.routes = []); + app.use(this.middleware(argv.debug)); _ref1 = this.options.routes; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { route = _ref1[_j]; url = Object.keys(route)[0]; value = route[url]; if (typeof value === 'string') { - if (fs.existsSync(value)) { - if (argv.v) { - console.log("Map directory '" + value + "' to " + url); - } - app.use(url, connect["static"](value)); - } else { - console.log("ERROR: The folder " + value + " does not exist."); - process.exit(1); + if (!fs.existsSync(value)) { + this.errorAndExit("The folder " + value + " does not exist."); } + if (argv.v) { + console.log("Map directory '" + value + "' to " + url); + } + app.use(url, connect["static"](value)); } else if (value.host) { if (argv.v) { - console.log("Proxy requests from " + url + " to " + value.host); + console.log("Proxy '" + url + "' to " + value.host + ":" + value.port + value.hostPath); } app.use(url, this.createRoutingProxy(value)); if (value.patchRedirect) { this.patchServerResponseForRedirects(this.options.server.port, value); } } else { - throw new Error("Invalid route configuration for " + url); + this.errorAndExit("Invalid route configuration for " + url); } } return http.createServer(app).listen(this.options.server.port, this.options.server.host); @@ -168,6 +176,20 @@ return this.buildTargets(argv.targets); }; + Hem.prototype.version = function() { + var type; + + if (!this.options.version) { + console.error("ERROR: Versioning not enabled in slug.json"); + return; + } + type = versions[this.options.version.type]; + if (!type) { + this.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); + } + return type.updateFiles(this.options.version.files, this.packages); + }; + Hem.prototype.watch = function() { var pkg, targets, watchAll, _i, _len, _ref, _ref1, _results; @@ -261,6 +283,7 @@ } proxy = new httpProxy.RoutingProxy(); options.hostPath || (options.hostPath = ""); + options.port || (options.port = 80); return function(req, res, next) { req.url = "" + options.hostPath + req.url; return proxy.proxyRequest(req, res, options); @@ -323,7 +346,39 @@ return fileList; }; - Hem.prototype.determinePackageUrl = function(pkg) { + Hem.prototype.middleware = function(debug) { + var _this = this; + + return function(req, res, next) { + var pkg, str, url, _i, _len, _ref, _ref1; + + url = ((_ref = require("url").parse(req.url)) != null ? _ref.pathname.toLowerCase() : void 0) || ""; + if (!url.match(/\.js|\.css/)) { + next(); + return; + } + if (versions) { + url = versions.trimVersionFromUrl(url); + console.log("hmmmm", url); + } + console.log(url); + _ref1 = _this.packages; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + pkg = _ref1[_i]; + if (url === pkg.url) { + str = pkg.compile(!debug); + res.charset = 'utf-8'; + res.setHeader('Content-Type', pkg.contentType); + res.setHeader('Content-Length', Buffer.byteLength(str)); + res.end((req.method === 'HEAD' && null) || str); + return; + } + } + return next(); + }; + }; + + Hem.prototype.determineUrlFromRoutes = function(pkg) { var bestMatch, dir, route, url, _i, _len, _ref; bestMatch = {}; @@ -337,7 +392,7 @@ bestMatch.dir = dir; } } - return bestMatch.url; + return bestMatch.url.toLowerCase(); }; return Hem; diff --git a/lib/package.js b/lib/package.js index 5dc2a05..8922c35 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,7 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Dependency, Package, Stitch, fs, mime, path, stitchFile, toArray, uglify, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + var Dependency, Package, Stitch, fs, mime, path, stitchFile, toArray, uglify; fs = require('fs'); @@ -27,7 +26,6 @@ if (argv == null) { argv = {}; } - this.middleware = __bind(this.middleware, this); this.name = name; this.argv = argv; this.identifier = config.identifier; @@ -36,7 +34,7 @@ this.paths = toArray(config.paths || []); this.modules = toArray(config.modules || []); this.jsAfter = config.jsAfter || ""; - this.url = config.url || ""; + this.url = config.url; this.contentType = mime.lookup(this.target); if (this.isJavascript()) { this.compile = this.compileJavascript; @@ -113,8 +111,6 @@ } }; - Package.prototype.compileCache = function(packages, versionAddOn) {}; - Package.prototype.handleCompileError = function(ex) { console.error(ex.message); if (ex.path) { @@ -143,7 +139,7 @@ } }; - Package.prototype.build = function(minify, versionAddOn) { + Package.prototype.build = function(minify) { var source; if (minify == null) { @@ -190,20 +186,6 @@ return _results; }; - Package.prototype.middleware = function(debug) { - var _this = this; - - return function(req, res, next) { - var str; - - str = _this.compile(!debug); - res.charset = 'utf-8'; - res.setHeader('Content-Type', _this.contentType); - res.setHeader('Content-Length', Buffer.byteLength(str)); - return res.end((req.method === 'HEAD' && null) || str); - }; - }; - return Package; })(); diff --git a/lib/versioning.js b/lib/versioning.js new file mode 100644 index 0000000..53f4a78 --- /dev/null +++ b/lib/versioning.js @@ -0,0 +1,53 @@ +// Generated by CoffeeScript 1.6.2 +(function() { + var fs, path, replaceTargetInData, replaceTargetsInFiles, types; + + fs = require('fs'); + + path = require('path'); + + types = {}; + + replaceTargetsInFiles = function(files, version, pkgs) { + var data, file, pkg, _i, _j, _len, _len1, _results; + + _results = []; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + console.log("updating file " + file + " with version " + version); + data = fs.readFileSync(file, 'utf8'); + for (_j = 0, _len1 = pkgs.length; _j < _len1; _j++) { + pkg = pkgs[_j]; + data = replaceTargetInData(data, version, pkg); + } + _results.push(fs.writeFileSync(file, data)); + } + return _results; + }; + + replaceTargetInData = function(data, version, pkg) { + var ext, match, name, replace; + + ext = path.extname(pkg.target); + name = path.basename(pkg.target, ext); + match = new RegExp("=(\"|')" + name + "[^\"']?" + ext + "(\"|')"); + replace = "=$1" + name + "." + version + ext + "$2"; + return data.replace(match, replace); + }; + + types["package"] = { + getVersion: function() { + return JSON.parse(fs.readFileSync('package.json', 'utf8')).version; + }, + updateFiles: function(files, pkgs) { + return replaceTargetsInFiles(files, this.getVersion(), pkgs); + } + }; + + types.trimVersionFromUrl = function(url) { + return url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2"); + }; + + module.exports = types; + +}).call(this); From 80acecf7062c3f7564caeb14d483df5bd370428b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 15:59:13 -0500 Subject: [PATCH 003/167] removing some extra console logs --- lib/hem.js | 2 -- src/hem.coffee | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index e6fb7f2..5ca29ee 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -359,9 +359,7 @@ } if (versions) { url = versions.trimVersionFromUrl(url); - console.log("hmmmm", url); } - console.log(url); _ref1 = _this.packages; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { pkg = _ref1[_i]; diff --git a/src/hem.coffee b/src/hem.coffee index 88a9099..47553a6 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -237,9 +237,7 @@ class Hem # strip out any potential versioning if versions url = versions.trimVersionFromUrl(url) - console.log "hmmmm", url # loop over pkgs and call compile - console.log url for pkg in @packages if url is pkg.url # TODO: keep (and return) in memory build if there hasn't been any changes?? From b06b8a5299defdc2da6b527929f0eae9b8a3899a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 16:39:50 -0500 Subject: [PATCH 004/167] setting variables in compilers module --- src/hem.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index 47553a6..bbe2718 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -33,8 +33,8 @@ argv.command = argv._[0] argv.targets = argv._[1..] # set compilers debug mode -compilers.DEBUG = argv.debug and true or false - +compilers.DEBUG = !!argv.debug +compilers.VERBOSE = !!argv.v # ------- Global Functions From 03a5e6502bd9c688f359a8ba8d025ef21038c7b4 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 16:40:01 -0500 Subject: [PATCH 005/167] read from relative directory --- src/versioning.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/versioning.coffee b/src/versioning.coffee index d83f2bb..9167125 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -25,7 +25,7 @@ replaceTargetInData = (data, version, pkg) -> types.package = getVersion: -> - JSON.parse(fs.readFileSync('package.json', 'utf8')).version + JSON.parse(fs.readFileSync('./package.json', 'utf8')).version updateFiles: (files, pkgs) -> replaceTargetsInFiles(files, @getVersion(), pkgs) From fa65946587712274b864d62542b7f704e2a85169 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 16:40:34 -0500 Subject: [PATCH 006/167] new compilers module, inspired by http://blog.divshot.com/post/31336785156/exposing-env-to-spine --- src/compilers.coffee | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/compilers.coffee b/src/compilers.coffee index 0f03a32..d4986c0 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -103,4 +103,23 @@ try module._compile "module.exports = #{source}", filename catch err +# create a javascript module based off key values found in environment + +compilers.env = (path) -> + content = fs.readFileSync(path, 'utf8') + envhash = JSON.parse(content) + packjson = JSON.parse(fs.readFileSync('./package.json', 'utf8')) + # loop over values in file + for key of envhash + if process.env[key] + envhash[key] = process.env[key] + if compilers.VERBOSE + console.log "- Set env #{key} to #{envhash[key]}" + if packjson[key] + envhash[key] = packjson[key] + if compilers.VERBOSE + console.log "- Set env #{key} to #{envhash[key]}" + # return javascript module + return "module.exports = " + JSON.stringify(envhash) + module.exports = compilers From 2ff3814f37dead77e176d102d11230351a8bf691 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 16:40:46 -0500 Subject: [PATCH 007/167] new javascript compile --- lib/compilers.js | 23 +++++++++++++++++++++++ lib/hem.js | 4 +++- lib/versioning.js | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 6aa8802..dc54aad 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -133,6 +133,29 @@ err = _error; } + compilers.env = function(path) { + var content, envhash, key, packjson; + + content = fs.readFileSync(path, 'utf8'); + envhash = JSON.parse(content); + packjson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + for (key in envhash) { + if (process.env[key]) { + envhash[key] = process.env[key]; + if (compilers.VERBOSE) { + console.log("- Set env " + key + " to " + envhash[key]); + } + } + if (packjson[key]) { + envhash[key] = packjson[key]; + if (compilers.VERBOSE) { + console.log("- Set env " + key + " to " + envhash[key]); + } + } + } + return "module.exports = " + JSON.stringify(envhash); + }; + module.exports = compilers; }).call(this); diff --git a/lib/hem.js b/lib/hem.js index 5ca29ee..c487e8c 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -28,7 +28,9 @@ argv.targets = argv._.slice(1); - compilers.DEBUG = argv.debug && true || false; + compilers.DEBUG = !!argv.debug; + + compilers.VERBOSE = !!argv.v; help = function() { optimist.showHelp(); diff --git a/lib/versioning.js b/lib/versioning.js index 53f4a78..9095598 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -37,7 +37,7 @@ types["package"] = { getVersion: function() { - return JSON.parse(fs.readFileSync('package.json', 'utf8')).version; + return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; }, updateFiles: function(files, pkgs) { return replaceTargetsInFiles(files, this.getVersion(), pkgs); From 179a069dd2a65852b20540e102df87368c80536d Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 6 May 2013 16:55:43 -0500 Subject: [PATCH 008/167] update to version number and updated dependencies --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 9110e55..40a2590 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.3.2", + "version": "0.3.5", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": ["spine", "commonsJS", "coffeescript", "stylus", "jade", "eco", "testacular", "jasmine"], "contributors": ["maccman", "aeischeid", "cengebretson"], @@ -21,13 +21,13 @@ "eco": "1.1.0-rc-3", "uglify-js": "~1.3.3", "fast-detective": "~0.0.2", - "optimist": "~0.3.0", - "stylus" : ">=0.32.0", - "coffee-script" : ">=1.6.0", - "watch" : "~0.5.0", - "connect": "~2.6.0", - "http-proxy": "~0.8.2", - "karma": ">=0.8.0", - "jade": ">=0.28.2" + "optimist": "~0.4.0", + "stylus" : "~0.32.0", + "coffee-script" : "~1.6.0", + "watch" : "~0.7.0", + "connect": "~2.7.0", + "http-proxy": "~0.10.0", + "karma": "~0.8.0", + "jade": "~0.30.0" } } From 6fba091e19e2362d0fa2bc37cce467a9d450f42f Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 8 May 2013 19:13:21 -0500 Subject: [PATCH 009/167] updated http-proxy module doesn't supply headers as a parameter to writeHead anymore --- src/hem.coffee | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index bbe2718..43f17a5 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -188,14 +188,12 @@ class Hem patchServerResponseForRedirects: (port, config) -> writeHead = http.ServerResponse.prototype.writeHead - http.ServerResponse.prototype.writeHead = -> - @.emit('header') if (!@._emittedHeader) - @._emittedHeader = true - [ status, head ] = arguments + http.ServerResponse.prototype.writeHead = (status) -> if status in [301,302] + headers = @_headers oldLocation = new RegExp(":\/\/#{config.host}:?[0-9]*") newLocation = "://localhost:#{port}" - head.location = head.location.replace(oldLocation,newLocation) + headers.location = headers.location.replace(oldLocation,newLocation) return writeHead.apply(this, arguments) startTestacular: (targets = [], singleRun = true) -> From d99717d9419f9b116b4aa731076089a7cc27250c Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 8 May 2013 19:13:31 -0500 Subject: [PATCH 010/167] versioning tweaks --- src/hem.coffee | 17 ++++++++--------- src/versioning.coffee | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index 43f17a5..3b25435 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -75,6 +75,10 @@ class Hem # if versioning turned on, pass in correct module to config if @options.version @options.version.type or= "package" + @vertype = versions[@options.version.type] + # make sure version.type is valid + if not @vertype + @errorAndExit "Incorrect type value for versioning (#{@options.version.type})" # allow overrides and set defaults @options.server.port = argv.port if argv.port @options.server.host or= "" @@ -130,15 +134,10 @@ class Hem @buildTargets(argv.targets) version: -> - # make sure version settings are present - if not @options.version + if not @vertype console.error "ERROR: Versioning not enabled in slug.json" return - # find targets inside file and replace - type = versions[@options.version.type] - if not type - @errorAndExit "Incorrect type value for versioning (#{@options.version.type})" - type.updateFiles(@options.version.files, @packages) + @vertype.updateFiles(@options.version.files, @packages) watch: -> targets = argv.targets @@ -233,8 +232,8 @@ class Hem next() return # strip out any potential versioning - if versions - url = versions.trimVersionFromUrl(url) + if @vertype + url = @vertype.trimVersion(url) # loop over pkgs and call compile for pkg in @packages if url is pkg.url diff --git a/src/versioning.coffee b/src/versioning.coffee index 9167125..a8067f5 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -30,13 +30,13 @@ types.package = updateFiles: (files, pkgs) -> replaceTargetsInFiles(files, @getVersion(), pkgs) -# public methods - -types.trimVersionFromUrl = (url) -> + trimVersion: (url) -> url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2") # TODO: other types that could be made # 1) based on git commits/tags # 2) backed on jenkinds builds +# 3) Allow build/version to happen with one command +# 4) allow command line options to version command module.exports = types From 3a6bd76e8f53ed2c79d532309580f0546adcab4c Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 8 May 2013 19:13:39 -0500 Subject: [PATCH 011/167] compiled javascript files --- lib/hem.js | 30 ++++++++++++------------------ lib/versioning.js | 7 +++---- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index c487e8c..17eb2d3 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -91,6 +91,10 @@ } if (this.options.version) { (_base = this.options.version).type || (_base.type = "package"); + this.vertype = versions[this.options.version.type]; + if (!this.vertype) { + this.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); + } } if (argv.port) { this.options.server.port = argv.port; @@ -179,17 +183,11 @@ }; Hem.prototype.version = function() { - var type; - - if (!this.options.version) { + if (!this.vertype) { console.error("ERROR: Versioning not enabled in slug.json"); return; } - type = versions[this.options.version.type]; - if (!type) { - this.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); - } - return type.updateFiles(this.options.version.files, this.packages); + return this.vertype.updateFiles(this.options.version.files, this.packages); }; Hem.prototype.watch = function() { @@ -296,18 +294,14 @@ var writeHead; writeHead = http.ServerResponse.prototype.writeHead; - return http.ServerResponse.prototype.writeHead = function() { - var head, newLocation, oldLocation, status; + return http.ServerResponse.prototype.writeHead = function(status) { + var headers, newLocation, oldLocation; - if (!this._emittedHeader) { - this.emit('header'); - } - this._emittedHeader = true; - status = arguments[0], head = arguments[1]; if (status === 301 || status === 302) { + headers = this._headers; oldLocation = new RegExp(":\/\/" + config.host + ":?[0-9]*"); newLocation = "://localhost:" + port; - head.location = head.location.replace(oldLocation, newLocation); + headers.location = headers.location.replace(oldLocation, newLocation); } return writeHead.apply(this, arguments); }; @@ -359,8 +353,8 @@ next(); return; } - if (versions) { - url = versions.trimVersionFromUrl(url); + if (_this.vertype) { + url = _this.vertype.trimVersion(url); } _ref1 = _this.packages; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { diff --git a/lib/versioning.js b/lib/versioning.js index 9095598..ffbe413 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -41,13 +41,12 @@ }, updateFiles: function(files, pkgs) { return replaceTargetsInFiles(files, this.getVersion(), pkgs); + }, + trimVersion: function(url) { + return url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2"); } }; - types.trimVersionFromUrl = function(url) { - return url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2"); - }; - module.exports = types; }).call(this); From cc6c9aeb4ef87111e03f54ce27b9a34cec99caa9 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 15 May 2013 08:55:23 -0500 Subject: [PATCH 012/167] updating to 0.3.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40a2590..7874c1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.3.5", + "version": "0.3.6", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": ["spine", "commonsJS", "coffeescript", "stylus", "jade", "eco", "testacular", "jasmine"], "contributors": ["maccman", "aeischeid", "cengebretson"], From 6e1f37678020e6872e7da9d9a6e8a394d09cfd0b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 15 May 2013 09:04:55 -0500 Subject: [PATCH 013/167] adding package.json version output to help command --- lib/hem.js | 3 +++ src/hem.coffee | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/hem.js b/lib/hem.js index 17eb2d3..b0dbc3c 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -33,6 +33,9 @@ compilers.VERBOSE = !!argv.v; help = function() { + var _ref; + + console.log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); optimist.showHelp(); return process.exit(); }; diff --git a/src/hem.coffee b/src/hem.coffee index 3b25435..00d3e50 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -39,6 +39,7 @@ compilers.VERBOSE = !!argv.v # ------- Global Functions help = -> + console.log "HEM Version: " + require('../package.json')?.version + "\n" optimist.showHelp() process.exit() From 02f35d76dad640e351b7ed05d2371e0d69e630af Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 30 May 2013 18:14:05 -0500 Subject: [PATCH 014/167] updated package information and dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7874c1c..900498e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.3.6", + "version": "0.4.0", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": ["spine", "commonsJS", "coffeescript", "stylus", "jade", "eco", "testacular", "jasmine"], "contributors": ["maccman", "aeischeid", "cengebretson"], @@ -26,8 +26,8 @@ "coffee-script" : "~1.6.0", "watch" : "~0.7.0", "connect": "~2.7.0", - "http-proxy": "~0.10.0", + "http-proxy": "~0.10", "karma": "~0.8.0", - "jade": "~0.30.0" + "jade": "~0.30" } } From 3f4a62868664b2ca5785759a146c73931c87f737 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 30 May 2013 19:03:12 -0500 Subject: [PATCH 015/167] default patchRedirect to true --- lib/hem.js | 2 +- src/hem.coffee | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index b0dbc3c..126cee4 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -154,7 +154,7 @@ console.log("Proxy '" + url + "' to " + value.host + ":" + value.port + value.hostPath); } app.use(url, this.createRoutingProxy(value)); - if (value.patchRedirect) { + if (value.patchRedirect === !false) { this.patchServerResponseForRedirects(this.options.server.port, value); } } else { diff --git a/src/hem.coffee b/src/hem.coffee index 00d3e50..87ac3c3 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -118,7 +118,8 @@ class Hem # setup proxy console.log "Proxy '#{url}' to #{value.host}:#{value.port}#{value.hostPath}" if argv.v app.use(url, @createRoutingProxy(value)) - @patchServerResponseForRedirects(@options.server.port, value) if value.patchRedirect + if value.patchRedirect is not false # default to true + @patchServerResponseForRedirects(@options.server.port, value) else @errorAndExit "Invalid route configuration for #{url}" From 9327f2e0c1cb6e69a04dcbfc7ad514052398bca1 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 3 Jun 2013 18:45:56 -0500 Subject: [PATCH 016/167] refactoring the server code out of the hem.coffee and into its own file. Also making it possible to use hem functions as middleware in a connect/express server. --- lib/hem.js | 191 +++++++++------------------------------------- lib/server.js | 140 +++++++++++++++++++++++++++++++++ src/hem.coffee | 156 ++++++++++--------------------------- src/server.coffee | 111 +++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 268 deletions(-) create mode 100644 lib/server.js create mode 100644 src/server.coffee diff --git a/lib/hem.js b/lib/hem.js index 126cee4..7a4f2d4 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,7 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Hem, Package, argv, compilers, connect, fs, help, http, httpProxy, optimist, path, versions, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + var Hem, Package, argv, compilers, fs, help, optimist, path, server, versions, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; path = require('path'); @@ -10,14 +9,10 @@ optimist = require('optimist'); - connect = require('connect'); - - httpProxy = require('http-proxy'); - - http = require('http'); - compilers = require('./compilers'); + server = require('./server'); + versions = require('./versioning'); Package = require('./package'); @@ -28,9 +23,9 @@ argv.targets = argv._.slice(1); - compilers.DEBUG = !!argv.debug; + compilers.DEBUG = server.DEBUG = !!argv.debug; - compilers.VERBOSE = !!argv.v; + compilers.VERBOSE = server.VERBOSE = !!argv.v; help = function() { var _ref; @@ -56,14 +51,20 @@ return _results; }; - Hem.prototype.compilers = compilers; + Hem.middleware = function(slugFile) { + var hem; + + hem = new Hem(slugFile); + return server.middleware(hem.packages, hem.options.server); + }; - Hem.prototype.slug = argv.slug || './slug.json'; + Hem.prototype.compilers = compilers; Hem.prototype.options = { + framework: "spine", server: { port: 9294, - host: 'localhost' + host: "localhost" } }; @@ -73,29 +74,32 @@ }; function Hem(options) { - var config, key, name, value, _base, _base1, _base2, _ref; + var config, key, name, slug, value, _base, _base1, _base2, _ref; if (options == null) { options = {}; } - this.middleware = __bind(this.middleware, this); - for (key in options) { - value = options[key]; - this.options[key] = value; + if (options === "string") { + slug = options; + } else { + slug = argv.slug || './slug.json'; + for (key in options) { + value = options[key]; + this.options[key] = value; + } } - if (fs.existsSync(this.slug)) { - _ref = this.readSlug(); + if (fs.existsSync(slug)) { + _ref = this.readSlug(slug); for (key in _ref) { value = _ref[key]; this.options[key] = value; } } else { - this.errorAndExit("Unable to find " + this.slug + " file in current directory"); + this.errorAndExit("Unable to find " + slug + " file in current directory"); } if (this.options.version) { (_base = this.options.version).type || (_base.type = "package"); - this.vertype = versions[this.options.version.type]; - if (!this.vertype) { + if (!(this.options.version.module = versions[this.options.version.type])) { this.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); } } @@ -103,7 +107,7 @@ this.options.server.port = argv.port; } (_base1 = this.options.server).host || (_base1.host = ""); - (_base2 = this.options).routes || (_base2.routes = []); + (_base2 = this.options.server).routes || (_base2.routes = []); this.packages = (function() { var _ref1, _results; @@ -118,50 +122,7 @@ } Hem.prototype.server = function() { - var app, pkg, route, url, value, _i, _j, _len, _len1, _ref, _ref1; - - app = connect(); - _ref = this.packages; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - if (!(!argv.n)) { - continue; - } - pkg.url || (pkg.url = this.determineUrlFromRoutes(pkg)); - if (!pkg.url) { - this.errorAndExit("Unable to determine url mapping for package: " + pkg.name); - } - if (argv.v) { - console.log("Map package '" + pkg.name + "' to " + pkg.url); - } - } - app.use(this.middleware(argv.debug)); - _ref1 = this.options.routes; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - route = _ref1[_j]; - url = Object.keys(route)[0]; - value = route[url]; - if (typeof value === 'string') { - if (!fs.existsSync(value)) { - this.errorAndExit("The folder " + value + " does not exist."); - } - if (argv.v) { - console.log("Map directory '" + value + "' to " + url); - } - app.use(url, connect["static"](value)); - } else if (value.host) { - if (argv.v) { - console.log("Proxy '" + url + "' to " + value.host + ":" + value.port + value.hostPath); - } - app.use(url, this.createRoutingProxy(value)); - if (value.patchRedirect === !false) { - this.patchServerResponseForRedirects(this.options.server.port, value); - } - } else { - this.errorAndExit("Invalid route configuration for " + url); - } - } - return http.createServer(app).listen(this.options.server.port, this.options.server.host); + return server.start(this.packages, this.options.server); }; Hem.prototype.clean = function() { @@ -186,11 +147,15 @@ }; Hem.prototype.version = function() { - if (!this.vertype) { - console.error("ERROR: Versioning not enabled in slug.json"); - return; + var files, module, _ref, _ref1; + + module = (_ref = this.options.version) != null ? _ref.module : void 0; + files = (_ref1 = this.options.version) != null ? _ref1.files : void 0; + if (module && files) { + return module.updateFiles(files, this.packages); + } else { + return console.error("ERROR: Versioning not enabled in slug.json"); } - return this.vertype.updateFiles(this.options.version.files, this.packages); }; Hem.prototype.watch = function() { @@ -245,9 +210,6 @@ }; Hem.prototype.readSlug = function(slug) { - if (slug == null) { - slug = this.slug; - } if (!(slug && fs.existsSync(slug))) { return {}; } @@ -278,38 +240,6 @@ return _results; }; - Hem.prototype.createRoutingProxy = function(options) { - var proxy; - - if (options == null) { - options = {}; - } - proxy = new httpProxy.RoutingProxy(); - options.hostPath || (options.hostPath = ""); - options.port || (options.port = 80); - return function(req, res, next) { - req.url = "" + options.hostPath + req.url; - return proxy.proxyRequest(req, res, options); - }; - }; - - Hem.prototype.patchServerResponseForRedirects = function(port, config) { - var writeHead; - - writeHead = http.ServerResponse.prototype.writeHead; - return http.ServerResponse.prototype.writeHead = function(status) { - var headers, newLocation, oldLocation; - - if (status === 301 || status === 302) { - headers = this._headers; - oldLocation = new RegExp(":\/\/" + config.host + ":?[0-9]*"); - newLocation = "://localhost:" + port; - headers.location = headers.location.replace(oldLocation, newLocation); - } - return writeHead.apply(this, arguments); - }; - }; - Hem.prototype.startTestacular = function(targets, singleRun) { var testConfig; @@ -345,53 +275,6 @@ return fileList; }; - Hem.prototype.middleware = function(debug) { - var _this = this; - - return function(req, res, next) { - var pkg, str, url, _i, _len, _ref, _ref1; - - url = ((_ref = require("url").parse(req.url)) != null ? _ref.pathname.toLowerCase() : void 0) || ""; - if (!url.match(/\.js|\.css/)) { - next(); - return; - } - if (_this.vertype) { - url = _this.vertype.trimVersion(url); - } - _ref1 = _this.packages; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - pkg = _ref1[_i]; - if (url === pkg.url) { - str = pkg.compile(!debug); - res.charset = 'utf-8'; - res.setHeader('Content-Type', pkg.contentType); - res.setHeader('Content-Length', Buffer.byteLength(str)); - res.end((req.method === 'HEAD' && null) || str); - return; - } - } - return next(); - }; - }; - - Hem.prototype.determineUrlFromRoutes = function(pkg) { - var bestMatch, dir, route, url, _i, _len, _ref; - - bestMatch = {}; - _ref = this.options.routes; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - route = _ref[_i]; - url = Object.keys(route); - dir = route[url]; - if (pkg.target.indexOf(dir) === 0 && (!bestMatch.url || bestMatch.dir.length < dir.length)) { - bestMatch.url = url + pkg.target.slice(dir.length); - bestMatch.dir = dir; - } - } - return bestMatch.url.toLowerCase(); - }; - return Hem; })(); diff --git a/lib/server.js b/lib/server.js new file mode 100644 index 0000000..cb63598 --- /dev/null +++ b/lib/server.js @@ -0,0 +1,140 @@ +// Generated by CoffeeScript 1.6.2 +(function() { + var connect, createRoutingProxy, determineUrlFromRoutes, fs, http, httpProxy, patchServerResponseForRedirects, server; + + connect = require('connect'); + + httpProxy = require('http-proxy'); + + http = require('http'); + + fs = require('fs'); + + server = {}; + + server.start = function(packages, options) { + var app; + + app = connect(); + app.use(server.middleware(app, packages, options)); + return http.createServer(app).listen(options.port, options.host); + }; + + server.middleware = function(app, packages, options) { + var pkg, route, url, value, _i, _j, _len, _len1, _ref; + + for (_i = 0, _len = packages.length; _i < _len; _i++) { + pkg = packages[_i]; + pkg.url || (pkg.url = determineUrlFromRoutes(pkg, options.routes)); + if (!pkg.url) { + throw new Error("Unable to determine url mapping for package: " + pkg.name); + } + if (!!server.VERBOSE) { + console.log("Map package '" + pkg.name + "' to " + pkg.url); + } + } + _ref = options.routes; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + route = _ref[_j]; + url = Object.keys(route)[0]; + value = route[url]; + if (typeof value === 'string') { + if (fs.existsSync(value)) { + if (!!server.VERBOSE) { + console.log("Map directory '" + value + "' to " + url); + } + app.use(url, connect["static"](value)); + } else { + console.log("ERROR: The folder " + value + " does not exist."); + process.exit(1); + } + } else if (value.host) { + if (!!server.VERBOSE) { + console.log("Proxy requests from " + url + " to " + value.host); + } + app.use(url, createRoutingProxy(value)); + } else { + throw new Error("Invalid route configuration for " + url); + } + } + return function(req, res, next) { + var str, _k, _len2, _ref1; + + url = ((_ref1 = require("url").parse(req.url)) != null ? _ref1.pathname.toLowerCase() : void 0) || ""; + if (url.match(/\.js|\.css/)) { + for (_k = 0, _len2 = packages.length; _k < _len2; _k++) { + pkg = packages[_k]; + if (url === pkg.url) { + str = pkg.compile(!!server.DEBUG); + res.charset = 'utf-8'; + res.setHeader('Content-Type', pkg.contentType); + res.setHeader('Content-Length', Buffer.byteLength(str)); + res.end((req.method === 'HEAD' && null) || str); + return; + } + } + } + return next(); + }; + }; + + createRoutingProxy = function(options) { + var proxy; + + if (options == null) { + options = {}; + } + proxy = new httpProxy.RoutingProxy(); + options.hostPath || (options.hostPath = ""); + options.port || (options.port = 80); + options.patchRedirect || (options.patchRedirect = true); + if (options.patchRedirect) { + proxy.once("start", function(req, res) { + var returnHost; + + returnHost = req.headers.host; + return patchServerResponseForRedirects(options.host, returnHost); + }); + } + return function(req, res, next) { + req.url = "" + options.hostPath + req.url; + return proxy.proxyRequest(req, res, options); + }; + }; + + patchServerResponseForRedirects = function(fromHost, returnHost) { + var writeHead; + + writeHead = http.ServerResponse.prototype.writeHead; + return http.ServerResponse.prototype.writeHead = function(status) { + var headers, newLocation, oldLocation; + + if (status === 301 || status === 302) { + headers = this._headers; + oldLocation = new RegExp(":\/\/" + fromHost + ":?[0-9]*"); + newLocation = "://" + returnHost; + headers.location = headers.location.replace(oldLocation, newLocation); + } + return writeHead.apply(this, arguments); + }; + }; + + determineUrlFromRoutes = function(pkg, routes) { + var bestMatch, dir, route, url, _i, _len; + + bestMatch = {}; + for (_i = 0, _len = routes.length; _i < _len; _i++) { + route = routes[_i]; + url = Object.keys(route); + dir = route[url]; + if (pkg.target.indexOf(dir) === 0 && (!bestMatch.url || bestMatch.dir.length < dir.length)) { + bestMatch.url = url + pkg.target.slice(dir.length); + bestMatch.dir = dir; + } + } + return bestMatch.url.toLowerCase(); + }; + + module.exports = server; + +}).call(this); diff --git a/src/hem.coffee b/src/hem.coffee index 87ac3c3..9b32ab6 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,10 +1,8 @@ path = require('path') fs = require('fs') optimist = require('optimist') -connect = require('connect') -httpProxy = require('http-proxy') -http = require('http') compilers = require('./compilers') +server = require('./server') versions = require('./versioning') Package = require('./package') @@ -33,8 +31,8 @@ argv.command = argv._[0] argv.targets = argv._[1..] # set compilers debug mode -compilers.DEBUG = !!argv.debug -compilers.VERBOSE = !!argv.v +compilers.DEBUG = server.DEBUG = !!argv.debug +compilers.VERBOSE = server.VERBOSE = !!argv.v # ------- Global Functions @@ -46,87 +44,68 @@ help = -> # ------- Hem Class class Hem + @exec: (command, options) -> (new @(options)).exec(command) @include: (props) -> @::[key] = value for key, value of props - compilers: compilers + @middleware: (slugFile) -> + hem = new Hem(slugFile) + server.middleware(hem.packages, hem.options.server) - # TODO: include way to handle older slug.json files?? backwards compatible?? - slug: argv.slug or './slug.json' + compilers: compilers + # TODO: have a default options for spine pulled in, make it part of spine.app integration. + # Create a framework interface to pull in, default to spine. This will in turn create the + # slug.json to load in. Make this a separate node_module to pull in? options: + framework: "spine" server: port: 9294 - host: 'localhost' + host: "localhost" errorAndExit: (error) -> console.log "ERROR: #{error}" process.exit(1) constructor: (options = {}) -> - @options[key] = value for key, value of options + # handle slug file + if options is "string" + slug = options + else + slug = argv.slug or './slug.json' + @options[key] = value for key, value of options + + # TODO: add argv to options + # quick check to make sure slug file exists - if fs.existsSync(@slug) - @options[key] = value for key, value of @readSlug() + if fs.existsSync(slug) + @options[key] = value for key, value of @readSlug(slug) else - @errorAndExit "Unable to find #{@slug} file in current directory" + @errorAndExit "Unable to find #{slug} file in current directory" + # if versioning turned on, pass in correct module to config if @options.version @options.version.type or= "package" - @vertype = versions[@options.version.type] - # make sure version.type is valid - if not @vertype + if not (@options.version.module = versions[@options.version.type]) @errorAndExit "Incorrect type value for versioning (#{@options.version.type})" + # allow overrides and set defaults @options.server.port = argv.port if argv.port @options.server.host or= "" - @options.routes or= [] + @options.server.routes or= [] + # setup packages from options/slug @packages = (@createPackage(name, config) for name, config of @options.packages) - # TODO: move server code to server.coffee file! + # ------- Command Functions + server: -> - # create app - app = connect() - - # setup dynamic targets first - for pkg in @packages when not argv.n - # determine url if its not already set - pkg.url or= @determineUrlFromRoutes(pkg) - # exit if pkg.url isn't defined - if not pkg.url - @errorAndExit "Unable to determine url mapping for package: #{pkg.name}" - console.log "Map package '#{pkg.name}' to #{pkg.url}" if argv.v - - # setup middleware to server packages - app.use(@middleware(argv.debug)) - - # setup static routes - for route in @options.routes - url = Object.keys(route)[0] - value = route[url] - if (typeof value is 'string') - # make sure path exists - if not fs.existsSync(value) - @errorAndExit "The folder #{value} does not exist." - console.log "Map directory '#{value}' to #{url}" if argv.v - app.use(url, connect.static(value)) - else if value.host - # setup proxy - console.log "Proxy '#{url}' to #{value.host}:#{value.port}#{value.hostPath}" if argv.v - app.use(url, @createRoutingProxy(value)) - if value.patchRedirect is not false # default to true - @patchServerResponseForRedirects(@options.server.port, value) - else - @errorAndExit "Invalid route configuration for #{url}" - - # start server - http.createServer(app).listen(@options.server.port, @options.server.host) - - clean: () -> + server.start(@packages, @options.server) + + clean: -> targets = argv.targets cleanAll = targets.length is 0 pkg.unlink() for pkg in @packages when pkg.name in targets or cleanAll @@ -136,10 +115,13 @@ class Hem @buildTargets(argv.targets) version: -> - if not @vertype + # TODO: this should be done at the package level, not globally + module = @options.version?.module + files = @options.version?.files + if module and files + module.updateFiles(files, @packages) + else console.error "ERROR: Versioning not enabled in slug.json" - return - @vertype.updateFiles(@options.version.files, @packages) watch: -> targets = argv.targets @@ -166,7 +148,7 @@ class Hem # ------- Private Functions - readSlug: (slug = @slug) -> + readSlug: (slug) -> return {} unless slug and fs.existsSync(slug) JSON.parse(fs.readFileSync(slug, 'utf-8')) @@ -177,26 +159,6 @@ class Hem buildAll = targets.length is 0 pkg.build(not argv.debug) for pkg in @packages when pkg.name in targets or buildAll - createRoutingProxy: (options = {}) -> - proxy = new httpProxy.RoutingProxy() - # additional options - options.hostPath or= "" - options.port or= 80 - # return function used by connect to access proxy - return (req, res, next) -> - req.url = "#{options.hostPath}#{req.url}" - proxy.proxyRequest(req, res, options) - - patchServerResponseForRedirects: (port, config) -> - writeHead = http.ServerResponse.prototype.writeHead - http.ServerResponse.prototype.writeHead = (status) -> - if status in [301,302] - headers = @_headers - oldLocation = new RegExp(":\/\/#{config.host}:?[0-9]*") - newLocation = "://localhost:#{port}" - headers.location = headers.location.replace(oldLocation,newLocation) - return writeHead.apply(this, arguments) - startTestacular: (targets = [], singleRun = true) -> # use custom testacular config file provided by user testConfig = fs.existsSync(argv.test) and fs.realpathSync(argv.test) @@ -226,39 +188,5 @@ class Hem fileList.push pkg.target for pkg in @packages when pkg.isJavascript() return fileList - middleware: (debug) => - (req, res, next) => - # only deal with js/css files - url = require("url").parse(req.url)?.pathname.toLowerCase() or "" - if not url.match(/\.js|\.css/) - next() - return - # strip out any potential versioning - if @vertype - url = @vertype.trimVersion(url) - # loop over pkgs and call compile - for pkg in @packages - if url is pkg.url - # TODO: keep (and return) in memory build if there hasn't been any changes?? - str = pkg.compile(not debug) - res.charset = 'utf-8' - res.setHeader('Content-Type', pkg.contentType) - res.setHeader('Content-Length', Buffer.byteLength(str)) - res.end((req.method is 'HEAD' and null) or str) - return - # no matches, go to next middleware - next() - - determineUrlFromRoutes: (pkg) -> - bestMatch = {} - for route in @options.routes - url = Object.keys(route) - dir = route[url] - # compare against package target - if pkg.target.indexOf(dir) == 0 and (!bestMatch.url or bestMatch.dir.length < dir.length) - bestMatch.url = url + pkg.target.slice(dir.length) - bestMatch.dir = dir - bestMatch.url.toLowerCase() - module.exports = Hem diff --git a/src/server.coffee b/src/server.coffee new file mode 100644 index 0000000..08af08a --- /dev/null +++ b/src/server.coffee @@ -0,0 +1,111 @@ +connect = require('connect') +httpProxy = require('http-proxy') +http = require('http') +fs = require('fs') +server = {} + +# ------- Public Functions + +server.start = (packages, options) -> + app = connect() + app.use(server.middleware(app, packages, options)) + http.createServer(app).listen(options.port, options.host) + +# eventually just pass in routes and server options +server.middleware = (app, packages, options) -> + + # TODO: flag to not make the files dynamically compiled, skip mapping + # setup dynamic targets first + for pkg in packages + # determine url if its not already set + pkg.url or= determineUrlFromRoutes(pkg, options.routes) + # exit if pkg.url isn't defined + if not pkg.url + throw new Error "Unable to determine url mapping for package: #{pkg.name}" + console.log "Map package '#{pkg.name}' to #{pkg.url}" if !!server.VERBOSE + + # setup static and proxy middleware + for route in options.routes + url = Object.keys(route)[0] + value = route[url] + # setup static route + if (typeof value is 'string') + if fs.existsSync(value) + console.log "Map directory '#{value}' to #{url}" if !!server.VERBOSE + app.use(url, connect.static(value)) + else + console.log "ERROR: The folder #{value} does not exist." + process.exit(1) + # setup proxy route + else if value.host + console.log "Proxy requests from #{url} to #{value.host}" if !!server.VERBOSE + app.use(url, createRoutingProxy(value)) + else + throw new Error("Invalid route configuration for #{url}") + + # return the custom middleware for connect to use + return (req, res, next) -> + + # get url path + url = require("url").parse(req.url)?.pathname.toLowerCase() or "" + + # strip out any potential versioning + # if @vertype + # url = @vertype.trimVersion(url) + + # loop over pkgs and call compile when there is a match + if url.match(/\.js|\.css/) + for pkg in packages + # TODO: get non versioned url here?? + if url is pkg.url + # TODO: keep (and return) in memory build if there hasn't been any changes?? + str = pkg.compile(!!server.DEBUG) + res.charset = 'utf-8' + res.setHeader('Content-Type', pkg.contentType) + res.setHeader('Content-Length', Buffer.byteLength(str)) + res.end((req.method is 'HEAD' and null) or str) + return + # continue to next middleware + next() + +# ------- Private Functions + +createRoutingProxy = (options = {}) -> + proxy = new httpProxy.RoutingProxy() + # additional options + options.hostPath or= "" + options.port or= 80 + options.patchRedirect or= true + # handle redirects + if options.patchRedirect + proxy.once "start", (req, res) -> + returnHost = req.headers.host + patchServerResponseForRedirects(options.host, returnHost) + # return function used by connect to access proxy + return (req, res, next) -> + req.url = "#{options.hostPath}#{req.url}" + proxy.proxyRequest(req, res, options) + +patchServerResponseForRedirects = (fromHost, returnHost) -> + writeHead = http.ServerResponse.prototype.writeHead + http.ServerResponse.prototype.writeHead = (status) -> + if status in [301,302] + headers = @_headers + oldLocation = new RegExp(":\/\/#{fromHost}:?[0-9]*") + newLocation = "://#{returnHost}" + headers.location = headers.location.replace(oldLocation,newLocation) + return writeHead.apply(@, arguments) + +determineUrlFromRoutes = (pkg, routes) -> + bestMatch = {} + for route in routes + url = Object.keys(route) + dir = route[url] + # compare against package target + if pkg.target.indexOf(dir) == 0 and (!bestMatch.url or bestMatch.dir.length < dir.length) + bestMatch.url = url + pkg.target.slice(dir.length) + bestMatch.dir = dir + bestMatch.url.toLowerCase() + +# export the start function +module.exports = server From a374c5afdfa104a1786f75d391a4ec994b784a7b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 5 Jun 2013 17:27:16 -0500 Subject: [PATCH 017/167] some more major refactoring, but work still to be done --- assets/frameworks/spine.json | 27 +++++ lib/hem.js | 83 +++++++------ lib/package.js | 225 +++++++++++++++++++++-------------- lib/server.js | 33 ++--- lib/test.js | 45 +++++++ src/hem.coffee | 56 +++------ src/package.coffee | 168 ++++++++++++++++---------- src/server.coffee | 36 ++---- src/test.coffee | 41 +++++++ 9 files changed, 433 insertions(+), 281 deletions(-) create mode 100644 assets/frameworks/spine.json create mode 100644 lib/test.js create mode 100644 src/test.coffee diff --git a/assets/frameworks/spine.json b/assets/frameworks/spine.json new file mode 100644 index 0000000..2c1fa30 --- /dev/null +++ b/assets/frameworks/spine.json @@ -0,0 +1,27 @@ +{ + "root": "./", + "route": "/", + "public": "public", + "js": { + "paths": ["app"], + "libs": ["lib"], + "identifier": "require", + "modules": [], + "target": "application.js", + "url": "/" + }, + "css": { + "paths": ["css"], + "target": "application.css", + "url": "/" + }, + "test": { + // autogenerate the index.html file (store in assets) + "paths": ["test/specs"], + "public": "test/public", + "target": "specs.js", + "after": "alert('!')", + "url": "/test" + } +} + diff --git a/lib/hem.js b/lib/hem.js index 7a4f2d4..30cfe4d 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Hem, Package, argv, compilers, fs, help, optimist, path, server, versions, + var Hem, Package, argv, compilers, fs, help, optimist, path, server, testing, versions, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; path = require('path'); @@ -17,6 +17,8 @@ Package = require('./package'); + testing = require('./test'); + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('d', 'debug').describe('d', ':all compilations use debug mode').alias('t', 'test').describe('t', ':run testacular while using watch').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('b', 'browser').describe('b', ':run testacular using the supplied browser[s]').alias('n', 'noBuild').describe('n', ':turn off dynamic builds during server mode').describe('v', ':make hem more talkative(verbose)').argv; argv.command = argv._[0]; @@ -74,7 +76,7 @@ }; function Hem(options) { - var config, key, name, slug, value, _base, _base1, _base2, _ref; + var config, key, name, slug, slugDir, value, _base, _base1, _base2, _ref; if (options == null) { options = {}; @@ -94,6 +96,8 @@ value = _ref[key]; this.options[key] = value; } + slugDir = path.dirname(path.resolve(process.cwd() + "/" + slug)); + process.chdir(slugDir); } else { this.errorAndExit("Unable to find " + slug + " file in current directory"); } @@ -115,7 +119,7 @@ _results = []; for (name in _ref1) { config = _ref1[name]; - _results.push(this.createPackage(name, config)); + _results.push(Package.createPackage(name, config)); } return _results; }).call(this); @@ -162,9 +166,11 @@ var pkg, targets, watchAll, _i, _len, _ref, _ref1, _results; targets = argv.targets; - this.buildTargets(targets); + this.buildPackages(targets); if (argv.test) { - this.startTestacular(targets, false); + this.testTargets(targets, { + singleRun: false + }); } watchAll = targets.length === 0; _ref = this.packages; @@ -180,7 +186,7 @@ Hem.prototype.test = function() { this.buildTargets(argv.targets); - return this.startTestacular(argv.targets); + return this.testTargets(argv.targets); }; Hem.prototype.exec = function(command) { @@ -216,63 +222,62 @@ return JSON.parse(fs.readFileSync(slug, 'utf-8')); }; - Hem.prototype.createPackage = function(name, config) { - var pkg; - - return pkg = new Package(name, config, argv); - }; - - Hem.prototype.buildTargets = function(targets) { - var buildAll, pkg, _i, _len, _ref, _ref1, _results; + Hem.prototype.getTargetPackages = function(targets) { + var pkg, targetAll, _i, _len, _ref, _ref1, _results; if (targets == null) { targets = []; } - buildAll = targets.length === 0; + targetAll = targets.length === 0; _ref = this.packages; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; - if ((_ref1 = pkg.name, __indexOf.call(targets, _ref1) >= 0) || buildAll) { - _results.push(pkg.build(!argv.debug)); + if ((_ref1 = pkg.name, __indexOf.call(targets, _ref1) >= 0) || targetAll) { + _results.push(pkg); } } return _results; }; - Hem.prototype.startTestacular = function(targets, singleRun) { - var testConfig; + Hem.prototype.testTargets = function(targets, options) { + var pkg, testPackages; if (targets == null) { targets = []; } - if (singleRun == null) { - singleRun = true; + if (options == null) { + options = {}; } - testConfig = fs.existsSync(argv.test) && fs.realpathSync(argv.test); - testConfig || (testConfig = { - configFile: require.resolve("../assets/testacular.conf.js"), - singleRun: singleRun, - basePath: process.cwd(), - logLevel: 'error', - browsers: argv.browser && argv.browser.split(/[ ,]+/) || ['PhantomJS'], - files: this.createTestacularFileList() - }); - return require('karma').server.start(testConfig); + testPackages = (function() { + var _i, _len, _ref, _ref1, _results; + + _ref = this.getTargetPackages(targets); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; + if ((_ref1 = pkg.target) != null ? _ref1.test(/\.js$/i) : void 0) { + _results.push(pkg); + } + } + return _results; + }).call(this); + return testing.run(testPackages, singlRun); }; - Hem.prototype.createTestacularFileList = function() { - var fileList, pkg, _i, _len, _ref; + Hem.prototype.buildTargets = function(targets) { + var pkg, _i, _len, _ref, _results; - fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), require.resolve("../node_modules/karma/adapter/jasmine.js")]; - _ref = this.packages; + if (targets == null) { + targets = []; + } + _ref = this.getTargetPackages(targets); + _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; - if (pkg.isJavascript()) { - fileList.push(pkg.target); - } + _results.push(pkg.build(!argv.debug)); } - return fileList; + return _results; }; return Hem; diff --git a/lib/package.js b/lib/package.js index 8922c35..30d7b15 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,8 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Dependency, Package, Stitch, fs, mime, path, stitchFile, toArray, uglify; + var CssPackage, Dependency, JsPackage, Package, Stitch, createPackage, cssFile, fs, jsFile, path, stitchFile, toArray, uglify, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; fs = require('fs'); @@ -16,101 +18,44 @@ toArray = require('./utils').toArray; - mime = require('connect')["static"].mime; + jsFile = /\.js$/i; + + cssFile = /\.css$/i; + + createPackage = function(name, config) { + if (jsFile.test(config.target)) { + return new JsPackage(name, config); + } else if (cssFile.test(config.target)) { + return new CssPackage(name, config); + } else { + throw new Error("Unsupported package type."); + } + }; Package = (function() { - function Package(name, config, argv) { + function Package(name, config) { if (config == null) { config = {}; } - if (argv == null) { - argv = {}; - } this.name = name; - this.argv = argv; - this.identifier = config.identifier; - this.target = config.target; - this.libs = toArray(config.libs || []); + this.target = config.target || (function() { + throw new Error("Missing target for " + name); + })(); + this.root = "./"; + this["public"] = "public"; this.paths = toArray(config.paths || []); + this.test = config.test; + this.url = config.url || "/" + target; + this["static"] = config["static"] || { + "/": "" + root + "/public" + }; + this.version = config.version; + this.identifier = config.identifier || 'require'; + this.libs = toArray(config.libs || []); this.modules = toArray(config.modules || []); - this.jsAfter = config.jsAfter || ""; - this.url = config.url; - this.contentType = mime.lookup(this.target); - if (this.isJavascript()) { - this.compile = this.compileJavascript; - } else { - this.compile = this.compileCss; - } + this.after = config.after || ""; } - Package.prototype.compileModules = function() { - var _modules, _stitch; - - this.depend || (this.depend = new Dependency(this.modules)); - _stitch = new Stitch(this.paths); - _modules = this.depend.resolve().concat(_stitch.resolve()); - return stitchFile({ - identifier: this.identifier, - modules: _modules - }); - }; - - Package.prototype.compileLibs = function() { - var lib; - - return ((function() { - var _i, _len, _ref, _results; - - _ref = this.libs; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - lib = _ref[_i]; - _results.push(fs.readFileSync(lib, 'utf8')); - } - return _results; - }).call(this)).join("\n"); - }; - - Package.prototype.compileJavascript = function(minify) { - var ex, result; - - if (minify == null) { - minify = false; - } - try { - result = [this.compileLibs(), this.compileModules(), this.jsAfter].join("\n"); - if (minify) { - result = uglify(result); - } - return result; - } catch (_error) { - ex = _error; - return this.handleCompileError(ex); - } - }; - - Package.prototype.compileCss = function(minify) { - var ex, result, _i, _len, _path, _ref; - - if (minify == null) { - minify = false; - } - try { - result = []; - _ref = this.paths; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - _path = _ref[_i]; - _path = require.resolve(path.resolve(_path)); - delete require.cache[_path]; - result.push(require(_path)); - } - return result.join("\n"); - } catch (_error) { - ex = _error; - return this.handleCompileError(ex); - } - }; - Package.prototype.handleCompileError = function(ex) { console.error(ex.message); if (ex.path) { @@ -129,10 +74,6 @@ } }; - Package.prototype.isJavascript = function() { - return this.contentType === "application/javascript"; - }; - Package.prototype.unlink = function() { if (fs.existsSync(this.target)) { return fs.unlinkSync(this.target); @@ -186,10 +127,112 @@ return _results; }; + Package.prototype.canTest = function() { + return jsFile.test(target); + }; + + Package.prototype.isMatchingUrl = function(url) {}; + return Package; })(); - module.exports = Package; + JsPackage = (function(_super) { + __extends(JsPackage, _super); + + function JsPackage(name, config) { + if (config == null) { + config = {}; + } + JsPackage.__super__.constructor.call(this, name, config); + } + + JsPackage.prototype.compile = function(minify) { + var ex, result; + + if (minify == null) { + minify = false; + } + try { + result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); + if (minify) { + result = uglify(result); + } + return result; + } catch (_error) { + ex = _error; + return this.handleCompileError(ex); + } + }; + + JsPackage.prototype.compileModules = function() { + var _modules, _stitch; + + this.depend || (this.depend = new Dependency(this.modules)); + _stitch = new Stitch(this.paths); + _modules = this.depend.resolve().concat(_stitch.resolve()); + return stitchFile({ + identifier: this.identifier, + modules: _modules + }); + }; + + JsPackage.prototype.compileLibs = function() { + var lib; + + return ((function() { + var _i, _len, _ref, _results; + + _ref = this.libs; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + lib = _ref[_i]; + _results.push(fs.readFileSync(lib, 'utf8')); + } + return _results; + }).call(this)).join("\n"); + }; + + return JsPackage; + + })(Package); + + CssPackage = (function(_super) { + __extends(CssPackage, _super); + + function CssPackage(name, config) { + if (config == null) { + config = {}; + } + CssPackage.__super__.constructor.call(this, name, config); + } + + CssPackage.prototype.compile = function(minify) { + var ex, result, _i, _len, _path, _ref; + + if (minify == null) { + minify = false; + } + try { + result = []; + _ref = this.paths; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + _path = _ref[_i]; + _path = require.resolve(path.resolve(_path)); + delete require.cache[_path]; + result.push(require(_path)); + } + return result.join("\n"); + } catch (_error) { + ex = _error; + return this.handleCompileError(ex); + } + }; + + return CssPackage; + + })(Package); + + module.exports.createPackage = createPackage; }).call(this); diff --git a/lib/server.js b/lib/server.js index cb63598..ab5a6a9 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,9 +1,11 @@ // Generated by CoffeeScript 1.6.2 (function() { - var connect, createRoutingProxy, determineUrlFromRoutes, fs, http, httpProxy, patchServerResponseForRedirects, server; + var connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server; connect = require('connect'); + mime = require('connect')["static"].mime; + httpProxy = require('http-proxy'); http = require('http'); @@ -25,13 +27,12 @@ for (_i = 0, _len = packages.length; _i < _len; _i++) { pkg = packages[_i]; - pkg.url || (pkg.url = determineUrlFromRoutes(pkg, options.routes)); - if (!pkg.url) { - throw new Error("Unable to determine url mapping for package: " + pkg.name); - } - if (!!server.VERBOSE) { + if (pkg.url && !!server.VERBOSE) { console.log("Map package '" + pkg.name + "' to " + pkg.url); } + if (pkg["static"]) { + options.routes.push(pkg["static"]); + } } _ref = options.routes; for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { @@ -64,10 +65,10 @@ if (url.match(/\.js|\.css/)) { for (_k = 0, _len2 = packages.length; _k < _len2; _k++) { pkg = packages[_k]; - if (url === pkg.url) { + if (pkg.isMatchingUrl(url)) { str = pkg.compile(!!server.DEBUG); res.charset = 'utf-8'; - res.setHeader('Content-Type', pkg.contentType); + res.setHeader('Content-Type', mime.lookup(pkg.target)); res.setHeader('Content-Length', Buffer.byteLength(str)); res.end((req.method === 'HEAD' && null) || str); return; @@ -119,22 +120,6 @@ }; }; - determineUrlFromRoutes = function(pkg, routes) { - var bestMatch, dir, route, url, _i, _len; - - bestMatch = {}; - for (_i = 0, _len = routes.length; _i < _len; _i++) { - route = routes[_i]; - url = Object.keys(route); - dir = route[url]; - if (pkg.target.indexOf(dir) === 0 && (!bestMatch.url || bestMatch.dir.length < dir.length)) { - bestMatch.url = url + pkg.target.slice(dir.length); - bestMatch.dir = dir; - } - } - return bestMatch.url.toLowerCase(); - }; - module.exports = server; }).call(this); diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..f29538a --- /dev/null +++ b/lib/test.js @@ -0,0 +1,45 @@ +// Generated by CoffeeScript 1.6.2 +(function() { + var createKarmaFileList, fs, start, startKarma; + + fs = require('fs'); + + start = function(packages, options) { + if (options == null) { + options = {}; + } + return startKarma(packages, singleRun); + }; + + startKarma = function(packages, options) { + var testConfig; + + if (options == null) { + options = {}; + } + testConfig = fs.existsSync(options.config) && fs.realpathSync(options.config); + testConfig || (testConfig = { + configFile: require.resolve("../assets/testacular.conf.js"), + singleRun: options.singleRun || true, + basePath: process.cwd(), + logLevel: 'error', + browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], + files: createKarmaFileList(packages) + }); + return require('karma').server.start(testConfig); + }; + + createKarmaFileList = function(packages) { + var fileList, pkg, _i, _len; + + fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), require.resolve("../node_modules/karma/adapter/jasmine.js")]; + for (_i = 0, _len = packages.length; _i < _len; _i++) { + pkg = packages[_i]; + fileList.push(pkg.target); + } + return fileList; + }; + + module.exports.start = start; + +}).call(this); diff --git a/src/hem.coffee b/src/hem.coffee index 9b32ab6..10d8b43 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -5,6 +5,7 @@ compilers = require('./compilers') server = require('./server') versions = require('./versioning') Package = require('./package') +testing = require('./test') # ------- Commandline arguments @@ -78,11 +79,12 @@ class Hem slug = argv.slug or './slug.json' @options[key] = value for key, value of options - # TODO: add argv to options - # quick check to make sure slug file exists if fs.existsSync(slug) @options[key] = value for key, value of @readSlug(slug) + # make sure we are in same directory as slug + slugDir = path.dirname(path.resolve(process.cwd() + "/" + slug)) + process.chdir(slugDir) else @errorAndExit "Unable to find #{slug} file in current directory" @@ -98,7 +100,7 @@ class Hem @options.server.routes or= [] # setup packages from options/slug - @packages = (@createPackage(name, config) for name, config of @options.packages) + @packages = (Package.createPackage(name, config) for name, config of @options.packages) # ------- Command Functions @@ -125,16 +127,16 @@ class Hem watch: -> targets = argv.targets - @buildTargets(targets) + @buildPackages(targets) # also run testacular tests if -t is passed in the command line - @startTestacular(targets, false) if argv.test + @testTargets(targets, singleRun: false) if argv.test # begin watching package targets watchAll = targets.length is 0 pkg.watch() for pkg in @packages when pkg.name in targets or watchAll test: -> @buildTargets(argv.targets) - @startTestacular(argv.targets) + @testTargets(argv.targets) exec: (command = argv.command) -> return help() unless @[command] @@ -152,41 +154,17 @@ class Hem return {} unless slug and fs.existsSync(slug) JSON.parse(fs.readFileSync(slug, 'utf-8')) - createPackage: (name, config) -> - pkg = new Package(name, config, argv) + getTargetPackages: (targets = []) -> + targetAll = targets.length is 0 + (pkg for pkg in @packages when pkg.name in targets or targetAll) + + testTargets: (targets = [], options = {}) -> + testPackages = (pkg for pkg in @getTargetPackages(targets) when pkg.target.canTest() + testing.run(testPackages, singlRun) buildTargets: (targets = []) -> - buildAll = targets.length is 0 - pkg.build(not argv.debug) for pkg in @packages when pkg.name in targets or buildAll - - startTestacular: (targets = [], singleRun = true) -> - # use custom testacular config file provided by user - testConfig = fs.existsSync(argv.test) and fs.realpathSync(argv.test) - - # create config file to pass into server if user doesn't supply a file to use - testConfig or= - configFile : require.resolve("../assets/testacular.conf.js") - singleRun : singleRun - basePath : process.cwd() - logLevel : 'error' - browsers : argv.browser and argv.browser.split(/[ ,]+/) or ['PhantomJS'] - files : @createTestacularFileList() - - # start testacular server - require('karma').server.start(testConfig) - - createTestacularFileList: () -> - # look at at test type to see what assets we add - fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), - require.resolve("../node_modules/karma/adapter/jasmine.js")] - # TODO: would we ever need a way to specificy only certain files to test?? - # Perhaps a special package type that just lists a group of packages to build/test?? - # "testGroup" : [ "spine", "test" ], then check typeof array to use this group?? Would need a - # new function to create the targets array used by the build/watch/clean methods... - # - # loop over javascript packages and add their targets - fileList.push pkg.target for pkg in @packages when pkg.isJavascript() - return fileList + pkg.build(not argv.debug) for pkg in @getTargetPackages(targets) + module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index 2dfcabe..0e8c0e6 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -1,67 +1,59 @@ -fs = require('fs') -path = require('path') -uglify = require('uglify-js') -stitchFile = require('../assets/stitch') -Dependency = require('./dependency') -Stitch = require('./stitch') -{toArray} = require('./utils') -mime = require('connect').static.mime +fs = require('fs') +path = require('path') +uglify = require('uglify-js') +stitchFile = require('../assets/stitch') +Dependency = require('./dependency') +Stitch = require('./stitch') +{toArray} = require('./utils') + +# ------- Public Functions + +jsFile = /\.js$/i +cssFile = /\.css$/i + +createPackage = (name, config) -> + if jsFile.test config.target + # TODO: at some point merge in framework defaults (ex. spine) + return new JsPackage(name,config) + else if cssFile.test config.target + # TODO: framework defaults for css + return new CssPackage(name, config) + # TODO: setup target for package that contains other packages? + else + throw new Error("Unsupported package type.") + +# ------- Classes class Package - constructor: (name, config = {}, argv = {}) -> - @name = name - @argv = argv - - # set config values - @identifier = config.identifier - @target = config.target - @libs = toArray(config.libs || []) - @paths = toArray(config.paths || []) - @modules = toArray(config.modules || []) - @jsAfter = config.jsAfter or "" - @url = config.url - # TODO: sanity checkes on config values?? - - # determine content type based on target file name - @contentType = mime.lookup(@target) - - # set correct compiler based on mime type, set @compile = javascriptCompiler - if @isJavascript() - @compile = @compileJavascript - else - @compile = @compileCss + constructor: (name, config = {}) -> + @name = name + @target = config.target or throw new Error("Missing target for #{name}") + @root = "./" + @public = "public" + @paths = toArray(config.paths or []) + @test = config.test - compileModules: -> - @depend or= new Dependency(@modules) - _stitch = new Stitch(@paths) - _modules = @depend.resolve().concat(_stitch.resolve()) - stitchFile(identifier: @identifier, modules: _modules) + # determine url + @url = config.url or "/" + target - compileLibs: -> - # TODO: be able to handle being given a folder and loading each file...can this compile coffeescript?? - (fs.readFileSync(lib, 'utf8') for lib in @libs).join("\n") + # determine static folder + @static = config.static or + "/" : "#{root}/public" - compileJavascript: (minify = false) -> - try - result = [@compileLibs(), @compileModules(), @jsAfter].join("\n") - result = uglify(result) if minify - result - catch ex - @handleCompileError(ex) + # handle versioning + @version = config.version - compileCss: (minify = false) -> - try - result = [] - for _path in @paths - # TODO: currently this only works with index files, perhaps someday loop over the directory - # contents and pickup the other files?? though with stylus can always get other content by mixins - _path = require.resolve(path.resolve(_path)) - delete require.cache[_path] - result.push require(_path) - # TODO: do we want a minify option for css or is that built into the compilers?? - result.join("\n") - catch ex - @handleCompileError(ex) + # TODO: provide framework file to that will supply defaults + # - merge that with options provided? put merge in utils.coffee copy from connect + # - if folder starts with ./ then start from slug otherwise use defaults + # - provide root/context value to set starting point + # - call framework options by name of framework, hem spine new healthlink + + # javascript only configurations + @identifier = config.identifier or 'require' + @libs = toArray(config.libs or []) + @modules = toArray(config.modules or []) + @after = config.after or "" handleCompileError: (ex) -> console.error ex.message @@ -73,9 +65,6 @@ class Package when "watch" then return "" else process.exit(1) - isJavascript: -> - @contentType is "application/javascript" - unlink: -> fs.unlinkSync(@target) if fs.existsSync(@target) @@ -91,4 +80,59 @@ class Package require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => @build() if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) -module.exports = Package + canTest: -> + # eventually see if there is /test folder + return jsFile.test target + + isMatchingUrl: (url) -> + # TODO: strp out any versioning + + +# ------- Child Classes + +class JsPackage extends Package + + constructor: (name, config = {}) -> + super(name, config) + + compile: (minify = false) -> + try + result = [@compileLibs(), @compileModules(), @after].join("\n") + result = uglify(result) if minify + result + catch ex + @handleCompileError(ex) + + compileModules: -> + @depend or= new Dependency(@modules) + _stitch = new Stitch(@paths) + # TODO use detective....?? + _modules = @depend.resolve().concat(_stitch.resolve()) + stitchFile(identifier: @identifier, modules: _modules) + + compileLibs: -> + # TODO: check if lib is a folder, then pull in everything + (fs.readFileSync(lib, 'utf8') for lib in @libs).join("\n") + +class CssPackage extends Package + + constructor: (name, config = {}) -> + super(name, config) + + compile: (minify = false) -> + try + result = [] + for _path in @paths + # TODO: currently this only works with index files, perhaps someday loop over the directory + # contents and pickup the other files?? though with stylus can always get other content by mixins + _path = require.resolve(path.resolve(_path)) + delete require.cache[_path] + result.push require(_path) + # TODO: do we want a minify option for css or is that built into the compilers?? + result.join("\n") + catch ex + @handleCompileError(ex) + +module.exports.createPackage = createPackage + + diff --git a/src/server.coffee b/src/server.coffee index 08af08a..01353a6 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -1,4 +1,5 @@ connect = require('connect') +mime = require('connect').static.mime httpProxy = require('http-proxy') http = require('http') fs = require('fs') @@ -14,15 +15,12 @@ server.start = (packages, options) -> # eventually just pass in routes and server options server.middleware = (app, packages, options) -> - # TODO: flag to not make the files dynamically compiled, skip mapping - # setup dynamic targets first + # determine if there is any static routes to add for pkg in packages - # determine url if its not already set - pkg.url or= determineUrlFromRoutes(pkg, options.routes) - # exit if pkg.url isn't defined - if not pkg.url - throw new Error "Unable to determine url mapping for package: #{pkg.name}" - console.log "Map package '#{pkg.name}' to #{pkg.url}" if !!server.VERBOSE + if pkg.url and !!server.VERBOSE + console.log "Map package '#{pkg.name}' to #{pkg.url}" + if pkg.static + options.routes.push pkg.static # setup static and proxy middleware for route in options.routes @@ -49,19 +47,15 @@ server.middleware = (app, packages, options) -> # get url path url = require("url").parse(req.url)?.pathname.toLowerCase() or "" - # strip out any potential versioning - # if @vertype - # url = @vertype.trimVersion(url) - # loop over pkgs and call compile when there is a match if url.match(/\.js|\.css/) for pkg in packages # TODO: get non versioned url here?? - if url is pkg.url + if pkg.isMatchingUrl(url) # TODO: keep (and return) in memory build if there hasn't been any changes?? str = pkg.compile(!!server.DEBUG) res.charset = 'utf-8' - res.setHeader('Content-Type', pkg.contentType) + res.setHeader('Content-Type', mime.lookup(pkg.target)) res.setHeader('Content-Length', Buffer.byteLength(str)) res.end((req.method is 'HEAD' and null) or str) return @@ -79,6 +73,7 @@ createRoutingProxy = (options = {}) -> # handle redirects if options.patchRedirect proxy.once "start", (req, res) -> + # get the requesting hostname and port returnHost = req.headers.host patchServerResponseForRedirects(options.host, returnHost) # return function used by connect to access proxy @@ -96,16 +91,5 @@ patchServerResponseForRedirects = (fromHost, returnHost) -> headers.location = headers.location.replace(oldLocation,newLocation) return writeHead.apply(@, arguments) -determineUrlFromRoutes = (pkg, routes) -> - bestMatch = {} - for route in routes - url = Object.keys(route) - dir = route[url] - # compare against package target - if pkg.target.indexOf(dir) == 0 and (!bestMatch.url or bestMatch.dir.length < dir.length) - bestMatch.url = url + pkg.target.slice(dir.length) - bestMatch.dir = dir - bestMatch.url.toLowerCase() - -# export the start function +# export the public functions module.exports = server diff --git a/src/test.coffee b/src/test.coffee new file mode 100644 index 0000000..4955ab0 --- /dev/null +++ b/src/test.coffee @@ -0,0 +1,41 @@ +fs = require('fs') + +# ------- Public Functions + +start = (packages, options = {}) -> + # TODO: determine whether to use phantomjs or karma for testing + startKarma(packages, singleRun) + +# ------- Test Functions + +startKarma = (packages, options = {}) -> + # use custom testacular config file provided by user + testConfig = fs.existsSync(options.config) and fs.realpathSync(options.config) + + # create config file to pass into server if user doesn't supply a file to use + testConfig or= + configFile : require.resolve("../assets/testacular.conf.js") + singleRun : options.singleRun or true + basePath : process.cwd() + logLevel : 'error' + browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] + files : createKarmaFileList(packages) + + # start testacular server + require('karma').server.start(testConfig) + +createKarmaFileList = (packages) -> + # TODO how to configure this to use other adapters? + + # look at at test type to see what assets we add + fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), + require.resolve("../node_modules/karma/adapter/jasmine.js")] + # loop over javascript packages and add their targets + fileList.push pkg.target for pkg in packages + return fileList + +# ------- Exports + +module.exports.start = start + + From 24031244f60f2ebf0551d4357421b1540fe7172e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 13 Jun 2013 13:17:34 -0500 Subject: [PATCH 018/167] fixing coffee typo --- lib/hem.js | 4 ++-- src/hem.coffee | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 30cfe4d..78607f0 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -250,13 +250,13 @@ options = {}; } testPackages = (function() { - var _i, _len, _ref, _ref1, _results; + var _i, _len, _ref, _results; _ref = this.getTargetPackages(targets); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; - if ((_ref1 = pkg.target) != null ? _ref1.test(/\.js$/i) : void 0) { + if (pkg.target.canTest()) { _results.push(pkg); } } diff --git a/src/hem.coffee b/src/hem.coffee index 10d8b43..e78be82 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -159,7 +159,7 @@ class Hem (pkg for pkg in @packages when pkg.name in targets or targetAll) testTargets: (targets = [], options = {}) -> - testPackages = (pkg for pkg in @getTargetPackages(targets) when pkg.target.canTest() + testPackages = (pkg for pkg in @getTargetPackages(targets) when pkg.target.canTest()) testing.run(testPackages, singlRun) buildTargets: (targets = []) -> From 002abc6de1de3a038975bfbfa8145ba0a8a0e434 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 17 Jun 2013 10:35:42 -0500 Subject: [PATCH 019/167] more refactoring --- assets/frameworks/spine.json | 12 ++++++--- lib/hem.js | 33 +++++++++++++------------ lib/package.js | 4 +-- package.json | 47 +++++++++++++++++++++++++----------- src/compilers.coffee | 1 + src/hem.coffee | 33 +++++++++++++++---------- src/package.coffee | 27 ++++++++++----------- src/server.coffee | 2 +- 8 files changed, 94 insertions(+), 65 deletions(-) diff --git a/assets/frameworks/spine.json b/assets/frameworks/spine.json index 2c1fa30..5fd11e1 100644 --- a/assets/frameworks/spine.json +++ b/assets/frameworks/spine.json @@ -1,19 +1,23 @@ { "root": "./", "route": "/", - "public": "public", + "framework": "spine", + "static": { + "public": "", + "test/public": "test" + }, "js": { "paths": ["app"], "libs": ["lib"], "identifier": "require", "modules": [], "target": "application.js", - "url": "/" + "url": "" }, "css": { "paths": ["css"], "target": "application.css", - "url": "/" + "url": "" }, "test": { // autogenerate the index.html file (store in assets) @@ -21,7 +25,7 @@ "public": "test/public", "target": "specs.js", "after": "alert('!')", - "url": "/test" + "url": "" } } diff --git a/lib/hem.js b/lib/hem.js index 78607f0..8f5677d 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -63,20 +63,16 @@ Hem.prototype.compilers = compilers; Hem.prototype.options = { - framework: "spine", server: { port: 9294, host: "localhost" } }; - Hem.prototype.errorAndExit = function(error) { - console.log("ERROR: " + error); - return process.exit(1); - }; + Hem.prototype.packages = []; function Hem(options) { - var config, key, name, slug, slugDir, value, _base, _base1, _base2, _ref; + var config, key, name, slug, slugDir, value, _base, _base1, _base2, _ref, _ref1; if (options == null) { options = {}; @@ -112,17 +108,14 @@ } (_base1 = this.options.server).host || (_base1.host = ""); (_base2 = this.options.server).routes || (_base2.routes = []); - this.packages = (function() { - var _ref1, _results; - - _ref1 = this.options.packages; - _results = []; - for (name in _ref1) { - config = _ref1[name]; - _results.push(Package.createPackage(name, config)); + _ref1 = this.options.packages; + for (name in _ref1) { + config = _ref1[name]; + if (name === "server") { + continue; } - return _results; - }).call(this); + this.packages.push(Package.createPackage(name, config)); + } } Hem.prototype.server = function() { @@ -209,6 +202,9 @@ case 'clean': console.log('Clean application'); break; + case 'version': + console.log('Version application'); + break; case 'server': console.log("Starting Server at " + this.options.server.host + ":" + this.options.server.port); } @@ -222,6 +218,11 @@ return JSON.parse(fs.readFileSync(slug, 'utf-8')); }; + Hem.prototype.errorAndExit = function(error) { + console.log("ERROR: " + error); + return process.exit(1); + }; + Hem.prototype.getTargetPackages = function(targets) { var pkg, targetAll, _i, _len, _ref, _ref1, _results; diff --git a/lib/package.js b/lib/package.js index 30d7b15..a3438a5 100644 --- a/lib/package.js +++ b/lib/package.js @@ -65,10 +65,8 @@ console.error(ex.location); } switch (this.argv.command) { - case "server": + case "server" || "watch": return "console.log(\"" + ex + "\");"; - case "watch": - return ""; default: return process.exit(1); } diff --git a/package.json b/package.json index 900498e..9876286 100644 --- a/package.json +++ b/package.json @@ -2,32 +2,51 @@ "name": "hem", "version": "0.4.0", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", - "keywords": ["spine", "commonsJS", "coffeescript", "stylus", "jade", "eco", "testacular", "jasmine"], - "contributors": ["maccman", "aeischeid", "cengebretson"], - "licenses": - [{ + "keywords": [ + "spine", + "commonsJS", + "coffeescript", + "stylus", + "jade", + "eco", + "testacular", + "jasmine" + ], + "contributors": [ + "maccman", + "aeischeid", + "cengebretson" + ], + "licenses": [ + { "type": "MIT", "url": "https://github.com/spine/hem/blob/master/LICENSE" - }], + } + ], "repository": { - "type" : "git", + "type": "git", "url": "https://github.com/spine/hem.git" }, - "engine" : [ "node >=0.6.0" ], - "main" : "./lib/hem.js", - "bin": { "hem": "./bin/hem" }, + "engine": [ + "node >=0.6.0" + ], + "main": "./lib/hem.js", + "bin": { + "hem": "./bin/hem" + }, "preferGlobal": true, - "dependencies" : { + "dependencies": { "eco": "1.1.0-rc-3", "uglify-js": "~1.3.3", "fast-detective": "~0.0.2", "optimist": "~0.4.0", - "stylus" : "~0.32.0", - "coffee-script" : "~1.6.0", - "watch" : "~0.7.0", + "stylus": "~0.32.0", + "coffee-script": "~1.6.0", + "watch": "~0.7.0", "connect": "~2.7.0", "http-proxy": "~0.10", "karma": "~0.8.0", - "jade": "~0.30" + "jade": "~0.30", + "node.extend": "~1.0.7" } } diff --git a/src/compilers.coffee b/src/compilers.coffee index d4986c0..7ee3ff4 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -23,6 +23,7 @@ try throw err catch err +# TODO: make eco conditional with try/catch eco = require 'eco' compilers.eco = (path) -> diff --git a/src/hem.coffee b/src/hem.coffee index e78be82..6781ef6 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -56,20 +56,20 @@ class Hem hem = new Hem(slugFile) server.middleware(hem.packages, hem.options.server) + # ------- instance variables + compilers: compilers - # TODO: have a default options for spine pulled in, make it part of spine.app integration. - # Create a framework interface to pull in, default to spine. This will in turn create the - # slug.json to load in. Make this a separate node_module to pull in? + # default values for server options: - framework: "spine" server: port: 9294 host: "localhost" - errorAndExit: (error) -> - console.log "ERROR: #{error}" - process.exit(1) + # emtpy packages list + packages: [] + + # ------- Constructor constructor: (options = {}) -> # handle slug file @@ -100,7 +100,9 @@ class Hem @options.server.routes or= [] # setup packages from options/slug - @packages = (Package.createPackage(name, config) for name, config of @options.packages) + for name, config of @options.packages + continue if name is "server" + @packages.push Package.createPackage(name, config) # ------- Command Functions @@ -141,11 +143,12 @@ class Hem exec: (command = argv.command) -> return help() unless @[command] switch command - when 'build' then console.log 'Build application' - when 'watch' then console.log 'Watching application' - when 'test' then console.log 'Test application' - when 'clean' then console.log 'Clean application' - when 'server' then console.log "Starting Server at #{@options.server.host}:#{@options.server.port}" + when 'build' then console.log 'Build application' + when 'watch' then console.log 'Watching application' + when 'test' then console.log 'Test application' + when 'clean' then console.log 'Clean application' + when 'version' then console.log 'Version application' + when 'server' then console.log "Starting Server at #{@options.server.host}:#{@options.server.port}" @[command]() # ------- Private Functions @@ -154,6 +157,10 @@ class Hem return {} unless slug and fs.existsSync(slug) JSON.parse(fs.readFileSync(slug, 'utf-8')) + errorAndExit: (error) -> + console.log "ERROR: #{error}" + process.exit(1) + getTargetPackages: (targets = []) -> targetAll = targets.length is 0 (pkg for pkg in @packages when pkg.name in targets or targetAll) diff --git a/src/package.coffee b/src/package.coffee index 0e8c0e6..d44230f 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -37,8 +37,7 @@ class Package @url = config.url or "/" + target # determine static folder - @static = config.static or - "/" : "#{root}/public" + @static = config.static or "/" : "#{root}/public" # handle versioning @version = config.version @@ -50,10 +49,10 @@ class Package # - call framework options by name of framework, hem spine new healthlink # javascript only configurations - @identifier = config.identifier or 'require' - @libs = toArray(config.libs or []) - @modules = toArray(config.modules or []) - @after = config.after or "" + @identifier = config.identifier or 'require' + @libs = toArray(config.libs or []) + @modules = toArray(config.modules or []) + @after = config.after or "" handleCompileError: (ex) -> console.error ex.message @@ -61,8 +60,7 @@ class Package console.error ex.location if ex.location # only return when in server/watch mode, otherwise exit switch @argv.command - when "server" then return "console.log(\"#{ex}\");" - when "watch" then return "" + when "server" or "watch" then return "console.log(\"#{ex}\");" else process.exit(1) unlink: -> @@ -78,15 +76,15 @@ class Package for dir in (path.dirname(lib) for lib in @libs).concat @paths continue unless fs.existsSync(dir) require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => - @build() if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) + if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) + @build() canTest: -> # eventually see if there is /test folder return jsFile.test target isMatchingUrl: (url) -> - # TODO: strp out any versioning - + # TODO: strip out any versioning # ------- Child Classes @@ -104,9 +102,9 @@ class JsPackage extends Package @handleCompileError(ex) compileModules: -> + # TODO use detective....?? @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) - # TODO use detective....?? _modules = @depend.resolve().concat(_stitch.resolve()) stitchFile(identifier: @identifier, modules: _modules) @@ -123,8 +121,9 @@ class CssPackage extends Package try result = [] for _path in @paths - # TODO: currently this only works with index files, perhaps someday loop over the directory - # contents and pickup the other files?? though with stylus can always get other content by mixins + # TODO: currently this only works with index files, perhaps someday loop + # over the directory contents and pickup the other files?? though with + # stylus can always get other content by mixins _path = require.resolve(path.resolve(_path)) delete require.cache[_path] result.push require(_path) diff --git a/src/server.coffee b/src/server.coffee index 01353a6..da03df6 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -15,7 +15,7 @@ server.start = (packages, options) -> # eventually just pass in routes and server options server.middleware = (app, packages, options) -> - # determine if there is any static routes to add + # determine if there is any dynamic or static routes to add for pkg in packages if pkg.url and !!server.VERBOSE console.log "Map package '#{pkg.name}' to #{pkg.url}" From 2f01d8d327e619c490b4acbfce15259b5a8fa231 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 17 Jun 2013 19:44:40 -0500 Subject: [PATCH 020/167] more refactoring fun, seems to be building correctly again --- assets/defaults/spine.json | 52 ++++++++ assets/frameworks/spine.json | 31 ----- lib/hem.js | 114 +++++++++--------- lib/package.js | 225 +++++++++++++++++++++++++---------- lib/server.js | 61 +++++----- lib/test.js | 20 ++-- lib/utils.js | 59 ++++++++- package.json | 3 +- src/hem.coffee | 91 +++++++------- src/package.coffee | 173 +++++++++++++++++---------- src/server.coffee | 50 ++++---- src/test.coffee | 25 ++-- src/utils.coffee | 42 ++++++- 13 files changed, 606 insertions(+), 340 deletions(-) create mode 100644 assets/defaults/spine.json delete mode 100644 assets/frameworks/spine.json diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json new file mode 100644 index 0000000..703407b --- /dev/null +++ b/assets/defaults/spine.json @@ -0,0 +1,52 @@ +{ + "root": "", + "route": "/", + + "css": { + "paths": [ + "css" + ], + "target": "application.css" + }, + + "js": { + "identifier": "require", + "libs": [ + "lib" + ], + "after": [ + "after" + ], + "modules": [ + "jqueryify", + "spine", + "spine/lib/local", + "spine/lib/list", + "spine/lib/ajax", + "spine/lib/route", + "spine/lib/manager", + "spine/lib/relation" + ], + "paths": [ + "app" + ], + "target": "public/application.js" + }, + + "static": { + "/": "public", + "/test": "test/public" + }, + + "test": { + "after": "alert('!')", + "identifier": "specs", + "modules": [], + "paths": [ + "test/specs" + ], + "target": "test/public/specs.js", + "type": "jasmine" + } +} + diff --git a/assets/frameworks/spine.json b/assets/frameworks/spine.json deleted file mode 100644 index 5fd11e1..0000000 --- a/assets/frameworks/spine.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "root": "./", - "route": "/", - "framework": "spine", - "static": { - "public": "", - "test/public": "test" - }, - "js": { - "paths": ["app"], - "libs": ["lib"], - "identifier": "require", - "modules": [], - "target": "application.js", - "url": "" - }, - "css": { - "paths": ["css"], - "target": "application.css", - "url": "" - }, - "test": { - // autogenerate the index.html file (store in assets) - "paths": ["test/specs"], - "public": "test/public", - "target": "specs.js", - "after": "alert('!')", - "url": "" - } -} - diff --git a/lib/hem.js b/lib/hem.js index 8f5677d..160a5f4 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Hem, Package, argv, compilers, fs, help, optimist, path, server, testing, versions, + var Hem, application, argv, compilers, fs, help, optimist, path, server, testing, utils, versions, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; path = require('path'); @@ -15,24 +15,30 @@ versions = require('./versioning'); - Package = require('./package'); + application = require('./package'); testing = require('./test'); + utils = require('./utils'); + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('d', 'debug').describe('d', ':all compilations use debug mode').alias('t', 'test').describe('t', ':run testacular while using watch').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('b', 'browser').describe('b', ':run testacular using the supplied browser[s]').alias('n', 'noBuild').describe('n', ':turn off dynamic builds during server mode').describe('v', ':make hem more talkative(verbose)').argv; argv.command = argv._[0]; argv.targets = argv._.slice(1); - compilers.DEBUG = server.DEBUG = !!argv.debug; + utils.ARGV = argv; + + utils.DEBUG = argv.debug = !!argv.debug; - compilers.VERBOSE = server.VERBOSE = !!argv.v; + utils.VERBOSE = argv.v = !!argv.v; + + utils.COMMAND = argv.command; help = function() { var _ref; - console.log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); + utils.log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); optimist.showHelp(); return process.exit(); }; @@ -57,11 +63,13 @@ var hem; hem = new Hem(slugFile); - return server.middleware(hem.packages, hem.options.server); + return server.middleware(hem.apps, hem.options.server); }; Hem.prototype.compilers = compilers; + Hem.prototype.homeDir = ''; + Hem.prototype.options = { server: { port: 9294, @@ -69,10 +77,10 @@ } }; - Hem.prototype.packages = []; + Hem.prototype.apps = []; function Hem(options) { - var config, key, name, slug, slugDir, value, _base, _base1, _base2, _ref, _ref1; + var config, key, name, slug, value, _base, _base1, _base2, _ref, _ref1; if (options == null) { options = {}; @@ -92,15 +100,15 @@ value = _ref[key]; this.options[key] = value; } - slugDir = path.dirname(path.resolve(process.cwd() + "/" + slug)); - process.chdir(slugDir); + this.homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)); + process.chdir(this.homeDir); } else { - this.errorAndExit("Unable to find " + slug + " file in current directory"); + utils.errorAndExit("Unable to find " + slug + " file in current directory"); } if (this.options.version) { (_base = this.options.version).type || (_base.type = "package"); if (!(this.options.version.module = versions[this.options.version.type])) { - this.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); + utils.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); } } if (argv.port) { @@ -108,31 +116,31 @@ } (_base1 = this.options.server).host || (_base1.host = ""); (_base2 = this.options.server).routes || (_base2.routes = []); - _ref1 = this.options.packages; + _ref1 = this.options; for (name in _ref1) { config = _ref1[name]; if (name === "server") { continue; } - this.packages.push(Package.createPackage(name, config)); + this.apps.push(application.createApplication(name, config)); } } Hem.prototype.server = function() { - return server.start(this.packages, this.options.server); + return server.start(this.apps, this.options.server); }; Hem.prototype.clean = function() { - var cleanAll, pkg, targets, _i, _len, _ref, _ref1, _results; + var app, cleanAll, targets, _i, _len, _ref, _ref1, _results; targets = argv.targets; cleanAll = targets.length === 0; - _ref = this.packages; + _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - if ((_ref1 = pkg.name, __indexOf.call(targets, _ref1) >= 0) || cleanAll) { - _results.push(pkg.unlink()); + app = _ref[_i]; + if ((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || cleanAll) { + _results.push(app.unlink()); } } return _results; @@ -149,29 +157,29 @@ module = (_ref = this.options.version) != null ? _ref.module : void 0; files = (_ref1 = this.options.version) != null ? _ref1.files : void 0; if (module && files) { - return module.updateFiles(files, this.packages); + return module.updateFiles(files, this.apps); } else { return console.error("ERROR: Versioning not enabled in slug.json"); } }; Hem.prototype.watch = function() { - var pkg, targets, watchAll, _i, _len, _ref, _ref1, _results; + var app, targets, watchAll, _i, _len, _ref, _ref1, _results; targets = argv.targets; - this.buildPackages(targets); + this.buildApps(targets); if (argv.test) { this.testTargets(targets, { singleRun: false }); } watchAll = targets.length === 0; - _ref = this.packages; + _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - if ((_ref1 = pkg.name, __indexOf.call(targets, _ref1) >= 0) || watchAll) { - _results.push(pkg.watch()); + app = _ref[_i]; + if ((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || watchAll) { + _results.push(app.watch()); } } return _results; @@ -190,23 +198,20 @@ return help(); } switch (command) { - case 'build': - console.log('Build application'); - break; case 'watch': - console.log('Watching application'); + utils.log('Watching application'); break; case 'test': - console.log('Test application'); + utils.log('Test application'); break; case 'clean': - console.log('Clean application'); + utils.log('Clean application'); break; case 'version': - console.log('Version application'); + utils.log('Version application'); break; case 'server': - console.log("Starting Server at " + this.options.server.host + ":" + this.options.server.port); + utils.log("Starting Server at " + this.options.server.host + ":" + this.options.server.port); } return this[command](); }; @@ -218,31 +223,26 @@ return JSON.parse(fs.readFileSync(slug, 'utf-8')); }; - Hem.prototype.errorAndExit = function(error) { - console.log("ERROR: " + error); - return process.exit(1); - }; - - Hem.prototype.getTargetPackages = function(targets) { - var pkg, targetAll, _i, _len, _ref, _ref1, _results; + Hem.prototype.getTargetApps = function(targets) { + var app, targetAll, _i, _len, _ref, _ref1, _results; if (targets == null) { targets = []; } targetAll = targets.length === 0; - _ref = this.packages; + _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - if ((_ref1 = pkg.name, __indexOf.call(targets, _ref1) >= 0) || targetAll) { - _results.push(pkg); + app = _ref[_i]; + if ((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || targetAll) { + _results.push(app); } } return _results; }; Hem.prototype.testTargets = function(targets, options) { - var pkg, testPackages; + var app, testApps; if (targets == null) { targets = []; @@ -250,33 +250,33 @@ if (options == null) { options = {}; } - testPackages = (function() { + testApps = (function() { var _i, _len, _ref, _results; - _ref = this.getTargetPackages(targets); + _ref = this.getTargetApps(targets); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - if (pkg.target.canTest()) { - _results.push(pkg); + app = _ref[_i]; + if (app.test) { + _results.push(app); } } return _results; }).call(this); - return testing.run(testPackages, singlRun); + return testing.run(this, testApps, options); }; Hem.prototype.buildTargets = function(targets) { - var pkg, _i, _len, _ref, _results; + var app, _i, _len, _ref, _results; if (targets == null) { targets = []; } - _ref = this.getTargetPackages(targets); + _ref = this.getTargetApps(targets); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - _results.push(pkg.build(!argv.debug)); + app = _ref[_i]; + _results.push(app.build(!argv.debug)); } return _results; }; diff --git a/lib/package.js b/lib/package.js index a3438a5..ad787ff 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var CssPackage, Dependency, JsPackage, Package, Stitch, createPackage, cssFile, fs, jsFile, path, stitchFile, toArray, uglify, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, createApplication, fs, path, stitchFile, uglify, utils, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -16,57 +16,147 @@ Stitch = require('./stitch'); - toArray = require('./utils').toArray; + utils = require('./utils'); - jsFile = /\.js$/i; + Application = (function() { + function Application(name, config) { + var defaults, err, _ref; - cssFile = /\.css$/i; - - createPackage = function(name, config) { - if (jsFile.test(config.target)) { - return new JsPackage(name, config); - } else if (cssFile.test(config.target)) { - return new CssPackage(name, config); - } else { - throw new Error("Unsupported package type."); + if (config == null) { + config = {}; + } + this.name = name; + if (config.defaults) { + try { + defaults = require('../assets/defaults/' + config.defaults); + } catch (_error) { + err = _error; + console.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); + process.exit(1); + } + utils.log(((_ref = global.ARGV) != null ? _ref.v : void 0) ? "Applying '" + config.defaults + "' defaults to configuration..." : void 0); + config = utils.extend(defaults, config); + } + this.root = config.root || ""; + this.route = config.route || "/"; + this.packages = []; + this["static"] = config["static"] || void 0; + if (config.js) { + this.js = new JsPackage(this, config.js); + this.packages.push(this.js); + } + if (config.css) { + this.css = new CssPackage(this, config.css); + this.packages.push(this.css); + } + if (config.test) { + this.test = new JsPackage(this, config.test); + this.packages.push(this.test); + } } - }; + + Application.prototype.isMatchingRoute = function(route) { + var pkg, _i, _len, _ref; + + _ref = this.packages; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; + if (route === pkg.url) { + return pkg; + } + } + return void 0; + }; + + Application.prototype.unlink = function() { + var pkg, _i, _len, _ref, _results; + + _ref = this.packages; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; + _results.push(pkg.unlink()); + } + return _results; + }; + + Application.prototype.build = function(minify) { + var pkg, _i, _len, _ref, _results; + + if (minify == null) { + minify = false; + } + utils.log("Building application: " + this.name + ""); + _ref = this.packages; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; + _results.push(pkg.build(minify)); + } + return _results; + }; + + Application.prototype.watch = function() { + var pkg, _i, _len, _ref, _results; + + utils.log("Watching application: '" + this.name + "'"); + _ref = this.packages; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; + _results.push(pkg.watch()); + } + return _results; + }; + + return Application; + + })(); Package = (function() { - function Package(name, config) { + function Package(parent, config) { + var slash; + if (config == null) { config = {}; } - this.name = name; - this.target = config.target || (function() { - throw new Error("Missing target for " + name); - })(); - this.root = "./"; - this["public"] = "public"; - this.paths = toArray(config.paths || []); - this.test = config.test; - this.url = config.url || "/" + target; - this["static"] = config["static"] || { - "/": "" + root + "/public" - }; - this.version = config.version; - this.identifier = config.identifier || 'require'; - this.libs = toArray(config.libs || []); - this.modules = toArray(config.modules || []); - this.after = config.after || ""; + this.parent = parent; + this.paths = utils.toArray(config.paths || []); + slash = utils.endsWith(parent.root, path.sep) ? "" : path.sep; + if (this.parent.root.length > 0) { + this.target = parent.root + slash + config.target; + } else { + this.target = config.target; + } + slash = utils.startsWith(parent.route, "/") ? "" : "/"; + if (config.route) { + if (utils.startsWith(this.target, "/")) { + this.route = config.route; + } else { + this.route = parent.route + slash + config.route; + } + } else { + this.route = parent.route + slash + this.target; + } } Package.prototype.handleCompileError = function(ex) { - console.error(ex.message); + var _ref; + + if (ex.stack) { + utils.log(ex.stack); + } else { + utils.error(ex.message); + } if (ex.path) { console.error(ex.path); } if (ex.location) { console.error(ex.location); } - switch (this.argv.command) { + switch ((_ref = global.ARGV) != null ? _ref.command : void 0) { case "server" || "watch": - return "console.log(\"" + ex + "\");"; + return "console.log(\"HEM compile ERROR: " + ex + "\");"; default: return process.exit(1); } @@ -84,7 +174,7 @@ if (minify == null) { minify = false; } - console.log("Building '" + this.name + "' target: " + this.target); + utils.log(" - Building target: " + this.target + ""); source = this.compile(minify); if (source) { return fs.writeFileSync(this.target, source); @@ -95,7 +185,6 @@ var dir, lib, _i, _len, _ref, _results, _this = this; - console.log("Watching '" + this.name + "'"); _ref = ((function() { var _j, _len, _ref, _results1; @@ -125,12 +214,6 @@ return _results; }; - Package.prototype.canTest = function() { - return jsFile.test(target); - }; - - Package.prototype.isMatchingUrl = function(url) {}; - return Package; })(); @@ -138,11 +221,17 @@ JsPackage = (function(_super) { __extends(JsPackage, _super); - function JsPackage(name, config) { + function JsPackage(parent, config) { if (config == null) { config = {}; } - JsPackage.__super__.constructor.call(this, name, config); + config.target || (config.target = parent.name + ".js"); + JsPackage.__super__.constructor.call(this, parent, config); + this.identifier = config.identifier || 'require'; + this.libs = utils.toArray(config.libs || []); + this.modules = utils.toArray(config.modules || []); + this.after = utils.toArray(config.after || []); + this.testType = config.test || void 0; } JsPackage.prototype.compile = function(minify) { @@ -152,7 +241,7 @@ minify = false; } try { - result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); + result = [this.compileLibs(), this.compileModules(), this.compileLibs(this.after)].join("\n"); if (minify) { result = uglify(result); } @@ -175,20 +264,31 @@ }); }; - JsPackage.prototype.compileLibs = function() { - var lib; - - return ((function() { - var _i, _len, _ref, _results; + JsPackage.prototype.compileLibs = function(files, parentDir) { + var dir, file, results, slash, stats, _i, _len; - _ref = this.libs; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - lib = _ref[_i]; - _results.push(fs.readFileSync(lib, 'utf8')); + if (files == null) { + files = this.libs; + } + if (parentDir == null) { + parentDir = ""; + } + results = []; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + slash = parentDir === "" ? "" : path.sep; + file = parentDir + slash + file; + if (fs.existsSync(file)) { + stats = fs.lstatSync(file); + if (stats.isDirectory()) { + dir = fs.readdirSync(file); + results.push(this.compileLibs(dir, file)); + } else if (stats.isFile()) { + results.push(fs.readFileSync(file, 'utf8')); + } } - return _results; - }).call(this)).join("\n"); + } + return results.join("\n"); }; return JsPackage; @@ -198,11 +298,12 @@ CssPackage = (function(_super) { __extends(CssPackage, _super); - function CssPackage(name, config) { + function CssPackage(parent, config) { if (config == null) { config = {}; } - CssPackage.__super__.constructor.call(this, name, config); + config.target || (config.target = parent.name + ".css"); + CssPackage.__super__.constructor.call(this, parent, config); } CssPackage.prototype.compile = function(minify) { @@ -231,6 +332,10 @@ })(Package); - module.exports.createPackage = createPackage; + createApplication = function(name, config) { + return new Application(name, config); + }; + + module.exports.createApplication = createApplication; }).call(this); diff --git a/lib/server.js b/lib/server.js index ab5a6a9..2cdb0a3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,72 +1,71 @@ // Generated by CoffeeScript 1.6.2 (function() { - var connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server; + var connect, createRoutingProxy, fs, http, mime, patchServerResponseForRedirects, server, utils; connect = require('connect'); mime = require('connect')["static"].mime; - httpProxy = require('http-proxy'); - http = require('http'); fs = require('fs'); + utils = require('./utils'); + server = {}; - server.start = function(packages, options) { + server.start = function(hemapps, options) { var app; app = connect(); - app.use(server.middleware(app, packages, options)); + app.use(server.middleware(app, hemapps, options)); return http.createServer(app).listen(options.port, options.host); }; - server.middleware = function(app, packages, options) { - var pkg, route, url, value, _i, _j, _len, _len1, _ref; + server.middleware = function(app, hemapps, options) { + var hemapp, pkg, route, url, value, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; - for (_i = 0, _len = packages.length; _i < _len; _i++) { - pkg = packages[_i]; - if (pkg.url && !!server.VERBOSE) { - console.log("Map package '" + pkg.name + "' to " + pkg.url); + for (_i = 0, _len = hemapps.length; _i < _len; _i++) { + hemapp = hemapps[_i]; + if (hemapp.url && utils.VERBOSE) { + _ref = hemapp.packages; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + pkg = _ref[_j]; + utils.log("Map application target '" + pkg.target + "' to " + pkg.route); + } } - if (pkg["static"]) { - options.routes.push(pkg["static"]); + if (hemapp["static"]) { + options.routes = utils.extend(options.routes, hemapp["static"]); } } - _ref = options.routes; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - route = _ref[_j]; + _ref1 = options.routes; + for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { + route = _ref1[_k]; url = Object.keys(route)[0]; value = route[url]; if (typeof value === 'string') { if (fs.existsSync(value)) { - if (!!server.VERBOSE) { - console.log("Map directory '" + value + "' to " + url); - } + utils.verbose("Map directory '" + value + "' to " + url); app.use(url, connect["static"](value)); } else { - console.log("ERROR: The folder " + value + " does not exist."); - process.exit(1); + utils.errorAndExit("The folder " + value + " does not exist."); } } else if (value.host) { - if (!!server.VERBOSE) { - console.log("Proxy requests from " + url + " to " + value.host); - } + utils.verbose("Proxy requests from " + url + " to " + value.host); app.use(url, createRoutingProxy(value)); } else { throw new Error("Invalid route configuration for " + url); } } return function(req, res, next) { - var str, _k, _len2, _ref1; + var str, _l, _len3, _ref2; - url = ((_ref1 = require("url").parse(req.url)) != null ? _ref1.pathname.toLowerCase() : void 0) || ""; + url = ((_ref2 = require("url").parse(req.url)) != null ? _ref2.pathname.toLowerCase() : void 0) || ""; if (url.match(/\.js|\.css/)) { - for (_k = 0, _len2 = packages.length; _k < _len2; _k++) { - pkg = packages[_k]; - if (pkg.isMatchingUrl(url)) { - str = pkg.compile(!!server.DEBUG); + for (_l = 0, _len3 = hemapps.length; _l < _len3; _l++) { + hemapp = hemapps[_l]; + if (pkg = hemapp.isMatchingRoute(url)) { + str = pkg.compile(!debug); res.charset = 'utf-8'; res.setHeader('Content-Type', mime.lookup(pkg.target)); res.setHeader('Content-Length', Buffer.byteLength(str)); @@ -85,7 +84,7 @@ if (options == null) { options = {}; } - proxy = new httpProxy.RoutingProxy(); + proxy = new require('http-proxy').RoutingProxy(); options.hostPath || (options.hostPath = ""); options.port || (options.port = 80); options.patchRedirect || (options.patchRedirect = true); diff --git a/lib/test.js b/lib/test.js index f29538a..c02e29b 100644 --- a/lib/test.js +++ b/lib/test.js @@ -4,14 +4,14 @@ fs = require('fs'); - start = function(packages, options) { + start = function(hem, hemapps, options) { if (options == null) { options = {}; } - return startKarma(packages, singleRun); + return startKarma(hem, hemapps, options); }; - startKarma = function(packages, options) { + startKarma = function(hemapps, options) { var testConfig; if (options == null) { @@ -21,21 +21,21 @@ testConfig || (testConfig = { configFile: require.resolve("../assets/testacular.conf.js"), singleRun: options.singleRun || true, - basePath: process.cwd(), + basePath: hem.homeDir, logLevel: 'error', browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], - files: createKarmaFileList(packages) + files: createKarmaFileList(hemapps) }); return require('karma').server.start(testConfig); }; - createKarmaFileList = function(packages) { - var fileList, pkg, _i, _len; + createKarmaFileList = function(hemapps) { + var app, fileList, _i, _len; fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), require.resolve("../node_modules/karma/adapter/jasmine.js")]; - for (_i = 0, _len = packages.length; _i < _len; _i++) { - pkg = packages[_i]; - fileList.push(pkg.target); + for (_i = 0, _len = hemapps.length; _i < _len; _i++) { + app = hemapps[_i]; + fileList.push(app.test.target); } return fileList; }; diff --git a/lib/utils.js b/lib/utils.js index 8f40320..54c99d7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,8 +1,12 @@ // Generated by CoffeeScript 1.6.2 (function() { - var flatten; + var extend, flatten, sty, utils; - exports.flatten = flatten = function(array, results) { + sty = require('sty'); + + utils = {}; + + utils.flatten = flatten = function(array, results) { var item, _i, _len; if (results == null) { @@ -19,7 +23,7 @@ return results; }; - exports.toArray = function(value) { + utils.toArray = function(value) { if (value == null) { value = []; } @@ -30,4 +34,53 @@ } }; + utils.startsWith = function(str, value) { + return str.slice(-value.length) === value; + }; + + utils.endsWith = function(str, value) { + return str.slice(0, value.length) === value; + }; + + utils.extend = extend = function(a, b) { + var x; + + for (x in b) { + if (typeof b[x] === 'object' && !Array.isArray(b[x])) { + a[x] || (a[x] = {}); + extend(a[x], b[x]); + } else { + a[x] = b[x]; + } + } + return a; + }; + + utils.log = function(message) { + return console.log(sty.parse(message)); + }; + + utils.debug = function(message) { + if (DEBUG) { + return console.log(sty.parse(message)); + } + }; + + utils.verbose = function(message) { + if (VERBOSE) { + return console.log(sty.parse(message)); + } + }; + + utils.error = function(message) { + return console.log("" + (sty.red('ERROR:')) + " " + (sty.parse(message))); + }; + + utils.errorAndExit = function(error) { + utils.error(error); + return process.exit(1); + }; + + module.exports = utils; + }).call(this); diff --git a/package.json b/package.json index 9876286..3c6586f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "connect": "~2.7.0", "http-proxy": "~0.10", "karma": "~0.8.0", - "jade": "~0.30", - "node.extend": "~1.0.7" + "jade": "~0.30" } } diff --git a/src/hem.coffee b/src/hem.coffee index 6781ef6..727f7f2 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,11 +1,12 @@ -path = require('path') -fs = require('fs') -optimist = require('optimist') -compilers = require('./compilers') -server = require('./server') -versions = require('./versioning') -Package = require('./package') -testing = require('./test') +path = require('path') +fs = require('fs') +optimist = require('optimist') +compilers = require('./compilers') +server = require('./server') +versions = require('./versioning') +application = require('./package') +testing = require('./test') +utils = require('./utils') # ------- Commandline arguments @@ -31,14 +32,18 @@ argv = optimist.usage([ argv.command = argv._[0] argv.targets = argv._[1..] -# set compilers debug mode -compilers.DEBUG = server.DEBUG = !!argv.debug -compilers.VERBOSE = server.VERBOSE = !!argv.v +# expose argv +utils.ARGV = argv + +# always have a value for these argv options +utils.DEBUG = argv.debug = !!argv.debug +utils.VERBOSE = argv.v = !!argv.v +utils.COMMAND = argv.command # ------- Global Functions help = -> - console.log "HEM Version: " + require('../package.json')?.version + "\n" + utils.log "HEM Version: " + require('../package.json')?.version + "\n" optimist.showHelp() process.exit() @@ -54,20 +59,23 @@ class Hem @middleware: (slugFile) -> hem = new Hem(slugFile) - server.middleware(hem.packages, hem.options.server) + server.middleware(hem.apps, hem.options.server) # ------- instance variables compilers: compilers + # the slug directory + homeDir: '' + # default values for server options: server: port: 9294 host: "localhost" - # emtpy packages list - packages: [] + # emtpy applications list + apps: [] # ------- Constructor @@ -83,58 +91,58 @@ class Hem if fs.existsSync(slug) @options[key] = value for key, value of @readSlug(slug) # make sure we are in same directory as slug - slugDir = path.dirname(path.resolve(process.cwd() + "/" + slug)) - process.chdir(slugDir) + @homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)) + process.chdir(@homeDir) else - @errorAndExit "Unable to find #{slug} file in current directory" + utils.errorAndExit "Unable to find #{slug} file in current directory" # if versioning turned on, pass in correct module to config if @options.version @options.version.type or= "package" if not (@options.version.module = versions[@options.version.type]) - @errorAndExit "Incorrect type value for versioning (#{@options.version.type})" + utils.errorAndExit "Incorrect type value for versioning (#{@options.version.type})" # allow overrides and set defaults @options.server.port = argv.port if argv.port @options.server.host or= "" @options.server.routes or= [] - # setup packages from options/slug - for name, config of @options.packages + # setup applications from options/slug + for name, config of @options continue if name is "server" - @packages.push Package.createPackage(name, config) + @apps.push application.createApplication(name, config) # ------- Command Functions server: -> - server.start(@packages, @options.server) + server.start(@apps, @options.server) clean: -> targets = argv.targets cleanAll = targets.length is 0 - pkg.unlink() for pkg in @packages when pkg.name in targets or cleanAll + app.unlink() for app in @apps when app.name in targets or cleanAll build: -> @clean() @buildTargets(argv.targets) version: -> - # TODO: this should be done at the package level, not globally + # TODO: this should be done at the application level, not globally module = @options.version?.module files = @options.version?.files if module and files - module.updateFiles(files, @packages) + module.updateFiles(files, @apps) else console.error "ERROR: Versioning not enabled in slug.json" watch: -> targets = argv.targets - @buildPackages(targets) + @buildApps(targets) # also run testacular tests if -t is passed in the command line @testTargets(targets, singleRun: false) if argv.test - # begin watching package targets + # begin watching application targets watchAll = targets.length is 0 - pkg.watch() for pkg in @packages when pkg.name in targets or watchAll + app.watch() for app in @apps when app.name in targets or watchAll test: -> @buildTargets(argv.targets) @@ -143,12 +151,11 @@ class Hem exec: (command = argv.command) -> return help() unless @[command] switch command - when 'build' then console.log 'Build application' - when 'watch' then console.log 'Watching application' - when 'test' then console.log 'Test application' - when 'clean' then console.log 'Clean application' - when 'version' then console.log 'Version application' - when 'server' then console.log "Starting Server at #{@options.server.host}:#{@options.server.port}" + when 'watch' then utils.log 'Watching application' + when 'test' then utils.log 'Test application' + when 'clean' then utils.log 'Clean application' + when 'version' then utils.log 'Version application' + when 'server' then utils.log "Starting Server at #{@options.server.host}:#{@options.server.port}" @[command]() # ------- Private Functions @@ -157,20 +164,16 @@ class Hem return {} unless slug and fs.existsSync(slug) JSON.parse(fs.readFileSync(slug, 'utf-8')) - errorAndExit: (error) -> - console.log "ERROR: #{error}" - process.exit(1) - - getTargetPackages: (targets = []) -> + getTargetApps: (targets = []) -> targetAll = targets.length is 0 - (pkg for pkg in @packages when pkg.name in targets or targetAll) + (app for app in @apps when app.name in targets or targetAll) testTargets: (targets = [], options = {}) -> - testPackages = (pkg for pkg in @getTargetPackages(targets) when pkg.target.canTest()) - testing.run(testPackages, singlRun) + testApps = (app for app in @getTargetApps(targets) when app.test) + testing.run(@, testApps, options) buildTargets: (targets = []) -> - pkg.build(not argv.debug) for pkg in @getTargetPackages(targets) + app.build(not argv.debug) for app in @getTargetApps(targets) module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index d44230f..baabba0 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -4,98 +4,129 @@ uglify = require('uglify-js') stitchFile = require('../assets/stitch') Dependency = require('./dependency') Stitch = require('./stitch') -{toArray} = require('./utils') +utils = require('./utils') -# ------- Public Functions - -jsFile = /\.js$/i -cssFile = /\.css$/i - -createPackage = (name, config) -> - if jsFile.test config.target - # TODO: at some point merge in framework defaults (ex. spine) - return new JsPackage(name,config) - else if cssFile.test config.target - # TODO: framework defaults for css - return new CssPackage(name, config) - # TODO: setup target for package that contains other packages? - else - throw new Error("Unsupported package type.") - -# ------- Classes +# ------- Parent Classes -class Package +class Application constructor: (name, config = {}) -> - @name = name - @target = config.target or throw new Error("Missing target for #{name}") - @root = "./" - @public = "public" - @paths = toArray(config.paths or []) - @test = config.test + @name = name + + # apply defaults + if (config.defaults) + try + defaults = require('../assets/defaults/' + config.defaults) + catch err + console.error "ERROR: Invalid 'defaults' value provided: " + config.defaults + process.exit 1 + utils.log("Applying '" + config.defaults + "' defaults to configuration..." if global.ARGV?.v) + config = utils.extend(defaults, config) + + # set variables + @root = config.root or "" + @route = config.route or "/" + + # create packages + @packages = [] + @static = config.static or undefined + if config.js + @js = new JsPackage(@,config.js) + @packages.push @js + if config.css + @css = new CssPackage(@,config.css) + @packages.push @css + if config.test + @test = new JsPackage(@,config.test) + @packages.push @test + + isMatchingRoute: (route) -> + # TODO: strip out any versioning here first + for pkg in @packages + return pkg if route is pkg.url + return undefined - # determine url - @url = config.url or "/" + target + unlink: -> + pkg.unlink() for pkg in @packages - # determine static folder - @static = config.static or "/" : "#{root}/public" + build: (minify = false) -> + utils.log("Building application: #{@name}") + pkg.build(minify) for pkg in @packages - # handle versioning - @version = config.version + watch: -> + utils.log("Watching application: '#{@name}'") + pkg.watch() for pkg in @packages - # TODO: provide framework file to that will supply defaults - # - merge that with options provided? put merge in utils.coffee copy from connect - # - if folder starts with ./ then start from slug otherwise use defaults - # - provide root/context value to set starting point - # - call framework options by name of framework, hem spine new healthlink +class Package + constructor: (parent, config = {}) -> + @parent = parent + @paths = utils.toArray(config.paths or []) - # javascript only configurations - @identifier = config.identifier or 'require' - @libs = toArray(config.libs or []) - @modules = toArray(config.modules or []) - @after = config.after or "" + # determine target filename + slash = if utils.endsWith(parent.root, path.sep) then "" else path.sep + if @parent.root.length > 0 + @target = parent.root + slash + config.target + else + @target = config.target + + # determine url + slash = if utils.startsWith(parent.route,"/") then "" else "/" + if config.route + if utils.startsWith(@target,"/") + @route = config.route + else + @route = parent.route + slash + config.route + else + @route = parent.route + slash + @target handleCompileError: (ex) -> - console.error ex.message + if ex.stack + utils.log(ex.stack) + else + utils.error(ex.message) console.error ex.path if ex.path console.error ex.location if ex.location # only return when in server/watch mode, otherwise exit - switch @argv.command - when "server" or "watch" then return "console.log(\"#{ex}\");" + switch global.ARGV?.command + when "server" or "watch" then return "console.log(\"HEM compile ERROR: #{ex}\");" else process.exit(1) unlink: -> fs.unlinkSync(@target) if fs.existsSync(@target) build: (minify = false) -> - console.log "Building '#{@name}' target: #{@target}" + utils.log(" - Building target: #{@target}") source = @compile(minify) fs.writeFileSync(@target, source) if source watch: -> - console.log "Watching '#{@name}'" for dir in (path.dirname(lib) for lib in @libs).concat @paths continue unless fs.existsSync(dir) require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() - canTest: -> - # eventually see if there is /test folder - return jsFile.test target - - isMatchingUrl: (url) -> - # TODO: strip out any versioning - # ------- Child Classes class JsPackage extends Package - constructor: (name, config = {}) -> - super(name, config) + constructor: (parent, config = {}) -> + config.target or= parent.name + ".js" + super(parent, config) + + # javascript only configurations + @identifier = config.identifier or 'require' + @libs = utils.toArray(config.libs or []) + @modules = utils.toArray(config.modules or []) + @after = utils.toArray(config.after or []) + + # for testing types + # TODO: or have test libs to pull test files from? woulnd't need after stuff if we did that?? + # testLibs = ['jasmine'] or ['test/public/lib'] + @testType = config.test or undefined compile: (minify = false) -> try - result = [@compileLibs(), @compileModules(), @after].join("\n") + result = [@compileLibs(), @compileModules(), @compileLibs(@after)].join("\n") result = uglify(result) if minify result catch ex @@ -108,14 +139,27 @@ class JsPackage extends Package _modules = @depend.resolve().concat(_stitch.resolve()) stitchFile(identifier: @identifier, modules: _modules) - compileLibs: -> - # TODO: check if lib is a folder, then pull in everything - (fs.readFileSync(lib, 'utf8') for lib in @libs).join("\n") + compileLibs: (files = @libs, parentDir = "") -> + # check if folder or file + results = [] + for file in files + slash = if parentDir is "" then "" else path.sep + file = parentDir + slash + file + if fs.existsSync(file) + stats = fs.lstatSync(file) + if (stats.isDirectory()) + # get directory contents + dir = fs.readdirSync(file) + results.push @compileLibs(dir, file) + else if (stats.isFile()) + results.push fs.readFileSync(file, 'utf8') + results.join("\n") class CssPackage extends Package - constructor: (name, config = {}) -> - super(name, config) + constructor: (parent, config = {}) -> + config.target or= parent.name + ".css" + super(parent, config) compile: (minify = false) -> try @@ -132,6 +176,11 @@ class CssPackage extends Package catch ex @handleCompileError(ex) -module.exports.createPackage = createPackage +# ------- Public Functions + +createApplication = (name, config) -> + return new Application(name, config) + +module.exports.createApplication = createApplication diff --git a/src/server.coffee b/src/server.coffee index da03df6..6e6299e 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -1,42 +1,41 @@ -connect = require('connect') -mime = require('connect').static.mime -httpProxy = require('http-proxy') -http = require('http') -fs = require('fs') -server = {} +connect = require('connect') +mime = require('connect').static.mime +http = require('http') +fs = require('fs') +utils = require('./utils') +server = {} # ------- Public Functions -server.start = (packages, options) -> +server.start = (hemapps, options) -> app = connect() - app.use(server.middleware(app, packages, options)) + app.use(server.middleware(app, hemapps, options)) http.createServer(app).listen(options.port, options.host) -# eventually just pass in routes and server options -server.middleware = (app, packages, options) -> +server.middleware = (app, hemapps, options) -> # determine if there is any dynamic or static routes to add - for pkg in packages - if pkg.url and !!server.VERBOSE - console.log "Map package '#{pkg.name}' to #{pkg.url}" - if pkg.static - options.routes.push pkg.static + for hemapp in hemapps + if hemapp.url and utils.VERBOSE + for pkg in hemapp.packages + utils.log "Map application target '#{pkg.target}' to #{pkg.route}" + if hemapp.static + options.routes = utils.extend(options.routes, hemapp.static) - # setup static and proxy middleware + # setup static routes and proxy middleware for route in options.routes url = Object.keys(route)[0] value = route[url] # setup static route if (typeof value is 'string') if fs.existsSync(value) - console.log "Map directory '#{value}' to #{url}" if !!server.VERBOSE + utils.verbose "Map directory '#{value}' to #{url}" app.use(url, connect.static(value)) else - console.log "ERROR: The folder #{value} does not exist." - process.exit(1) + utils.errorAndExit "The folder #{value} does not exist." # setup proxy route else if value.host - console.log "Proxy requests from #{url} to #{value.host}" if !!server.VERBOSE + utils.verbose "Proxy requests from #{url} to #{value.host}" app.use(url, createRoutingProxy(value)) else throw new Error("Invalid route configuration for #{url}") @@ -47,13 +46,12 @@ server.middleware = (app, packages, options) -> # get url path url = require("url").parse(req.url)?.pathname.toLowerCase() or "" - # loop over pkgs and call compile when there is a match + # loop over hem applications and call compile when there is a match if url.match(/\.js|\.css/) - for pkg in packages - # TODO: get non versioned url here?? - if pkg.isMatchingUrl(url) + for hemapp in hemapps + if pkg = hemapp.isMatchingRoute(url) # TODO: keep (and return) in memory build if there hasn't been any changes?? - str = pkg.compile(!!server.DEBUG) + str = pkg.compile(not debug) res.charset = 'utf-8' res.setHeader('Content-Type', mime.lookup(pkg.target)) res.setHeader('Content-Length', Buffer.byteLength(str)) @@ -65,7 +63,7 @@ server.middleware = (app, packages, options) -> # ------- Private Functions createRoutingProxy = (options = {}) -> - proxy = new httpProxy.RoutingProxy() + proxy = new require('http-proxy').RoutingProxy() # additional options options.hostPath or= "" options.port or= 80 diff --git a/src/test.coffee b/src/test.coffee index 4955ab0..4edb0c6 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -2,13 +2,16 @@ fs = require('fs') # ------- Public Functions -start = (packages, options = {}) -> - # TODO: determine whether to use phantomjs or karma for testing - startKarma(packages, singleRun) +start = (hem, hemapps, options = {}) -> + # TODO: is karam avaliable, use that, + # - fall back to phantomjs + # - otherwise just open file in browser?? + + startKarma(hem, hemapps, options) # ------- Test Functions -startKarma = (packages, options = {}) -> +startKarma = (hemapps, options = {}) -> # use custom testacular config file provided by user testConfig = fs.existsSync(options.config) and fs.realpathSync(options.config) @@ -16,22 +19,22 @@ startKarma = (packages, options = {}) -> testConfig or= configFile : require.resolve("../assets/testacular.conf.js") singleRun : options.singleRun or true - basePath : process.cwd() + basePath : hem.homeDir logLevel : 'error' browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] - files : createKarmaFileList(packages) + files : createKarmaFileList(hemapps) # start testacular server require('karma').server.start(testConfig) -createKarmaFileList = (packages) -> - # TODO how to configure this to use other adapters? - +createKarmaFileList = (hemapps) -> + # TODO: other adapters? # look at at test type to see what assets we add fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), require.resolve("../node_modules/karma/adapter/jasmine.js")] - # loop over javascript packages and add their targets - fileList.push pkg.target for pkg in packages + + # loop over javascript hem applications and add their test targets + fileList.push app.test.target for app in hemapps return fileList # ------- Exports diff --git a/src/utils.coffee b/src/utils.coffee index 1e6bc92..584d86f 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,4 +1,7 @@ -exports.flatten = flatten = (array, results = []) -> +sty = require('sty') +utils = {} + +utils.flatten = flatten = (array, results = []) -> for item in array if Array.isArray(item) flatten(item, results) @@ -6,5 +9,38 @@ exports.flatten = flatten = (array, results = []) -> results.push(item) results -exports.toArray = (value = []) -> - if Array.isArray(value) then value else [value] \ No newline at end of file +utils.toArray = (value = []) -> + if Array.isArray(value) then value else [value] + +utils.startsWith = (str, value) -> + str.slice(-value.length) is value + +utils.endsWith = (str, value) -> + str.slice(0, value.length) is value + +utils.extend = extend = (a, b) -> + for x of b + if typeof b[x] is 'object' and not Array.isArray(b[x]) + a[x] or= {} + extend(a[x], b[x]) + else + a[x] = b[x] + return a + +utils.log = (message) -> + console.log sty.parse(message) + +utils.debug = (message) -> + console.log sty.parse(message) if DEBUG + +utils.verbose = (message) -> + console.log sty.parse(message) if VERBOSE + +utils.error = (message) -> + console.log "#{sty.red 'ERROR:'} #{sty.parse(message)}" + +utils.errorAndExit = (error) -> + utils.error(error) + process.exit(1) + +module.exports = utils From d97da8bd53f643e829fce8d3865c46d3da58b236 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 14:38:44 -0500 Subject: [PATCH 021/167] server works again, yay! --- assets/defaults/spine.json | 2 +- lib/hem.js | 16 +++---- lib/package.js | 74 ++++++++++++++++------------- lib/server.js | 97 +++++++++++++++++++++----------------- lib/utils.js | 40 ++++++++++++++-- package.json | 3 +- src/hem.coffee | 12 ++--- src/package.coffee | 48 ++++++++++++------- src/server.coffee | 84 +++++++++++++++++---------------- src/utils.coffee | 28 +++++++++-- 10 files changed, 244 insertions(+), 160 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 703407b..023e48b 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -6,7 +6,7 @@ "paths": [ "css" ], - "target": "application.css" + "target": "public/application.css" }, "js": { diff --git a/lib/hem.js b/lib/hem.js index 160a5f4..355515d 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -5,10 +5,12 @@ path = require('path'); - fs = require('fs'); - optimist = require('optimist'); + utils = require('./utils'); + + fs = require('fs'); + compilers = require('./compilers'); server = require('./server'); @@ -19,8 +21,6 @@ testing = require('./test'); - utils = require('./utils'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('d', 'debug').describe('d', ':all compilations use debug mode').alias('t', 'test').describe('t', ':run testacular while using watch').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('b', 'browser').describe('b', ':run testacular using the supplied browser[s]').alias('n', 'noBuild').describe('n', ':turn off dynamic builds during server mode').describe('v', ':make hem more talkative(verbose)').argv; argv.command = argv._[0]; @@ -63,7 +63,7 @@ var hem; hem = new Hem(slugFile); - return server.middleware(hem.apps, hem.options.server); + return server.middleware(hem, hem.options.server); }; Hem.prototype.compilers = compilers; @@ -127,7 +127,7 @@ } Hem.prototype.server = function() { - return server.start(this.apps, this.options.server); + return server.start(this, this.options.server); }; Hem.prototype.clean = function() { @@ -211,7 +211,7 @@ utils.log('Version application'); break; case 'server': - utils.log("Starting Server at " + this.options.server.host + ":" + this.options.server.port); + utils.log("Starting Server at http://" + (this.options.server.host || "localhost") + ":" + this.options.server.port + ""); } return this[command](); }; @@ -276,7 +276,7 @@ _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; - _results.push(app.build(!argv.debug)); + _results.push(app.build(true)); } return _results; }; diff --git a/lib/package.js b/lib/package.js index ad787ff..5398823 100644 --- a/lib/package.js +++ b/lib/package.js @@ -20,7 +20,7 @@ Application = (function() { function Application(name, config) { - var defaults, err, _ref; + var defaults, err, route, value, _ref, _ref1; if (config == null) { config = {}; @@ -40,7 +40,12 @@ this.root = config.root || ""; this.route = config.route || "/"; this.packages = []; - this["static"] = config["static"] || void 0; + this["static"] = {}; + _ref1 = config["static"]; + for (route in _ref1) { + value = _ref1[route]; + this["static"][utils.cleanRoute(this.route, route)] = value; + } if (config.js) { this.js = new JsPackage(this, config.js); this.packages.push(this.js); @@ -61,7 +66,7 @@ _ref = this.packages; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; - if (route === pkg.url) { + if (route === pkg.route) { return pkg; } } @@ -80,18 +85,15 @@ return _results; }; - Application.prototype.build = function(minify) { + Application.prototype.build = function() { var pkg, _i, _len, _ref, _results; - if (minify == null) { - minify = false; - } utils.log("Building application: " + this.name + ""); _ref = this.packages; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; - _results.push(pkg.build(minify)); + _results.push(pkg.build()); } return _results; }; @@ -115,34 +117,45 @@ Package = (function() { function Package(parent, config) { - var slash; + var regexp, route, targetUrl, value, _ref; if (config == null) { config = {}; } this.parent = parent; this.paths = utils.toArray(config.paths || []); - slash = utils.endsWith(parent.root, path.sep) ? "" : path.sep; if (this.parent.root.length > 0) { - this.target = parent.root + slash + config.target; + this.target = utils.cleanPath(parent.root, config.target); } else { this.target = config.target; } - slash = utils.startsWith(parent.route, "/") ? "" : "/"; if (config.route) { if (utils.startsWith(this.target, "/")) { this.route = config.route; } else { - this.route = parent.route + slash + config.route; + this.route = utils.cleanRoute(parent.route, config.route); } } else { - this.route = parent.route + slash + this.target; + _ref = this.parent["static"]; + for (route in _ref) { + value = _ref[route]; + if (!this.route) { + if (utils.startsWith(this.target, value)) { + regexp = new RegExp("^" + value); + targetUrl = this.target.replace(regexp, ""); + this.route = utils.cleanRoute(route, targetUrl); + } + } + } + } + if (utils.COMMAND === "server") { + if (!this.route) { + utils.errorAndExit("Unable to determine route for " + this.target + ""); + } } } Package.prototype.handleCompileError = function(ex) { - var _ref; - if (ex.stack) { utils.log(ex.stack); } else { @@ -154,7 +167,7 @@ if (ex.location) { console.error(ex.location); } - switch ((_ref = global.ARGV) != null ? _ref.command : void 0) { + switch (utils.COMMAND) { case "server" || "watch": return "console.log(\"HEM compile ERROR: " + ex + "\");"; default: @@ -168,17 +181,18 @@ } }; - Package.prototype.build = function(minify) { + Package.prototype.build = function(save) { var source; - if (minify == null) { - minify = false; + if (save == null) { + save = false; } - utils.log(" - Building target: " + this.target + ""); - source = this.compile(minify); - if (source) { - return fs.writeFileSync(this.target, source); + utils.log("- Building target: " + this.target + ""); + source = this.compile(); + if (source && save) { + fs.writeFileSync(this.target, source); } + return source; }; Package.prototype.watch = function() { @@ -234,15 +248,12 @@ this.testType = config.test || void 0; } - JsPackage.prototype.compile = function(minify) { + JsPackage.prototype.compile = function() { var ex, result; - if (minify == null) { - minify = false; - } try { result = [this.compileLibs(), this.compileModules(), this.compileLibs(this.after)].join("\n"); - if (minify) { + if (utils.DEBUG === false) { result = uglify(result); } return result; @@ -306,12 +317,9 @@ CssPackage.__super__.constructor.call(this, parent, config); } - CssPackage.prototype.compile = function(minify) { + CssPackage.prototype.compile = function() { var ex, result, _i, _len, _path, _ref; - if (minify == null) { - minify = false; - } try { result = []; _ref = this.paths; diff --git a/lib/server.js b/lib/server.js index 2cdb0a3..95f688c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var connect, createRoutingProxy, fs, http, mime, patchServerResponseForRedirects, server, utils; + var connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server, utils; connect = require('connect'); @@ -12,60 +12,30 @@ utils = require('./utils'); + httpProxy = require('http-proxy'); + server = {}; - server.start = function(hemapps, options) { + server.start = function(hem, options) { var app; app = connect(); - app.use(server.middleware(app, hemapps, options)); + app.use(server.middleware(hem, options)); + server.setupStaticRoutes(app, hem, options); return http.createServer(app).listen(options.port, options.host); }; - server.middleware = function(app, hemapps, options) { - var hemapp, pkg, route, url, value, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; - - for (_i = 0, _len = hemapps.length; _i < _len; _i++) { - hemapp = hemapps[_i]; - if (hemapp.url && utils.VERBOSE) { - _ref = hemapp.packages; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - pkg = _ref[_j]; - utils.log("Map application target '" + pkg.target + "' to " + pkg.route); - } - } - if (hemapp["static"]) { - options.routes = utils.extend(options.routes, hemapp["static"]); - } - } - _ref1 = options.routes; - for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { - route = _ref1[_k]; - url = Object.keys(route)[0]; - value = route[url]; - if (typeof value === 'string') { - if (fs.existsSync(value)) { - utils.verbose("Map directory '" + value + "' to " + url); - app.use(url, connect["static"](value)); - } else { - utils.errorAndExit("The folder " + value + " does not exist."); - } - } else if (value.host) { - utils.verbose("Proxy requests from " + url + " to " + value.host); - app.use(url, createRoutingProxy(value)); - } else { - throw new Error("Invalid route configuration for " + url); - } - } + server.middleware = function(hem, options) { return function(req, res, next) { - var str, _l, _len3, _ref2; + var hemapp, pkg, str, url, _i, _len, _ref, _ref1; - url = ((_ref2 = require("url").parse(req.url)) != null ? _ref2.pathname.toLowerCase() : void 0) || ""; + url = ((_ref = require("url").parse(req.url)) != null ? _ref.pathname.toLowerCase() : void 0) || ""; if (url.match(/\.js|\.css/)) { - for (_l = 0, _len3 = hemapps.length; _l < _len3; _l++) { - hemapp = hemapps[_l]; + _ref1 = hem.apps; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + hemapp = _ref1[_i]; if (pkg = hemapp.isMatchingRoute(url)) { - str = pkg.compile(!debug); + str = pkg.build(); res.charset = 'utf-8'; res.setHeader('Content-Type', mime.lookup(pkg.target)); res.setHeader('Content-Length', Buffer.byteLength(str)); @@ -78,13 +48,52 @@ }; }; + server.setupStaticRoutes = function(app, hem, options) { + var hemapp, pkg, route, value, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results; + + _ref = hem.apps; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + hemapp = _ref[_i]; + if (utils.VERBOSE) { + utils.log("> Apply route mappings for application: " + hemapp.name + ""); + _ref1 = hemapp.packages; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + pkg = _ref1[_j]; + utils.log("- Mapping route " + pkg.route + " to " + pkg.target + ""); + } + } + if (hemapp["static"]) { + options.routes = utils.extend(hemapp["static"], options.routes); + } + } + _ref2 = options.routes; + _results = []; + for (route in _ref2) { + value = _ref2[route]; + if (typeof value === 'string') { + if (fs.existsSync(value)) { + utils.verbose("- Mapping static " + route + " to " + value + ""); + _results.push(app.use(route, connect["static"](value))); + } else { + _results.push(utils.errorAndExit("The folder " + value + " does not exist.")); + } + } else if (value.host) { + utils.verbose("- Proxy requests " + route + " to " + value.host + ":" + (value.port || 80) + value.hostPath + ""); + _results.push(app.use(route, createRoutingProxy(value))); + } else { + _results.push(utils.errorAndExit("Invalid route configuration for " + route + "")); + } + } + return _results; + }; + createRoutingProxy = function(options) { var proxy; if (options == null) { options = {}; } - proxy = new require('http-proxy').RoutingProxy(); + proxy = new httpProxy.RoutingProxy(); options.hostPath || (options.hostPath = ""); options.port || (options.port = 80); options.patchRedirect || (options.patchRedirect = true); diff --git a/lib/utils.js b/lib/utils.js index 54c99d7..2f94bce 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,9 +1,12 @@ // Generated by CoffeeScript 1.6.2 (function() { - var extend, flatten, sty, utils; + var clean, extend, flatten, path, sty, utils, + __slice = [].slice; sty = require('sty'); + path = require('path'); + utils = {}; utils.flatten = flatten = function(array, results) { @@ -35,11 +38,11 @@ }; utils.startsWith = function(str, value) { - return str.slice(-value.length) === value; + return str.slice(0, value.length) === value; }; utils.endsWith = function(str, value) { - return str.slice(0, value.length) === value; + return str.slice(-value.length) === value; }; utils.extend = extend = function(a, b) { @@ -56,18 +59,45 @@ return a; }; + utils.cleanPath = function() { + var paths; + + paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + return clean(paths, path.sep); + }; + + utils.cleanRoute = function() { + var routes; + + routes = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + return clean(routes, "/"); + }; + + clean = function(values, sep) { + var regexp, result, value, _i, _len; + + result = ""; + for (_i = 0, _len = values.length; _i < _len; _i++) { + value = values[_i]; + result = result + sep + value; + } + regexp = new RegExp("" + sep + "+", "g"); + result = result.replace(regexp, sep); + return result; + }; + utils.log = function(message) { return console.log(sty.parse(message)); }; utils.debug = function(message) { - if (DEBUG) { + if (this.DEBUG) { return console.log(sty.parse(message)); } }; utils.verbose = function(message) { - if (VERBOSE) { + if (this.VERBOSE) { return console.log(sty.parse(message)); } }; diff --git a/package.json b/package.json index 3c6586f..9a62a8a 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "connect": "~2.7.0", "http-proxy": "~0.10", "karma": "~0.8.0", - "jade": "~0.30" + "jade": "~0.30", + "sty": "~0.6.1" } } diff --git a/src/hem.coffee b/src/hem.coffee index 727f7f2..7bbe6c5 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,12 +1,12 @@ path = require('path') -fs = require('fs') optimist = require('optimist') +utils = require('./utils') +fs = require('fs') compilers = require('./compilers') server = require('./server') versions = require('./versioning') application = require('./package') testing = require('./test') -utils = require('./utils') # ------- Commandline arguments @@ -59,7 +59,7 @@ class Hem @middleware: (slugFile) -> hem = new Hem(slugFile) - server.middleware(hem.apps, hem.options.server) + server.middleware(hem, hem.options.server) # ------- instance variables @@ -115,7 +115,7 @@ class Hem # ------- Command Functions server: -> - server.start(@apps, @options.server) + server.start(@, @options.server) clean: -> targets = argv.targets @@ -155,7 +155,7 @@ class Hem when 'test' then utils.log 'Test application' when 'clean' then utils.log 'Clean application' when 'version' then utils.log 'Version application' - when 'server' then utils.log "Starting Server at #{@options.server.host}:#{@options.server.port}" + when 'server' then utils.log "Starting Server at http://#{@options.server.host or "localhost"}:#{@options.server.port}" @[command]() # ------- Private Functions @@ -173,7 +173,7 @@ class Hem testing.run(@, testApps, options) buildTargets: (targets = []) -> - app.build(not argv.debug) for app in @getTargetApps(targets) + app.build(true) for app in @getTargetApps(targets) module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index baabba0..70c690b 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -28,7 +28,13 @@ class Application # create packages @packages = [] - @static = config.static or undefined + @static = {} + + # configure static routes + for route, value of config.static + @static[utils.cleanRoute(@route, route)] = value + + # configure js/css/test packages if config.js @js = new JsPackage(@,config.js) @packages.push @js @@ -42,15 +48,15 @@ class Application isMatchingRoute: (route) -> # TODO: strip out any versioning here first for pkg in @packages - return pkg if route is pkg.url + return pkg if route is pkg.route return undefined unlink: -> pkg.unlink() for pkg in @packages - build: (minify = false) -> + build: -> utils.log("Building application: #{@name}") - pkg.build(minify) for pkg in @packages + pkg.build() for pkg in @packages watch: -> utils.log("Watching application: '#{@name}'") @@ -62,21 +68,27 @@ class Package @paths = utils.toArray(config.paths or []) # determine target filename - slash = if utils.endsWith(parent.root, path.sep) then "" else path.sep if @parent.root.length > 0 - @target = parent.root + slash + config.target + @target = utils.cleanPath(parent.root, config.target) else @target = config.target # determine url - slash = if utils.startsWith(parent.route,"/") then "" else "/" if config.route if utils.startsWith(@target,"/") @route = config.route else - @route = parent.route + slash + config.route + @route = utils.cleanRoute(parent.route, config.route) else - @route = parent.route + slash + @target + # use the static urls to determine the package @route + for route, value of @parent.static when not @route + if utils.startsWith(@target, value) + regexp = new RegExp("^#{value}") + targetUrl = @target.replace(regexp,"") + @route = utils.cleanRoute(route, targetUrl) + # make sure we have a route to use + if utils.COMMAND is "server" + utils.errorAndExit("Unable to determine route for #{@target}") unless @route handleCompileError: (ex) -> if ex.stack @@ -86,17 +98,18 @@ class Package console.error ex.path if ex.path console.error ex.location if ex.location # only return when in server/watch mode, otherwise exit - switch global.ARGV?.command + switch utils.COMMAND when "server" or "watch" then return "console.log(\"HEM compile ERROR: #{ex}\");" else process.exit(1) unlink: -> fs.unlinkSync(@target) if fs.existsSync(@target) - build: (minify = false) -> - utils.log(" - Building target: #{@target}") - source = @compile(minify) - fs.writeFileSync(@target, source) if source + build: (save = false) -> + utils.log("- Building target: #{@target}") + source = @compile() + fs.writeFileSync(@target, source) if source and save + source watch: -> for dir in (path.dirname(lib) for lib in @libs).concat @paths @@ -124,16 +137,17 @@ class JsPackage extends Package # testLibs = ['jasmine'] or ['test/public/lib'] @testType = config.test or undefined - compile: (minify = false) -> + compile: -> try result = [@compileLibs(), @compileModules(), @compileLibs(@after)].join("\n") - result = uglify(result) if minify + result = uglify(result) if utils.DEBUG is false result catch ex @handleCompileError(ex) compileModules: -> # TODO use detective....?? + # TODO cache results since this shouldn't change too much?? @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) _modules = @depend.resolve().concat(_stitch.resolve()) @@ -161,7 +175,7 @@ class CssPackage extends Package config.target or= parent.name + ".css" super(parent, config) - compile: (minify = false) -> + compile: () -> try result = [] for _path in @paths diff --git a/src/server.coffee b/src/server.coffee index 6e6299e..12341cc 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -1,69 +1,71 @@ -connect = require('connect') -mime = require('connect').static.mime -http = require('http') -fs = require('fs') -utils = require('./utils') -server = {} +connect = require('connect') +mime = require('connect').static.mime +http = require('http') +fs = require('fs') +utils = require('./utils') +httpProxy = require('http-proxy') +server = {} # ------- Public Functions -server.start = (hemapps, options) -> +server.start = (hem, options) -> app = connect() - app.use(server.middleware(app, hemapps, options)) + app.use(server.middleware(hem, options)) + server.setupStaticRoutes(app, hem, options) http.createServer(app).listen(options.port, options.host) -server.middleware = (app, hemapps, options) -> - - # determine if there is any dynamic or static routes to add - for hemapp in hemapps - if hemapp.url and utils.VERBOSE - for pkg in hemapp.packages - utils.log "Map application target '#{pkg.target}' to #{pkg.route}" - if hemapp.static - options.routes = utils.extend(options.routes, hemapp.static) - - # setup static routes and proxy middleware - for route in options.routes - url = Object.keys(route)[0] - value = route[url] - # setup static route - if (typeof value is 'string') - if fs.existsSync(value) - utils.verbose "Map directory '#{value}' to #{url}" - app.use(url, connect.static(value)) - else - utils.errorAndExit "The folder #{value} does not exist." - # setup proxy route - else if value.host - utils.verbose "Proxy requests from #{url} to #{value.host}" - app.use(url, createRoutingProxy(value)) - else - throw new Error("Invalid route configuration for #{url}") - +server.middleware = (hem, options) -> # return the custom middleware for connect to use return (req, res, next) -> - # get url path url = require("url").parse(req.url)?.pathname.toLowerCase() or "" - # loop over hem applications and call compile when there is a match if url.match(/\.js|\.css/) - for hemapp in hemapps + for hemapp in hem.apps if pkg = hemapp.isMatchingRoute(url) # TODO: keep (and return) in memory build if there hasn't been any changes?? - str = pkg.compile(not debug) + str = pkg.build() res.charset = 'utf-8' res.setHeader('Content-Type', mime.lookup(pkg.target)) res.setHeader('Content-Length', Buffer.byteLength(str)) res.end((req.method is 'HEAD' and null) or str) return + + # TODO: check static content?? + # continue to next middleware next() +server.setupStaticRoutes = (app, hem, options) -> + # determine if there is any dynamic or static routes to add + for hemapp in hem.apps + if utils.VERBOSE + utils.log "> Apply route mappings for application: #{hemapp.name}" + for pkg in hemapp.packages + utils.log "- Mapping route #{pkg.route} to #{pkg.target}" + if hemapp.static + options.routes = utils.extend(hemapp.static, options.routes) + + # setup static routes and proxy middleware + for route, value of options.routes + # setup static route + if (typeof value is 'string') + if fs.existsSync(value) + utils.verbose "- Mapping static #{route} to #{value}" + app.use(route, connect.static(value)) + else + utils.errorAndExit "The folder #{value} does not exist." + # setup proxy route + else if value.host + utils.verbose "- Proxy requests #{route} to #{value.host}:#{value.port or 80}#{value.hostPath}" + app.use(route, createRoutingProxy(value)) + else + utils.errorAndExit("Invalid route configuration for #{route}") + # ------- Private Functions createRoutingProxy = (options = {}) -> - proxy = new require('http-proxy').RoutingProxy() + proxy = new httpProxy.RoutingProxy() # additional options options.hostPath or= "" options.port or= 80 diff --git a/src/utils.coffee b/src/utils.coffee index 584d86f..d1258e5 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,4 +1,5 @@ sty = require('sty') +path = require('path') utils = {} utils.flatten = flatten = (array, results = []) -> @@ -13,10 +14,10 @@ utils.toArray = (value = []) -> if Array.isArray(value) then value else [value] utils.startsWith = (str, value) -> - str.slice(-value.length) is value + str.slice(0, value.length) is value utils.endsWith = (str, value) -> - str.slice(0, value.length) is value + str.slice(-value.length) is value utils.extend = extend = (a, b) -> for x of b @@ -27,14 +28,33 @@ utils.extend = extend = (a, b) -> a[x] = b[x] return a +utils.cleanPath = (paths...) -> + clean(paths, path.sep) + +utils.cleanRoute = (routes...) -> + clean(routes, "/") + +clean = (values, sep) -> + result = "" + for value in values + result = result + sep + value + # clean duplicate sep + regexp = new RegExp "#{sep}+","g" + result = result.replace(regexp, sep) + # make sure doesn't end in sep + result + + +# ------ Logging Helpers + utils.log = (message) -> console.log sty.parse(message) utils.debug = (message) -> - console.log sty.parse(message) if DEBUG + console.log sty.parse(message) if @DEBUG utils.verbose = (message) -> - console.log sty.parse(message) if VERBOSE + console.log sty.parse(message) if @VERBOSE utils.error = (message) -> console.log "#{sty.red 'ERROR:'} #{sty.parse(message)}" From 4a94999f3f5291819a1f7d531888d184eaf74460 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 16:15:21 -0500 Subject: [PATCH 022/167] some refactoring of server flow --- lib/server.js | 57 +++++++++++++++++++++-------------------------- src/server.coffee | 49 ++++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/lib/server.js b/lib/server.js index 95f688c..2d7a412 100644 --- a/lib/server.js +++ b/lib/server.js @@ -21,35 +21,11 @@ app = connect(); app.use(server.middleware(hem, options)); - server.setupStaticRoutes(app, hem, options); return http.createServer(app).listen(options.port, options.host); }; server.middleware = function(hem, options) { - return function(req, res, next) { - var hemapp, pkg, str, url, _i, _len, _ref, _ref1; - - url = ((_ref = require("url").parse(req.url)) != null ? _ref.pathname.toLowerCase() : void 0) || ""; - if (url.match(/\.js|\.css/)) { - _ref1 = hem.apps; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - hemapp = _ref1[_i]; - if (pkg = hemapp.isMatchingRoute(url)) { - str = pkg.build(); - res.charset = 'utf-8'; - res.setHeader('Content-Type', mime.lookup(pkg.target)); - res.setHeader('Content-Length', Buffer.byteLength(str)); - res.end((req.method === 'HEAD' && null) || str); - return; - } - } - } - return next(); - }; - }; - - server.setupStaticRoutes = function(app, hem, options) { - var hemapp, pkg, route, value, _i, _j, _len, _len1, _ref, _ref1, _ref2, _results; + var hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; _ref = hem.apps; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -66,25 +42,44 @@ options.routes = utils.extend(hemapp["static"], options.routes); } } + statics = connect(); _ref2 = options.routes; - _results = []; for (route in _ref2) { value = _ref2[route]; if (typeof value === 'string') { if (fs.existsSync(value)) { utils.verbose("- Mapping static " + route + " to " + value + ""); - _results.push(app.use(route, connect["static"](value))); + statics.use(route, connect["static"](value)); } else { - _results.push(utils.errorAndExit("The folder " + value + " does not exist.")); + utils.errorAndExit("The folder " + value + " does not exist."); } } else if (value.host) { utils.verbose("- Proxy requests " + route + " to " + value.host + ":" + (value.port || 80) + value.hostPath + ""); - _results.push(app.use(route, createRoutingProxy(value))); + statics.use(route, createRoutingProxy(value)); } else { - _results.push(utils.errorAndExit("Invalid route configuration for " + route + "")); + utils.errorAndExit("Invalid route configuration for " + route + ""); } } - return _results; + return function(req, res, next) { + var str, url, _k, _len2, _ref3, _ref4; + + url = ((_ref3 = require("url").parse(req.url)) != null ? _ref3.pathname.toLowerCase() : void 0) || ""; + if (url.match(/\.js|\.css/)) { + _ref4 = hem.apps; + for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) { + hemapp = _ref4[_k]; + if (pkg = hemapp.isMatchingRoute(url)) { + str = pkg.build(); + res.charset = 'utf-8'; + res.setHeader('Content-Type', mime.lookup(pkg.target)); + res.setHeader('Content-Length', Buffer.byteLength(str)); + res.end((req.method === 'HEAD' && null) || str); + return; + } + } + } + return statics.handle(req, res, next); + }; }; createRoutingProxy = function(options) { diff --git a/src/server.coffee b/src/server.coffee index 12341cc..a7af526 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -11,32 +11,9 @@ server = {} server.start = (hem, options) -> app = connect() app.use(server.middleware(hem, options)) - server.setupStaticRoutes(app, hem, options) http.createServer(app).listen(options.port, options.host) server.middleware = (hem, options) -> - # return the custom middleware for connect to use - return (req, res, next) -> - # get url path - url = require("url").parse(req.url)?.pathname.toLowerCase() or "" - # loop over hem applications and call compile when there is a match - if url.match(/\.js|\.css/) - for hemapp in hem.apps - if pkg = hemapp.isMatchingRoute(url) - # TODO: keep (and return) in memory build if there hasn't been any changes?? - str = pkg.build() - res.charset = 'utf-8' - res.setHeader('Content-Type', mime.lookup(pkg.target)) - res.setHeader('Content-Length', Buffer.byteLength(str)) - res.end((req.method is 'HEAD' and null) or str) - return - - # TODO: check static content?? - - # continue to next middleware - next() - -server.setupStaticRoutes = (app, hem, options) -> # determine if there is any dynamic or static routes to add for hemapp in hem.apps if utils.VERBOSE @@ -47,21 +24,43 @@ server.setupStaticRoutes = (app, hem, options) -> options.routes = utils.extend(hemapp.static, options.routes) # setup static routes and proxy middleware + statics = connect() for route, value of options.routes # setup static route if (typeof value is 'string') if fs.existsSync(value) utils.verbose "- Mapping static #{route} to #{value}" - app.use(route, connect.static(value)) + statics.use(route, connect.static(value)) else utils.errorAndExit "The folder #{value} does not exist." # setup proxy route else if value.host utils.verbose "- Proxy requests #{route} to #{value.host}:#{value.port or 80}#{value.hostPath}" - app.use(route, createRoutingProxy(value)) + statics.use(route, createRoutingProxy(value)) else utils.errorAndExit("Invalid route configuration for #{route}") + # return the custom middleware for connect to use + return (req, res, next) -> + # get url path + url = require("url").parse(req.url)?.pathname.toLowerCase() or "" + + # loop over hem applications and call compile when there is a match + if url.match(/\.js|\.css/) + for hemapp in hem.apps + if pkg = hemapp.isMatchingRoute(url) + # TODO: keep (and return) in memory build if there hasn't been any changes?? + str = pkg.build() + res.charset = 'utf-8' + res.setHeader('Content-Type', mime.lookup(pkg.target)) + res.setHeader('Content-Length', Buffer.byteLength(str)) + res.end((req.method is 'HEAD' and null) or str) + return + + # check static content + statics.handle(req, res, next) + + # ------- Private Functions createRoutingProxy = (options = {}) -> From b69076deb80bca555d1512c511f903124ab315d1 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 18:01:24 -0500 Subject: [PATCH 023/167] versioning is working again, double yay --- lib/hem.js | 53 ++++++++++++++++++------------------------- lib/package.js | 29 +++++++++++++++++++---- lib/versioning.js | 27 +++++++++++++--------- src/hem.coffee | 25 ++++++-------------- src/package.coffee | 27 +++++++++++++++++++--- src/versioning.coffee | 19 +++++++++------- 6 files changed, 105 insertions(+), 75 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 355515d..8eca415 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Hem, application, argv, compilers, fs, help, optimist, path, server, testing, utils, versions, + var Hem, application, argv, compilers, fs, help, optimist, path, server, testing, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; path = require('path'); @@ -15,8 +15,6 @@ server = require('./server'); - versions = require('./versioning'); - application = require('./package'); testing = require('./test'); @@ -80,7 +78,7 @@ Hem.prototype.apps = []; function Hem(options) { - var config, key, name, slug, value, _base, _base1, _base2, _ref, _ref1; + var config, key, name, slug, value, _base, _base1, _ref, _ref1; if (options == null) { options = {}; @@ -105,17 +103,11 @@ } else { utils.errorAndExit("Unable to find " + slug + " file in current directory"); } - if (this.options.version) { - (_base = this.options.version).type || (_base.type = "package"); - if (!(this.options.version.module = versions[this.options.version.type])) { - utils.errorAndExit("Incorrect type value for versioning (" + this.options.version.type + ")"); - } - } if (argv.port) { this.options.server.port = argv.port; } - (_base1 = this.options.server).host || (_base1.host = ""); - (_base2 = this.options.server).routes || (_base2.routes = []); + (_base = this.options.server).host || (_base.host = ""); + (_base1 = this.options.server).routes || (_base1.routes = []); _ref1 = this.options; for (name in _ref1) { config = _ref1[name]; @@ -127,6 +119,7 @@ } Hem.prototype.server = function() { + utils.log("Starting Server at http://" + (this.options.server.host || "localhost") + ":" + this.options.server.port + ""); return server.start(this, this.options.server); }; @@ -152,22 +145,14 @@ }; Hem.prototype.version = function() { - var files, module, _ref, _ref1; - - module = (_ref = this.options.version) != null ? _ref.module : void 0; - files = (_ref1 = this.options.version) != null ? _ref1.files : void 0; - if (module && files) { - return module.updateFiles(files, this.apps); - } else { - return console.error("ERROR: Versioning not enabled in slug.json"); - } + return this.versionTargets(argv.targets); }; Hem.prototype.watch = function() { var app, targets, watchAll, _i, _len, _ref, _ref1, _results; targets = argv.targets; - this.buildApps(targets); + this.buildTargets(targets); if (argv.test) { this.testTargets(targets, { singleRun: false @@ -198,20 +183,11 @@ return help(); } switch (command) { - case 'watch': - utils.log('Watching application'); - break; case 'test': utils.log('Test application'); break; case 'clean': utils.log('Clean application'); - break; - case 'version': - utils.log('Version application'); - break; - case 'server': - utils.log("Starting Server at http://" + (this.options.server.host || "localhost") + ":" + this.options.server.port + ""); } return this[command](); }; @@ -281,6 +257,21 @@ return _results; }; + Hem.prototype.versionTargets = function(targets) { + var app, _i, _len, _ref, _results; + + if (targets == null) { + targets = []; + } + _ref = this.getTargetApps(targets); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + app = _ref[_i]; + _results.push(app.version()); + } + return _results; + }; + return Hem; })(); diff --git a/lib/package.js b/lib/package.js index 5398823..c9e823f 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, createApplication, fs, path, stitchFile, uglify, utils, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, createApplication, fs, path, stitchFile, uglify, utils, versions, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -18,9 +18,11 @@ utils = require('./utils'); + versions = require('./versioning'); + Application = (function() { function Application(name, config) { - var defaults, err, route, value, _ref, _ref1; + var defaults, err, route, value, _base, _ref, _ref1; if (config == null) { config = {}; @@ -58,11 +60,22 @@ this.test = new JsPackage(this, config.test); this.packages.push(this.test); } + this.versioning = config.version || void 0; + if (this.versioning) { + (_base = this.versioning).type || (_base.type = "package"); + this.versioning.module = versions[this.versioning.type]; + if (!this.versioning.module) { + utils.errorAndExit("Incorrect type value for versioning (" + this.versioning.type + ")"); + } + } } Application.prototype.isMatchingRoute = function(route) { var pkg, _i, _len, _ref; + if (this.versioning) { + route = this.versioning.module.trimVersion(route); + } _ref = this.packages; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; @@ -70,7 +83,6 @@ return pkg; } } - return void 0; }; Application.prototype.unlink = function() { @@ -101,7 +113,7 @@ Application.prototype.watch = function() { var pkg, _i, _len, _ref, _results; - utils.log("Watching application: '" + this.name + "'"); + utils.log("Watching application: " + this.name + ""); _ref = this.packages; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -111,6 +123,15 @@ return _results; }; + Application.prototype.version = function() { + utils.log("Versioning application: " + this.name + ""); + if (this.versioning) { + return this.versioning.module.updateVersion(this); + } else { + return utils.errorAndExit("ERROR: Versioning not enabled in slug.json"); + } + }; + return Application; })(); diff --git a/lib/versioning.js b/lib/versioning.js index ffbe413..5d61ccd 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,37 +1,42 @@ // Generated by CoffeeScript 1.6.2 (function() { - var fs, path, replaceTargetInData, replaceTargetsInFiles, types; + var fs, path, replaceTargetInData, replaceTargetsInAppFiles, types, utils; fs = require('fs'); path = require('path'); + utils = require('./utils'); + types = {}; - replaceTargetsInFiles = function(files, version, pkgs) { - var data, file, pkg, _i, _j, _len, _len1, _results; + replaceTargetsInAppFiles = function(app, value) { + var data, file, files, pkg, _i, _j, _len, _len1, _ref, _results; + files = utils.toArray(app.versioning.files); + console.log(files); _results = []; for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; - console.log("updating file " + file + " with version " + version); + utils.log("- updating file " + file + " with version: " + value + ""); data = fs.readFileSync(file, 'utf8'); - for (_j = 0, _len1 = pkgs.length; _j < _len1; _j++) { - pkg = pkgs[_j]; - data = replaceTargetInData(data, version, pkg); + _ref = app.packages; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + pkg = _ref[_j]; + data = replaceTargetInData(data, value, pkg); } _results.push(fs.writeFileSync(file, data)); } return _results; }; - replaceTargetInData = function(data, version, pkg) { + replaceTargetInData = function(data, value, pkg) { var ext, match, name, replace; ext = path.extname(pkg.target); name = path.basename(pkg.target, ext); match = new RegExp("=(\"|')" + name + "[^\"']?" + ext + "(\"|')"); - replace = "=$1" + name + "." + version + ext + "$2"; + replace = "=$1" + name + "." + value + ext + "$2"; return data.replace(match, replace); }; @@ -39,8 +44,8 @@ getVersion: function() { return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; }, - updateFiles: function(files, pkgs) { - return replaceTargetsInFiles(files, this.getVersion(), pkgs); + updateVersion: function(app) { + return replaceTargetsInAppFiles(app, this.getVersion()); }, trimVersion: function(url) { return url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2"); diff --git a/src/hem.coffee b/src/hem.coffee index 7bbe6c5..e252df9 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -4,7 +4,6 @@ utils = require('./utils') fs = require('fs') compilers = require('./compilers') server = require('./server') -versions = require('./versioning') application = require('./package') testing = require('./test') @@ -96,12 +95,6 @@ class Hem else utils.errorAndExit "Unable to find #{slug} file in current directory" - # if versioning turned on, pass in correct module to config - if @options.version - @options.version.type or= "package" - if not (@options.version.module = versions[@options.version.type]) - utils.errorAndExit "Incorrect type value for versioning (#{@options.version.type})" - # allow overrides and set defaults @options.server.port = argv.port if argv.port @options.server.host or= "" @@ -115,6 +108,7 @@ class Hem # ------- Command Functions server: -> + utils.log "Starting Server at http://#{@options.server.host or "localhost"}:#{@options.server.port}" server.start(@, @options.server) clean: -> @@ -127,17 +121,11 @@ class Hem @buildTargets(argv.targets) version: -> - # TODO: this should be done at the application level, not globally - module = @options.version?.module - files = @options.version?.files - if module and files - module.updateFiles(files, @apps) - else - console.error "ERROR: Versioning not enabled in slug.json" + @versionTargets(argv.targets) watch: -> targets = argv.targets - @buildApps(targets) + @buildTargets(targets) # also run testacular tests if -t is passed in the command line @testTargets(targets, singleRun: false) if argv.test # begin watching application targets @@ -151,11 +139,8 @@ class Hem exec: (command = argv.command) -> return help() unless @[command] switch command - when 'watch' then utils.log 'Watching application' when 'test' then utils.log 'Test application' when 'clean' then utils.log 'Clean application' - when 'version' then utils.log 'Version application' - when 'server' then utils.log "Starting Server at http://#{@options.server.host or "localhost"}:#{@options.server.port}" @[command]() # ------- Private Functions @@ -175,6 +160,10 @@ class Hem buildTargets: (targets = []) -> app.build(true) for app in @getTargetApps(targets) + versionTargets: (targets = []) -> + app.version() for app in @getTargetApps(targets) + + module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index 70c690b..14825e9 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -5,6 +5,7 @@ stitchFile = require('../assets/stitch') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') +versions = require('./versioning') # ------- Parent Classes @@ -45,11 +46,24 @@ class Application @test = new JsPackage(@,config.test) @packages.push @test + # configure versioning + @versioning = config.version or undefined + if @versioning + @versioning.type or= "package" + @versioning.module = versions[@versioning.type] + if not (@versioning.module) + utils.errorAndExit "Incorrect type value for versioning (#{@versioning.type})" + + isMatchingRoute: (route) -> - # TODO: strip out any versioning here first + # strip out any versioning applied to file + if @versioning + route = @versioning.module.trimVersion(route) + # compare against package route values for pkg in @packages return pkg if route is pkg.route - return undefined + # return nothing + return unlink: -> pkg.unlink() for pkg in @packages @@ -59,9 +73,16 @@ class Application pkg.build() for pkg in @packages watch: -> - utils.log("Watching application: '#{@name}'") + utils.log("Watching application: #{@name}") pkg.watch() for pkg in @packages + version: -> + utils.log("Versioning application: #{@name}") + if @versioning + @versioning.module.updateVersion(@) + else + utils.errorAndExit "ERROR: Versioning not enabled in slug.json" + class Package constructor: (parent, config = {}) -> @parent = parent diff --git a/src/versioning.coffee b/src/versioning.coffee index a8067f5..6c6a8b5 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -1,23 +1,26 @@ fs = require('fs') path = require('path') +utils = require('./utils') types = {} # private functions -replaceTargetsInFiles = (files, version, pkgs) -> +replaceTargetsInAppFiles = (app, value) -> + files = utils.toArray(app.versioning.files) + console.log files for file in files - console.log "updating file #{file} with version #{version}" + utils.log "- updating file #{file} with version: #{value}" data = fs.readFileSync(file, 'utf8') # match all target in packages - for pkg in pkgs - data = replaceTargetInData(data, version, pkg) + for pkg in app.packages + data = replaceTargetInData(data, value, pkg) fs.writeFileSync(file, data) -replaceTargetInData = (data, version, pkg) -> +replaceTargetInData = (data, value, pkg) -> ext = path.extname(pkg.target) name = path.basename(pkg.target, ext) match = new RegExp("=(\"|')#{name}[^\"']?#{ext}(\"|')") - replace = "=$1#{name}.#{version}#{ext}$2" + replace = "=$1#{name}.#{value}#{ext}$2" data.replace(match, replace) # handle versioning based on package.json version (default) @@ -27,8 +30,8 @@ types.package = getVersion: -> JSON.parse(fs.readFileSync('./package.json', 'utf8')).version - updateFiles: (files, pkgs) -> - replaceTargetsInFiles(files, @getVersion(), pkgs) + updateVersion: (app) -> + replaceTargetsInAppFiles(app, @getVersion()) trimVersion: (url) -> url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2") From 5c4d06c50da240e8c690badd1fe68ab47da66ac6 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 18:28:13 -0500 Subject: [PATCH 024/167] removing debug, just need info/verbose, also ability to turn off console colors --- lib/compilers.js | 15 ++++++--------- lib/hem.js | 27 ++++++++++++++++++--------- lib/server.js | 16 +++++++--------- lib/utils.js | 8 +------- src/compilers.coffee | 12 ++++++------ src/hem.coffee | 30 ++++++++++++++++++------------ src/server.coffee | 11 +++++------ src/utils.coffee | 5 +---- 8 files changed, 62 insertions(+), 62 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index dc54aad..203b201 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,12 +1,14 @@ // Generated by CoffeeScript 1.6.2 (function() { - var compileCoffeescript, compilers, cs, eco, err, fs, jade, path, stylus, + var compileCoffeescript, compilers, cs, eco, err, fs, jade, path, stylus, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; fs = require('fs'); path = require('path'); + utils = require('./utils'); + compilers = {}; compilers.js = compilers.css = function(path) { @@ -91,7 +93,6 @@ try { template = jade.compile(content, { filename: path, - compileDebug: compilers.DEBUG, client: true }); source = template.toString(); @@ -115,7 +116,7 @@ content = fs.readFileSync(_path, 'utf8'); result = ''; - stylus(content).include(path.dirname(_path)).set('include css', (__indexOf.call(process.argv, '--includeCss') >= 0)).set('compress', !compilers.DEBUG).render(function(err, css) { + stylus(content).include(path.dirname(_path)).set('include css', (__indexOf.call(process.argv, '--includeCss') >= 0)).set('compress', utils.COMPRESS).render(function(err, css) { if (err) { throw err; } @@ -142,15 +143,11 @@ for (key in envhash) { if (process.env[key]) { envhash[key] = process.env[key]; - if (compilers.VERBOSE) { - console.log("- Set env " + key + " to " + envhash[key]); - } + utils.info("- Set env " + key + " to " + envhash[key] + ""); } if (packjson[key]) { envhash[key] = packjson[key]; - if (compilers.VERBOSE) { - console.log("- Set env " + key + " to " + envhash[key]); - } + utils.info(" - Set env " + key + " to " + envhash[key] + ""); } } return "module.exports = " + JSON.stringify(envhash); diff --git a/lib/hem.js b/lib/hem.js index 8eca415..c702d77 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -19,15 +19,19 @@ testing = require('./test'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('d', 'debug').describe('d', ':all compilations use debug mode').alias('t', 'test').describe('t', ':run testacular while using watch').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('b', 'browser').describe('b', ':run testacular using the supplied browser[s]').alias('n', 'noBuild').describe('n', ':turn off dynamic builds during server mode').describe('v', ':make hem more talkative(verbose)').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all complications are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').describe('v', ':make hem more talkative(verbose)').argv; argv.command = argv._[0]; argv.targets = argv._.slice(1); + if (!!argv.nocolors) { + require("sty").disable(); + } + utils.ARGV = argv; - utils.DEBUG = argv.debug = !!argv.debug; + utils.COMPRESS = argv.compress = !!argv.compress; utils.VERBOSE = argv.v = !!argv.v; @@ -153,11 +157,6 @@ targets = argv.targets; this.buildTargets(targets); - if (argv.test) { - this.testTargets(targets, { - singleRun: false - }); - } watchAll = targets.length === 0; _ref = this.apps; _results = []; @@ -171,8 +170,18 @@ }; Hem.prototype.test = function() { - this.buildTargets(argv.targets); - return this.testTargets(argv.targets); + var targets, testOptions; + + targets = argv.targets; + testOptions = {}; + if (argv.watch) { + this.watch(); + testOptions.singleRun = false; + } else { + this.buildTargets(targets); + testOptions.singleRun = true; + } + return this.testTargets(targets, testOptions); }; Hem.prototype.exec = function(command) { diff --git a/lib/server.js b/lib/server.js index 2d7a412..27a9d3b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -30,13 +30,11 @@ _ref = hem.apps; for (_i = 0, _len = _ref.length; _i < _len; _i++) { hemapp = _ref[_i]; - if (utils.VERBOSE) { - utils.log("> Apply route mappings for application: " + hemapp.name + ""); - _ref1 = hemapp.packages; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - pkg = _ref1[_j]; - utils.log("- Mapping route " + pkg.route + " to " + pkg.target + ""); - } + utils.info("> Apply route mappings for application: " + hemapp.name + ""); + _ref1 = hemapp.packages; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + pkg = _ref1[_j]; + utils.info("- Mapping route " + pkg.route + " to " + pkg.target + ""); } if (hemapp["static"]) { options.routes = utils.extend(hemapp["static"], options.routes); @@ -48,13 +46,13 @@ value = _ref2[route]; if (typeof value === 'string') { if (fs.existsSync(value)) { - utils.verbose("- Mapping static " + route + " to " + value + ""); + utils.info("- Mapping static " + route + " to " + value + ""); statics.use(route, connect["static"](value)); } else { utils.errorAndExit("The folder " + value + " does not exist."); } } else if (value.host) { - utils.verbose("- Proxy requests " + route + " to " + value.host + ":" + (value.port || 80) + value.hostPath + ""); + utils.info("- Proxy requests " + route + " to " + value.host + ":" + (value.port || 80) + value.hostPath + ""); statics.use(route, createRoutingProxy(value)); } else { utils.errorAndExit("Invalid route configuration for " + route + ""); diff --git a/lib/utils.js b/lib/utils.js index 2f94bce..82b49c0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -90,13 +90,7 @@ return console.log(sty.parse(message)); }; - utils.debug = function(message) { - if (this.DEBUG) { - return console.log(sty.parse(message)); - } - }; - - utils.verbose = function(message) { + utils.info = function(message) { if (this.VERBOSE) { return console.log(sty.parse(message)); } diff --git a/src/compilers.coffee b/src/compilers.coffee index 7ee3ff4..cce9959 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -1,5 +1,6 @@ fs = require('fs') path = require('path') +utils = require('./utils') compilers = {} compilers.js = compilers.css = (path) -> @@ -72,7 +73,7 @@ try try template = jade.compile content, filename: path - compileDebug: compilers.DEBUG + # compileDebug: compilers.DEBUG client: true source = template.toString() "module.exports = #{source};" @@ -91,8 +92,9 @@ try result = '' stylus(content) .include(path.dirname(_path)) + # TODO: are there other settings we should be looking at?? .set('include css', ('--includeCss' in process.argv)) - .set('compress', not compilers.DEBUG) + .set('compress', utils.COMPRESS) .render((err, css) -> throw err if err result = css @@ -114,12 +116,10 @@ compilers.env = (path) -> for key of envhash if process.env[key] envhash[key] = process.env[key] - if compilers.VERBOSE - console.log "- Set env #{key} to #{envhash[key]}" + utils.info "- Set env #{key} to #{envhash[key]}" if packjson[key] envhash[key] = packjson[key] - if compilers.VERBOSE - console.log "- Set env #{key} to #{envhash[key]}" + utils.info " - Set env #{key} to #{envhash[key]}" # return javascript module return "module.exports = " + JSON.stringify(envhash) diff --git a/src/hem.coffee b/src/hem.coffee index e252df9..bb37968 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -19,11 +19,10 @@ argv = optimist.usage([ ' version :version the application files' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') -.alias('d', 'debug').describe('d',':all compilations use debug mode') -.alias('t', 'test').describe('t',':run testacular while using watch') +.alias('c', 'compress').describe('c',':all complications are compressed/minified') +.alias('w', 'watch').describe('w',':watch files when running tests') .alias('s', 'slug').describe('s',':run hem using a specified slug file') -.alias('b', 'browser').describe('b',':run testacular using the supplied browser[s]') -.alias('n', 'noBuild').describe('n',':turn off dynamic builds during server mode') +.alias('n', 'nocolors').describe('n',':disable color in console output') .describe('v',':make hem more talkative(verbose)') .argv @@ -31,13 +30,16 @@ argv = optimist.usage([ argv.command = argv._[0] argv.targets = argv._[1..] +# disable colors +require("sty").disable() if !!argv.nocolors + # expose argv utils.ARGV = argv # always have a value for these argv options -utils.DEBUG = argv.debug = !!argv.debug -utils.VERBOSE = argv.v = !!argv.v -utils.COMMAND = argv.command +utils.COMPRESS = argv.compress = !!argv.compress +utils.VERBOSE = argv.v = !!argv.v +utils.COMMAND = argv.command # ------- Global Functions @@ -126,15 +128,19 @@ class Hem watch: -> targets = argv.targets @buildTargets(targets) - # also run testacular tests if -t is passed in the command line - @testTargets(targets, singleRun: false) if argv.test - # begin watching application targets watchAll = targets.length is 0 app.watch() for app in @apps when app.name in targets or watchAll test: -> - @buildTargets(argv.targets) - @testTargets(argv.targets) + targets = argv.targets + testOptions = {} + if argv.watch + @watch() + testOptions.singleRun = false + else + @buildTargets(targets) + testOptions.singleRun = true + @testTargets(targets, testOptions) exec: (command = argv.command) -> return help() unless @[command] diff --git a/src/server.coffee b/src/server.coffee index a7af526..b595d4d 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -16,10 +16,9 @@ server.start = (hem, options) -> server.middleware = (hem, options) -> # determine if there is any dynamic or static routes to add for hemapp in hem.apps - if utils.VERBOSE - utils.log "> Apply route mappings for application: #{hemapp.name}" - for pkg in hemapp.packages - utils.log "- Mapping route #{pkg.route} to #{pkg.target}" + utils.info "> Apply route mappings for application: #{hemapp.name}" + for pkg in hemapp.packages + utils.info "- Mapping route #{pkg.route} to #{pkg.target}" if hemapp.static options.routes = utils.extend(hemapp.static, options.routes) @@ -29,13 +28,13 @@ server.middleware = (hem, options) -> # setup static route if (typeof value is 'string') if fs.existsSync(value) - utils.verbose "- Mapping static #{route} to #{value}" + utils.info "- Mapping static #{route} to #{value}" statics.use(route, connect.static(value)) else utils.errorAndExit "The folder #{value} does not exist." # setup proxy route else if value.host - utils.verbose "- Proxy requests #{route} to #{value.host}:#{value.port or 80}#{value.hostPath}" + utils.info "- Proxy requests #{route} to #{value.host}:#{value.port or 80}#{value.hostPath}" statics.use(route, createRoutingProxy(value)) else utils.errorAndExit("Invalid route configuration for #{route}") diff --git a/src/utils.coffee b/src/utils.coffee index d1258e5..e9bf66d 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -50,10 +50,7 @@ clean = (values, sep) -> utils.log = (message) -> console.log sty.parse(message) -utils.debug = (message) -> - console.log sty.parse(message) if @DEBUG - -utils.verbose = (message) -> +utils.info = (message) -> console.log sty.parse(message) if @VERBOSE utils.error = (message) -> From 095ebb742179c0b0302b70c8e3a876f4ab4b297e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 18:33:59 -0500 Subject: [PATCH 025/167] only minify/compress by switch, faster execution during development if turned off --- lib/package.js | 7 ++++--- src/package.coffee | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/package.js b/lib/package.js index c9e823f..0f36b4e 100644 --- a/lib/package.js +++ b/lib/package.js @@ -203,12 +203,13 @@ }; Package.prototype.build = function(save) { - var source; + var extra, source; if (save == null) { save = false; } - utils.log("- Building target: " + this.target + ""); + extra = (utils.COMPRESS && " --using compression") || ""; + utils.log("- Building target: " + this.target + "" + extra); source = this.compile(); if (source && save) { fs.writeFileSync(this.target, source); @@ -274,7 +275,7 @@ try { result = [this.compileLibs(), this.compileModules(), this.compileLibs(this.after)].join("\n"); - if (utils.DEBUG === false) { + if (utils.COMPRESS) { result = uglify(result); } return result; diff --git a/src/package.coffee b/src/package.coffee index 14825e9..f5aa6d7 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -127,7 +127,8 @@ class Package fs.unlinkSync(@target) if fs.existsSync(@target) build: (save = false) -> - utils.log("- Building target: #{@target}") + extra = (utils.COMPRESS and " --using compression") or "" + utils.log("- Building target: #{@target}#{extra}") source = @compile() fs.writeFileSync(@target, source) if source and save source @@ -161,7 +162,7 @@ class JsPackage extends Package compile: -> try result = [@compileLibs(), @compileModules(), @compileLibs(@after)].join("\n") - result = uglify(result) if utils.DEBUG is false + result = uglify(result) if utils.COMPRESS result catch ex @handleCompileError(ex) From 03f04954078450e54574e0d47787e7f0605ea6d4 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 18:42:49 -0500 Subject: [PATCH 026/167] some minor changes to the way files are watched --- assets/defaults/spine.json | 2 +- lib/package.js | 22 ++++++++++------------ src/package.coffee | 10 ++++++++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 023e48b..4efa400 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -39,7 +39,7 @@ }, "test": { - "after": "alert('!')", + "after": "test/after", "identifier": "specs", "modules": [], "paths": [ diff --git a/lib/package.js b/lib/package.js index 0f36b4e..0bf4e67 100644 --- a/lib/package.js +++ b/lib/package.js @@ -218,20 +218,10 @@ }; Package.prototype.watch = function() { - var dir, lib, _i, _len, _ref, _results, + var dir, _i, _len, _ref, _results, _this = this; - _ref = ((function() { - var _j, _len, _ref, _results1; - - _ref = this.libs; - _results1 = []; - for (_j = 0, _len = _ref.length; _j < _len; _j++) { - lib = _ref[_j]; - _results1.push(path.dirname(lib)); - } - return _results1; - }).call(this)).concat(this.paths); + _ref = this.getWatchedDirs(); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { dir = _ref[_i]; @@ -250,6 +240,10 @@ return _results; }; + Package.prototype.getWatchedDirs = function() { + return this.paths; + }; + return Package; })(); @@ -324,6 +318,10 @@ return results.join("\n"); }; + JsPackage.prototype.getWatchedDirs = function() { + return this.paths.concat(this.libs.concat(this.after)); + }; + return JsPackage; })(Package); diff --git a/src/package.coffee b/src/package.coffee index f5aa6d7..5c97e51 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -134,12 +134,15 @@ class Package source watch: -> - for dir in (path.dirname(lib) for lib in @libs).concat @paths + for dir in @getWatchedDirs() continue unless fs.existsSync(dir) require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() + getWatchedDirs: -> + return @paths + # ------- Child Classes class JsPackage extends Package @@ -156,7 +159,7 @@ class JsPackage extends Package # for testing types # TODO: or have test libs to pull test files from? woulnd't need after stuff if we did that?? - # testLibs = ['jasmine'] or ['test/public/lib'] + # TODO: testLibs = ['jasmine'] or ['test/public/lib'] @testType = config.test or undefined compile: -> @@ -191,6 +194,9 @@ class JsPackage extends Package results.push fs.readFileSync(file, 'utf8') results.join("\n") + getWatchedDirs: -> + @paths.concat @libs.concat @after + class CssPackage extends Package constructor: (parent, config = {}) -> From a0d6ba1bba0a2ddf95d3cf2d836e8dedeb427eac Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 18:46:08 -0500 Subject: [PATCH 027/167] make sure to write builds to file system during watch --- lib/package.js | 10 +++++----- src/package.coffee | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/package.js b/lib/package.js index 0bf4e67..76681f6 100644 --- a/lib/package.js +++ b/lib/package.js @@ -202,16 +202,16 @@ } }; - Package.prototype.build = function(save) { + Package.prototype.build = function(write) { var extra, source; - if (save == null) { - save = false; + if (write == null) { + write = false; } extra = (utils.COMPRESS && " --using compression") || ""; utils.log("- Building target: " + this.target + "" + extra); source = this.compile(); - if (source && save) { + if (source && write) { fs.writeFileSync(this.target, source); } return source; @@ -233,7 +233,7 @@ interval: 1000 }, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { - return _this.build(); + return _this.build(true); } })); } diff --git a/src/package.coffee b/src/package.coffee index 5c97e51..45c4548 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -126,11 +126,11 @@ class Package unlink: -> fs.unlinkSync(@target) if fs.existsSync(@target) - build: (save = false) -> + build: (write = false) -> extra = (utils.COMPRESS and " --using compression") or "" utils.log("- Building target: #{@target}#{extra}") source = @compile() - fs.writeFileSync(@target, source) if source and save + fs.writeFileSync(@target, source) if source and write source watch: -> @@ -138,7 +138,7 @@ class Package continue unless fs.existsSync(dir) require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) - @build() + @build(true) getWatchedDirs: -> return @paths From ddc1d6f1e2e3a4307062d918aa15fe4d2ac2904a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 18:47:21 -0500 Subject: [PATCH 028/167] make write to file system the default value for builds --- lib/hem.js | 2 +- lib/package.js | 4 ++-- lib/server.js | 2 +- src/hem.coffee | 2 +- src/package.coffee | 4 ++-- src/server.coffee | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index c702d77..3f0944a 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -261,7 +261,7 @@ _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; - _results.push(app.build(true)); + _results.push(app.build()); } return _results; }; diff --git a/lib/package.js b/lib/package.js index 76681f6..27a43df 100644 --- a/lib/package.js +++ b/lib/package.js @@ -206,7 +206,7 @@ var extra, source; if (write == null) { - write = false; + write = true; } extra = (utils.COMPRESS && " --using compression") || ""; utils.log("- Building target: " + this.target + "" + extra); @@ -233,7 +233,7 @@ interval: 1000 }, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { - return _this.build(true); + return _this.build(); } })); } diff --git a/lib/server.js b/lib/server.js index 27a9d3b..d2d54ce 100644 --- a/lib/server.js +++ b/lib/server.js @@ -67,7 +67,7 @@ for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) { hemapp = _ref4[_k]; if (pkg = hemapp.isMatchingRoute(url)) { - str = pkg.build(); + str = pkg.build(false); res.charset = 'utf-8'; res.setHeader('Content-Type', mime.lookup(pkg.target)); res.setHeader('Content-Length', Buffer.byteLength(str)); diff --git a/src/hem.coffee b/src/hem.coffee index bb37968..b504a5f 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -164,7 +164,7 @@ class Hem testing.run(@, testApps, options) buildTargets: (targets = []) -> - app.build(true) for app in @getTargetApps(targets) + app.build() for app in @getTargetApps(targets) versionTargets: (targets = []) -> app.version() for app in @getTargetApps(targets) diff --git a/src/package.coffee b/src/package.coffee index 45c4548..f2f8494 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -126,7 +126,7 @@ class Package unlink: -> fs.unlinkSync(@target) if fs.existsSync(@target) - build: (write = false) -> + build: (write = true) -> extra = (utils.COMPRESS and " --using compression") or "" utils.log("- Building target: #{@target}#{extra}") source = @compile() @@ -138,7 +138,7 @@ class Package continue unless fs.existsSync(dir) require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) - @build(true) + @build() getWatchedDirs: -> return @paths diff --git a/src/server.coffee b/src/server.coffee index b595d4d..2d675cd 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -49,7 +49,7 @@ server.middleware = (hem, options) -> for hemapp in hem.apps if pkg = hemapp.isMatchingRoute(url) # TODO: keep (and return) in memory build if there hasn't been any changes?? - str = pkg.build() + str = pkg.build(false) res.charset = 'utf-8' res.setHeader('Content-Type', mime.lookup(pkg.target)) res.setHeader('Content-Length', Buffer.byteLength(str)) From a6db04f674e8361b83ec2fcfc8d543627774a328 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 18 Jun 2013 19:41:27 -0500 Subject: [PATCH 029/167] starting refactoring of testing options --- assets/defaults/spine.json | 8 ++--- assets/testacular.conf.js | 62 -------------------------------------- lib/hem.js | 6 ++-- lib/test.js | 39 ++++++++++++++---------- package.json | 8 ++--- src/hem.coffee | 8 +++-- src/test.coffee | 42 ++++++++++++++------------ 7 files changed, 64 insertions(+), 109 deletions(-) delete mode 100644 assets/testacular.conf.js diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 4efa400..6f7a9f1 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -39,14 +39,14 @@ }, "test": { - "after": "test/after", - "identifier": "specs", + "identifier": "specs", "modules": [], "paths": [ "test/specs" ], - "target": "test/public/specs.js", - "type": "jasmine" + "target": "test/public/specs.js", + "test-type": "jasmine", + "test-runner": "karma" } } diff --git a/assets/testacular.conf.js b/assets/testacular.conf.js deleted file mode 100644 index fe750de..0000000 --- a/assets/testacular.conf.js +++ /dev/null @@ -1,62 +0,0 @@ -// Testacular configuration -// Generated on Fri Sep 21 2012 14:51:44 GMT-0500 (CDT) - - -// base path, that will be used to resolve files and exclude -basePath = ''; - - -// list of files / patterns to load in the browser -files = [ - JASMINE, - JASMINE_ADAPTER, - 'public/application.js', - 'test/public/specs.js' -]; - - -// list of files to exclude -exclude = [ - -]; - - -// test results reporter to use -// possible values: dots || progress -reporter = 'progress'; - - -// web server port -port = 9090; - - -// cli runner port -runnerPort = 9100; - - -// enable / disable colors in the output (reporters and logs) -colors = true; - - -// level of logging -// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG -logLevel = LOG_INFO; - - -// enable / disable watching file and executing tests whenever any file changes -autoWatch = true; - - -// Start these browsers, currently available: -// - Chrome -// - ChromeCanary -// - Firefox -// - Opera -// - Safari -// - PhantomJS -browsers = ['Chrome']; - - -// Continuous Integration mode -// if true, it capture browsers, run tests and exit -singleRun = false; diff --git a/lib/hem.js b/lib/hem.js index 3f0944a..2a2e6f2 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -173,7 +173,9 @@ var targets, testOptions; targets = argv.targets; - testOptions = {}; + testOptions = { + basePath: this.homeDir + }; if (argv.watch) { this.watch(); testOptions.singleRun = false; @@ -248,7 +250,7 @@ } return _results; }).call(this); - return testing.run(this, testApps, options); + return testing.run(testApps, options); }; Hem.prototype.buildTargets = function(targets) { diff --git a/lib/test.js b/lib/test.js index c02e29b..e854dc0 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,17 +1,25 @@ // Generated by CoffeeScript 1.6.2 (function() { - var createKarmaFileList, fs, start, startKarma; + var createKarmaFileList, fs, run, runKarma, runPhantomjs, utils; fs = require('fs'); - start = function(hem, hemapps, options) { + utils = require('./utils'); + + run = function(apps, options) { + if (options == null) { + options = {}; + } + return runKarma(apps, options); + }; + + runPhantomjs = function(apps, options) { if (options == null) { options = {}; } - return startKarma(hem, hemapps, options); }; - startKarma = function(hemapps, options) { + runKarma = function(apps, options) { var testConfig; if (options == null) { @@ -19,27 +27,26 @@ } testConfig = fs.existsSync(options.config) && fs.realpathSync(options.config); testConfig || (testConfig = { - configFile: require.resolve("../assets/testacular.conf.js"), singleRun: options.singleRun || true, - basePath: hem.homeDir, - logLevel: 'error', + basePath: options.basePath, + reporters: ['progress'], + logLevel: 'info', + frameworks: ['jasmine'], browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], - files: createKarmaFileList(hemapps) + files: createKarmaFileList(apps) }); return require('karma').server.start(testConfig); }; - createKarmaFileList = function(hemapps) { - var app, fileList, _i, _len; + createKarmaFileList = function(apps) { + var app, _i, _len; - fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), require.resolve("../node_modules/karma/adapter/jasmine.js")]; - for (_i = 0, _len = hemapps.length; _i < _len; _i++) { - app = hemapps[_i]; - fileList.push(app.test.target); + for (_i = 0, _len = apps.length; _i < _len; _i++) { + app = apps[_i]; + return [app.test.target, app.js.target]; } - return fileList; }; - module.exports.start = start; + module.exports.run = run; }).call(this); diff --git a/package.json b/package.json index 9a62a8a..2109eab 100644 --- a/package.json +++ b/package.json @@ -36,17 +36,17 @@ }, "preferGlobal": true, "dependencies": { - "eco": "1.1.0-rc-3", "uglify-js": "~1.3.3", "fast-detective": "~0.0.2", "optimist": "~0.4.0", - "stylus": "~0.32.0", "coffee-script": "~1.6.0", "watch": "~0.7.0", "connect": "~2.7.0", "http-proxy": "~0.10", - "karma": "~0.8.0", + "sty": "~0.6.1", "jade": "~0.30", - "sty": "~0.6.1" + "stylus": "~0.32.0", + "eco": "1.1.0-rc-3", + "karma": "~0.8.6" } } diff --git a/src/hem.coffee b/src/hem.coffee index b504a5f..2bb390a 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -133,13 +133,17 @@ class Hem test: -> targets = argv.targets - testOptions = {} + # set test options + testOptions = + basePath: @homeDir + # check for watch mode if argv.watch @watch() testOptions.singleRun = false else @buildTargets(targets) testOptions.singleRun = true + # run tests @testTargets(targets, testOptions) exec: (command = argv.command) -> @@ -161,7 +165,7 @@ class Hem testTargets: (targets = [], options = {}) -> testApps = (app for app in @getTargetApps(targets) when app.test) - testing.run(@, testApps, options) + testing.run(testApps, options) buildTargets: (targets = []) -> app.build() for app in @getTargetApps(targets) diff --git a/src/test.coffee b/src/test.coffee index 4edb0c6..5dcdf92 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -1,44 +1,48 @@ -fs = require('fs') +fs = require('fs') +utils = require('./utils') # ------- Public Functions -start = (hem, hemapps, options = {}) -> + # TODO: only ONE app at a time!!! + +run = (apps, options = {}) -> # TODO: is karam avaliable, use that, # - fall back to phantomjs # - otherwise just open file in browser?? - startKarma(hem, hemapps, options) + runKarma(apps, options) # ------- Test Functions -startKarma = (hemapps, options = {}) -> +runPhantomjs = (apps, options = {}) -> + # look at https://github.com/sgentle/phantomjs-node + # could spin up phantomjs and evaulate rendered page? + +runKarma = (apps, options = {}) -> # use custom testacular config file provided by user testConfig = fs.existsSync(options.config) and fs.realpathSync(options.config) # create config file to pass into server if user doesn't supply a file to use testConfig or= - configFile : require.resolve("../assets/testacular.conf.js") singleRun : options.singleRun or true - basePath : hem.homeDir - logLevel : 'error' + basePath : options.basePath + reporters : ['progress'] + logLevel : 'info' + frameworks : ['jasmine'] browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] - files : createKarmaFileList(hemapps) - + files : createKarmaFileList(apps) + # start testacular server require('karma').server.start(testConfig) -createKarmaFileList = (hemapps) -> - # TODO: other adapters? - # look at at test type to see what assets we add - fileList = [require.resolve("../node_modules/karma/adapter/lib/jasmine.js"), - require.resolve("../node_modules/karma/adapter/jasmine.js")] - - # loop over javascript hem applications and add their test targets - fileList.push app.test.target for app in hemapps - return fileList +createKarmaFileList = (apps) -> + for app in apps + return [app.test.target, app.js.target] + # TODO: need to add special js to load the specs in... should be a part of the jasmine type during builds, either that + # or don't use stitch on jasmine files, just concat.. # ------- Exports -module.exports.start = start +module.exports.run = run From 012132c41402c365a5373348aecbda0c69e234ca Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 25 Jun 2013 17:56:39 -0500 Subject: [PATCH 030/167] lots and lots and lots of changes, trying to get hem easier to configure, but at the same time allow a lot of customization --- assets/defaults/spine.json | 14 ++-- bin/hem | 2 +- lib/hem.js | 54 +++++++++---- lib/package.js | 154 +++++++++++++++++++++++++------------ lib/server.js | 53 ++++++------- lib/utils.js | 61 +++++++++++---- src/hem.coffee | 34 ++++---- src/package.coffee | 139 +++++++++++++++++++++++---------- src/server.coffee | 35 ++++----- src/utils.coffee | 40 +++++++--- 10 files changed, 389 insertions(+), 197 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 6f7a9f1..390517e 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -1,12 +1,16 @@ { "root": "", "route": "/", + "version": { + "type": "package", + "files": [ "public/index.html" ] + }, "css": { "paths": [ "css" ], - "target": "public/application.css" + "target": "public/appliation.css" }, "js": { @@ -14,9 +18,6 @@ "libs": [ "lib" ], - "after": [ - "after" - ], "modules": [ "jqueryify", "spine", @@ -40,13 +41,12 @@ "test": { "identifier": "specs", - "modules": [], "paths": [ "test/specs" ], "target": "test/public/specs.js", - "test-type": "jasmine", - "test-runner": "karma" + "type": "jasmine", + "runner": "karma" } } diff --git a/bin/hem b/bin/hem index 3c7af5f..f70a328 100755 --- a/bin/hem +++ b/bin/hem @@ -3,7 +3,7 @@ var Module = require('module'), fs = require('fs'); if (fs.existsSync('./slug.js')) { - return Module._load('./slug.js'); + Module._load('./slug.js'); } require('../lib/hem').exec(); diff --git a/lib/hem.js b/lib/hem.js index 2a2e6f2..69465a6 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -19,7 +19,7 @@ testing = require('./test'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all complications are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').describe('v', ':make hem more talkative(verbose)').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all complications are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').describe('v', ':make hem more talkative(verbose)').argv; argv.command = argv._[0]; @@ -65,7 +65,7 @@ var hem; hem = new Hem(slugFile); - return server.middleware(hem, hem.options.server); + return server.middleware(hem.apps, hem.options.server); }; Hem.prototype.compilers = compilers; @@ -73,7 +73,7 @@ Hem.prototype.homeDir = ''; Hem.prototype.options = { - server: { + hem: { port: 9294, host: "localhost" } @@ -108,28 +108,33 @@ utils.errorAndExit("Unable to find " + slug + " file in current directory"); } if (argv.port) { - this.options.server.port = argv.port; + this.options.hem.port = argv.port; } - (_base = this.options.server).host || (_base.host = ""); - (_base1 = this.options.server).routes || (_base1.routes = []); + (_base = this.options.hem).host || (_base.host = ""); + (_base1 = this.options.hem).routes || (_base1.routes = []); _ref1 = this.options; for (name in _ref1) { config = _ref1[name]; - if (name === "server") { + if (name === "hem") { continue; } + config.hem = this.options.hem; this.apps.push(application.createApplication(name, config)); } } Hem.prototype.server = function() { - utils.log("Starting Server at http://" + (this.options.server.host || "localhost") + ":" + this.options.server.port + ""); - return server.start(this, this.options.server); + var value; + + value = "http://" + (this.options.hem.host || "localhost") + ":" + this.options.hem.port; + utils.log("Starting Server at " + value + ""); + return server.start(this.apps, this.options.hem); }; Hem.prototype.clean = function() { var app, cleanAll, targets, _i, _len, _ref, _ref1, _results; + utils.log("Clean applications..."); targets = argv.targets; cleanAll = targets.length === 0; _ref = this.apps; @@ -186,6 +191,30 @@ return this.testTargets(targets, testOptions); }; + Hem.prototype.check = function() { + var app, printOptions, targetAll, targets, _i, _len, _ref, _ref1, _results; + + targets = argv.targets; + targetAll = targets.length === 0; + _ref = this.apps; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + app = _ref[_i]; + if (!((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || targetAll)) { + continue; + } + utils.log("> Configuration values for " + app.name + ""); + printOptions = { + showHidden: false, + colors: !argv.nocolors, + depth: null + }; + console.log(require('util').inspect(app, printOptions)); + _results.push(utils.log("")); + } + return _results; + }; + Hem.prototype.exec = function(command) { if (command == null) { command = argv.command; @@ -193,13 +222,6 @@ if (!this[command]) { return help(); } - switch (command) { - case 'test': - utils.log('Test application'); - break; - case 'clean': - utils.log('Clean application'); - } return this[command](); }; diff --git a/lib/package.js b/lib/package.js index 27a43df..f2739a7 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,7 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, createApplication, fs, path, stitchFile, uglify, utils, versions, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglify, utils, versions, + __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -10,8 +11,6 @@ uglify = require('uglify-js'); - stitchFile = require('../assets/stitch'); - Dependency = require('./dependency'); Stitch = require('./stitch'); @@ -22,15 +21,18 @@ Application = (function() { function Application(name, config) { - var defaults, err, route, value, _base, _ref, _ref1; + var defaults, err, files, key, packager, pkg, route, value, _base, _ref, _ref1, _ref2, + _this = this; if (config == null) { config = {}; } this.name = name; + this.route = config.route; + this.root = config.root; if (config.defaults) { try { - defaults = require('../assets/defaults/' + config.defaults); + defaults = utils.loadAsset('defaults/' + config.defaults); } catch (_error) { err = _error; console.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); @@ -39,31 +41,51 @@ utils.log(((_ref = global.ARGV) != null ? _ref.v : void 0) ? "Applying '" + config.defaults + "' defaults to configuration..." : void 0); config = utils.extend(defaults, config); } - this.root = config.root || ""; - this.route = config.route || "/"; - this.packages = []; - this["static"] = {}; - _ref1 = config["static"]; - for (route in _ref1) { - value = _ref1[route]; - this["static"][utils.cleanRoute(this.route, route)] = value; - } - if (config.js) { - this.js = new JsPackage(this, config.js); - this.packages.push(this.js); + if (!this.root) { + if (utils.isDirectory(this.name)) { + this.root = this.name; + this.route || (this.route = this.applyBaseRoute("/" + this.name)); + } else { + this.root = ""; + } } - if (config.css) { - this.css = new CssPackage(this, config.css); - this.packages.push(this.css); + this.route || (this.route = this.applyBaseRoute("/")); + this.route = this.applyBaseRoute((_ref1 = config.hem) != null ? _ref1.baseAppRoute : void 0, this.route); + this["static"] = {}; + this.packages = {}; + _ref2 = config["static"]; + for (route in _ref2) { + value = _ref2[route]; + this["static"][this.applyBaseRoute(this.route, route)] = this.applyRootDir(value)[0]; + } + for (key in config) { + value = config[key]; + packager = void 0; + if (key === 'js' || utils.endsWith(key, '.js')) { + packager = JsPackage; + value.name = key; + } else if (key === 'css' || utils.endsWith(key, '.css')) { + packager = CssPackage; + value.name = key; + } + if (packager) { + pkg = new packager(this, value); + this.packages[pkg.name] = pkg; + } } if (config.test) { - this.test = new JsPackage(this, config.test); - this.packages.push(this.test); + config.test.name = "test"; + this.packages.test = new TestPackage(this, config.test); } this.versioning = config.version || void 0; if (this.versioning) { (_base = this.versioning).type || (_base.type = "package"); this.versioning.module = versions[this.versioning.type]; + files = utils.toArray(this.versioning.files); + files = files.map(function(file) { + return _this.applyRootDir(file)[0]; + }); + this.versioning.files = files; if (!this.versioning.module) { utils.errorAndExit("Incorrect type value for versioning (" + this.versioning.type + ")"); } @@ -132,29 +154,54 @@ } }; + Application.prototype.applyRootDir = function() { + var values, + _this = this; + + values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + if (this.root !== "") { + values = values.map(function(value) { + return utils.cleanPath(_this.root, value); + }); + } + return values; + }; + + Application.prototype.applyBaseRoute = function() { + var values; + + values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + return utils.cleanRoute.apply(utils, values); + }; + return Application; })(); Package = (function() { function Package(parent, config) { - var regexp, route, targetUrl, value, _ref; + var regexp, route, targetFile, targetUrl, value, _ref; - if (config == null) { - config = {}; - } this.parent = parent; - this.paths = utils.toArray(config.paths || []); - if (this.parent.root.length > 0) { - this.target = utils.cleanPath(parent.root, config.target); - } else { - this.target = config.target; + this.name = config.name; + this.paths = this.parent.applyRootDir(config.paths); + this.target = this.parent.applyRootDir(config.target || "")[0]; + if (utils.isDirectory(this.target)) { + if (this.name === this.ext) { + targetFile = parent.name; + } else { + targetFile = this.name; + } + this.target = utils.cleanPath(this.target, targetFile); + } + if (!utils.endsWith(this.target, "." + this.ext)) { + this.target = "" + this.target + "." + this.ext; } if (config.route) { if (utils.startsWith(this.target, "/")) { this.route = config.route; } else { - this.route = utils.cleanRoute(parent.route, config.route); + this.route = this.parent.applyBaseRoute(parent.route, config.route); } } else { _ref = this.parent["static"]; @@ -164,7 +211,7 @@ if (utils.startsWith(this.target, value)) { regexp = new RegExp("^" + value); targetUrl = this.target.replace(regexp, ""); - this.route = utils.cleanRoute(route, targetUrl); + this.route = this.parent.applyBaseRoute(route, targetUrl); } } } @@ -244,6 +291,8 @@ return this.paths; }; + Package.prototype.ext = ""; + return Package; })(); @@ -252,23 +301,18 @@ __extends(JsPackage, _super); function JsPackage(parent, config) { - if (config == null) { - config = {}; - } - config.target || (config.target = parent.name + ".js"); JsPackage.__super__.constructor.call(this, parent, config); this.identifier = config.identifier || 'require'; - this.libs = utils.toArray(config.libs || []); + this.libs = this.parent.applyRootDir(config.libs); + this.after = config.after || ""; this.modules = utils.toArray(config.modules || []); - this.after = utils.toArray(config.after || []); - this.testType = config.test || void 0; } JsPackage.prototype.compile = function() { var ex, result; try { - result = [this.compileLibs(), this.compileModules(), this.compileLibs(this.after)].join("\n"); + result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); if (utils.COMPRESS) { result = uglify(result); } @@ -280,12 +324,13 @@ }; JsPackage.prototype.compileModules = function() { - var _modules, _stitch; + var _modules, _stitch, _template; this.depend || (this.depend = new Dependency(this.modules)); _stitch = new Stitch(this.paths); _modules = this.depend.resolve().concat(_stitch.resolve()); - return stitchFile({ + _template = utils.loadAsset('stitch'); + return _template({ identifier: this.identifier, modules: _modules }); @@ -319,21 +364,32 @@ }; JsPackage.prototype.getWatchedDirs = function() { - return this.paths.concat(this.libs.concat(this.after)); + return this.paths.concat(this.libs); }; + JsPackage.prototype.ext = "js"; + return JsPackage; })(Package); + TestPackage = (function(_super) { + __extends(TestPackage, _super); + + function TestPackage(parent, config) { + TestPackage.__super__.constructor.call(this, parent, config); + this.type = config.type; + this.runner = config.runner; + } + + return TestPackage; + + })(JsPackage); + CssPackage = (function(_super) { __extends(CssPackage, _super); function CssPackage(parent, config) { - if (config == null) { - config = {}; - } - config.target || (config.target = parent.name + ".css"); CssPackage.__super__.constructor.call(this, parent, config); } @@ -356,6 +412,8 @@ } }; + CssPackage.prototype.ext = "css"; + return CssPackage; })(Package); diff --git a/lib/server.js b/lib/server.js index d2d54ce..3194cd4 100644 --- a/lib/server.js +++ b/lib/server.js @@ -16,24 +16,23 @@ server = {}; - server.start = function(hem, options) { + server.start = function(applications, options) { var app; app = connect(); - app.use(server.middleware(hem, options)); + app.use(server.middleware(applications, options)); return http.createServer(app).listen(options.port, options.host); }; - server.middleware = function(hem, options) { + server.middleware = function(applications, options) { var hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; - _ref = hem.apps; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - hemapp = _ref[_i]; + for (_i = 0, _len = applications.length; _i < _len; _i++) { + hemapp = applications[_i]; utils.info("> Apply route mappings for application: " + hemapp.name + ""); - _ref1 = hemapp.packages; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - pkg = _ref1[_j]; + _ref = hemapp.packages; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + pkg = _ref[_j]; utils.info("- Mapping route " + pkg.route + " to " + pkg.target + ""); } if (hemapp["static"]) { @@ -41,31 +40,29 @@ } } statics = connect(); - _ref2 = options.routes; - for (route in _ref2) { - value = _ref2[route]; - if (typeof value === 'string') { - if (fs.existsSync(value)) { - utils.info("- Mapping static " + route + " to " + value + ""); - statics.use(route, connect["static"](value)); - } else { - utils.errorAndExit("The folder " + value + " does not exist."); - } - } else if (value.host) { - utils.info("- Proxy requests " + route + " to " + value.host + ":" + (value.port || 80) + value.hostPath + ""); - statics.use(route, createRoutingProxy(value)); + _ref1 = options.routes; + for (route in _ref1) { + value = _ref1[route]; + if (fs.existsSync(value)) { + utils.info("- Mapping static " + route + " to " + value + ""); + statics.use(route, connect["static"](value)); } else { - utils.errorAndExit("Invalid route configuration for " + route + ""); + utils.errorAndExit("The folder " + value + " does not exist."); } } + _ref2 = options.proxy; + for (route in _ref2) { + value = _ref2[route]; + utils.info("- Proxy requests " + route + " to " + value + ""); + statics.use(route, createRoutingProxy(value)); + } return function(req, res, next) { - var str, url, _k, _len2, _ref3, _ref4; + var str, url, _k, _len2, _ref3; url = ((_ref3 = require("url").parse(req.url)) != null ? _ref3.pathname.toLowerCase() : void 0) || ""; - if (url.match(/\.js|\.css/)) { - _ref4 = hem.apps; - for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) { - hemapp = _ref4[_k]; + if (url.match(/(\.js|\.css)$/)) { + for (_k = 0, _len2 = applications.length; _k < _len2; _k++) { + hemapp = applications[_k]; if (pkg = hemapp.isMatchingRoute(url)) { str = pkg.build(false); res.charset = 'utf-8'; diff --git a/lib/utils.js b/lib/utils.js index 82b49c0..c2cc4bf 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,12 +1,14 @@ // Generated by CoffeeScript 1.6.2 (function() { - var clean, extend, flatten, path, sty, utils, + var clean, extend, flatten, fs, path, sty, utils, __slice = [].slice; sty = require('sty'); path = require('path'); + fs = require('fs'); + utils = {}; utils.flatten = flatten = function(array, results) { @@ -38,11 +40,11 @@ }; utils.startsWith = function(str, value) { - return str.slice(0, value.length) === value; + return (str != null ? str.slice(0, value.length) : void 0) === value; }; utils.endsWith = function(str, value) { - return str.slice(-value.length) === value; + return (str != null ? str.slice(-value.length) : void 0) === value; }; utils.extend = extend = function(a, b) { @@ -59,33 +61,60 @@ return a; }; - utils.cleanPath = function() { - var paths; - - paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return clean(paths, path.sep); + utils.loadAsset = function(asset) { + return require("../assets/" + asset); }; - utils.cleanRoute = function() { - var routes; + utils.isDirectory = function(dir) { + var e, stats; - routes = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return clean(routes, "/"); + try { + stats = fs.lstatSync(dir); + return stats.isDirectory(); + } catch (_error) { + e = _error; + return false; + } }; - clean = function(values, sep) { + clean = function(values, sep, trimStart) { var regexp, result, value, _i, _len; + if (trimStart == null) { + trimStart = false; + } result = ""; for (_i = 0, _len = values.length; _i < _len; _i++) { value = values[_i]; - result = result + sep + value; + if (value) { + result = result + sep + value; + } } regexp = new RegExp("" + sep + "+", "g"); result = result.replace(regexp, sep); + if (trimStart && utils.startsWith(result, sep)) { + result = result.slice(sep.length); + } + if (utils.endsWith(result, sep)) { + result = result.slice(0, -sep.length); + } return result; }; + utils.cleanPath = function() { + var paths; + + paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + return clean(paths, path.sep, true); + }; + + utils.cleanRoute = function() { + var routes; + + routes = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + return clean(routes, "/"); + }; + utils.log = function(message) { return console.log(sty.parse(message)); }; @@ -105,6 +134,10 @@ return process.exit(1); }; + utils.parse = function(message) { + return sty.parse(message); + }; + module.exports = utils; }).call(this); diff --git a/src/hem.coffee b/src/hem.coffee index 2bb390a..4db5003 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -17,6 +17,7 @@ argv = optimist.usage([ ' test :build and run tests' ' clean :clean compiled targets' ' version :version the application files' + ' check :check slug file values' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') .alias('c', 'compress').describe('c',':all complications are compressed/minified') @@ -60,7 +61,7 @@ class Hem @middleware: (slugFile) -> hem = new Hem(slugFile) - server.middleware(hem, hem.options.server) + server.middleware(hem.apps, hem.options.server) # ------- instance variables @@ -71,7 +72,7 @@ class Hem # default values for server options: - server: + hem: port: 9294 host: "localhost" @@ -98,22 +99,25 @@ class Hem utils.errorAndExit "Unable to find #{slug} file in current directory" # allow overrides and set defaults - @options.server.port = argv.port if argv.port - @options.server.host or= "" - @options.server.routes or= [] + @options.hem.port = argv.port if argv.port + @options.hem.host or= "" + @options.hem.routes or= [] # setup applications from options/slug for name, config of @options - continue if name is "server" + continue if name is "hem" + config.hem = @options.hem @apps.push application.createApplication(name, config) # ------- Command Functions server: -> - utils.log "Starting Server at http://#{@options.server.host or "localhost"}:#{@options.server.port}" - server.start(@, @options.server) + value = "http://#{@options.hem.host or "localhost"}:#{@options.hem.port}" + utils.log "Starting Server at #{value}" + server.start(@apps, @options.hem) clean: -> + utils.log "Clean applications..." targets = argv.targets cleanAll = targets.length is 0 app.unlink() for app in @apps when app.name in targets or cleanAll @@ -146,11 +150,17 @@ class Hem # run tests @testTargets(targets, testOptions) + check: -> + targets = argv.targets + targetAll = targets.length is 0 + for app in @apps when app.name in targets or targetAll + utils.log "> Configuration values for #{app.name}" + printOptions = showHidden: false, colors: !argv.nocolors, depth: null + console.log(require('util').inspect(app, printOptions)) + utils.log "" + exec: (command = argv.command) -> return help() unless @[command] - switch command - when 'test' then utils.log 'Test application' - when 'clean' then utils.log 'Clean application' @[command]() # ------- Private Functions @@ -173,7 +183,5 @@ class Hem versionTargets: (targets = []) -> app.version() for app in @getTargetApps(targets) - - module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index f2f8494..1796406 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -1,7 +1,6 @@ fs = require('fs') path = require('path') uglify = require('uglify-js') -stitchFile = require('../assets/stitch') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') @@ -12,49 +11,73 @@ versions = require('./versioning') class Application constructor: (name, config = {}) -> @name = name + @route = config.route + @root = config.root # apply defaults if (config.defaults) try - defaults = require('../assets/defaults/' + config.defaults) + defaults = utils.loadAsset('defaults/' + config.defaults) catch err console.error "ERROR: Invalid 'defaults' value provided: " + config.defaults process.exit 1 utils.log("Applying '" + config.defaults + "' defaults to configuration..." if global.ARGV?.v) config = utils.extend(defaults, config) - # set variables - @root = config.root or "" - @route = config.route or "/" + # set root variable + unless @root + # if name is also directory assume that is root + if utils.isDirectory(@name) + @root = @name + @route or= @applyBaseRoute("/#{@name}") + # otherwise just work from top level directory + else + @root = "" - # create packages - @packages = [] + # make sure route has a value + @route or= @applyBaseRoute("/") + @route = @applyBaseRoute(config.hem?.baseAppRoute, @route) @static = {} - + @packages = {} + # configure static routes for route, value of config.static - @static[utils.cleanRoute(@route, route)] = value + @static[@applyBaseRoute(@route, route)] = @applyRootDir(value)[0] # configure js/css/test packages - if config.js - @js = new JsPackage(@,config.js) - @packages.push @js - if config.css - @css = new CssPackage(@,config.css) - @packages.push @css + for key, value of config + packager = undefined + # determine package type + if key is 'js' or utils.endsWith(key,'.js') + packager = JsPackage + value.name = key + else if key is 'css' or utils.endsWith(key,'.css') + packager = CssPackage + value.name = key + # add to @packages array + if packager + pkg = new packager(@, value) + @packages[pkg.name] = pkg + + # configure test structure if config.test - @test = new JsPackage(@,config.test) - @packages.push @test + config.test.name = "test" + @packages.test = new TestPackage(@, config.test) # configure versioning @versioning = config.version or undefined if @versioning + # TODO: move to a consturctor!! <<<<, need to make this an instance of a class!!! << @versioning.type or= "package" @versioning.module = versions[@versioning.type] + # update file paths + files = utils.toArray(@versioning.files) + files = files.map (file) => + @applyRootDir(file)[0] + @versioning.files = files if not (@versioning.module) utils.errorAndExit "Incorrect type value for versioning (#{@versioning.type})" - isMatchingRoute: (route) -> # strip out any versioning applied to file if @versioning @@ -83,31 +106,53 @@ class Application else utils.errorAndExit "ERROR: Versioning not enabled in slug.json" + applyRootDir: (values...) -> + if @root isnt "" + values = values.map (value) => + utils.cleanPath(@root, value) + values + + applyBaseRoute: (values...) -> + utils.cleanRoute.apply(utils, values) + class Package - constructor: (parent, config = {}) -> + + constructor: (parent, config) -> @parent = parent - @paths = utils.toArray(config.paths or []) + @name = config.name + @paths = @parent.applyRootDir(config.paths) + @target = @parent.applyRootDir(config.target or "")[0] + + # TODO: something fishy here <<<<<<< # determine target filename - if @parent.root.length > 0 - @target = utils.cleanPath(parent.root, config.target) - else - @target = config.target + if utils.isDirectory(@target) + # determine actual file name + if @name is @ext + targetFile = parent.name + else + targetFile = @name + @target = utils.cleanPath(@target, targetFile) + + # make sure correct extension is present + unless utils.endsWith(@target, ".#{@ext}") + @target = "#{@target}.#{@ext}" # determine url if config.route if utils.startsWith(@target,"/") @route = config.route else - @route = utils.cleanRoute(parent.route, config.route) + @route = @parent.applyBaseRoute(parent.route, config.route) else # use the static urls to determine the package @route for route, value of @parent.static when not @route if utils.startsWith(@target, value) regexp = new RegExp("^#{value}") targetUrl = @target.replace(regexp,"") - @route = utils.cleanRoute(route, targetUrl) - # make sure we have a route to use + @route = @parent.applyBaseRoute(route, targetUrl) + + # make sure we have a route to use when using server command if utils.COMMAND is "server" utils.errorAndExit("Unable to determine route for #{@target}") unless @route @@ -132,7 +177,7 @@ class Package source = @compile() fs.writeFileSync(@target, source) if source and write source - + watch: -> for dir in @getWatchedDirs() continue unless fs.existsSync(dir) @@ -143,28 +188,25 @@ class Package getWatchedDirs: -> return @paths + ext: "" + # ------- Child Classes class JsPackage extends Package - constructor: (parent, config = {}) -> - config.target or= parent.name + ".js" + constructor: (parent, config) -> + # call parent super(parent, config) # javascript only configurations @identifier = config.identifier or 'require' - @libs = utils.toArray(config.libs or []) + @libs = @parent.applyRootDir(config.libs) + @after = config.after or "" @modules = utils.toArray(config.modules or []) - @after = utils.toArray(config.after or []) - - # for testing types - # TODO: or have test libs to pull test files from? woulnd't need after stuff if we did that?? - # TODO: testLibs = ['jasmine'] or ['test/public/lib'] - @testType = config.test or undefined compile: -> try - result = [@compileLibs(), @compileModules(), @compileLibs(@after)].join("\n") + result = [@compileLibs(), @compileModules(), @after].join("\n") result = uglify(result) if utils.COMPRESS result catch ex @@ -176,7 +218,8 @@ class JsPackage extends Package @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) _modules = @depend.resolve().concat(_stitch.resolve()) - stitchFile(identifier: @identifier, modules: _modules) + _template = utils.loadAsset('stitch') + _template(identifier: @identifier, modules: _modules) compileLibs: (files = @libs, parentDir = "") -> # check if folder or file @@ -195,12 +238,22 @@ class JsPackage extends Package results.join("\n") getWatchedDirs: -> - @paths.concat @libs.concat @after + @paths.concat @libs + + ext: "js" + +class TestPackage extends JsPackage + + constructor: (parent, config) -> + super(parent, config) + # TODO: use after in default spine json to setup specs... + # TODO: testLibs = ['jasmine'] or ['test/public/lib'] + @type = config.type + @runner = config.runner class CssPackage extends Package - constructor: (parent, config = {}) -> - config.target or= parent.name + ".css" + constructor: (parent, config) -> super(parent, config) compile: () -> @@ -218,6 +271,8 @@ class CssPackage extends Package catch ex @handleCompileError(ex) + ext: "css" + # ------- Public Functions createApplication = (name, config) -> diff --git a/src/server.coffee b/src/server.coffee index 2d675cd..5f7894c 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -8,36 +8,33 @@ server = {} # ------- Public Functions -server.start = (hem, options) -> +server.start = (applications, options) -> app = connect() - app.use(server.middleware(hem, options)) + app.use(server.middleware(applications, options)) http.createServer(app).listen(options.port, options.host) -server.middleware = (hem, options) -> +server.middleware = (applications, options) -> # determine if there is any dynamic or static routes to add - for hemapp in hem.apps + for hemapp in applications utils.info "> Apply route mappings for application: #{hemapp.name}" for pkg in hemapp.packages utils.info "- Mapping route #{pkg.route} to #{pkg.target}" if hemapp.static options.routes = utils.extend(hemapp.static, options.routes) - # setup static routes and proxy middleware + # setup separate connect app for static routes and proxy middleware statics = connect() for route, value of options.routes - # setup static route - if (typeof value is 'string') - if fs.existsSync(value) - utils.info "- Mapping static #{route} to #{value}" - statics.use(route, connect.static(value)) - else - utils.errorAndExit "The folder #{value} does not exist." - # setup proxy route - else if value.host - utils.info "- Proxy requests #{route} to #{value.host}:#{value.port or 80}#{value.hostPath}" - statics.use(route, createRoutingProxy(value)) + if fs.existsSync(value) + utils.info "- Mapping static #{route} to #{value}" + statics.use(route, connect.static(value)) else - utils.errorAndExit("Invalid route configuration for #{route}") + utils.errorAndExit "The folder #{value} does not exist." + + # setup proxy route + for route, value of options.proxy + utils.info "- Proxy requests #{route} to #{value}" + statics.use(route, createRoutingProxy(value)) # return the custom middleware for connect to use return (req, res, next) -> @@ -45,8 +42,8 @@ server.middleware = (hem, options) -> url = require("url").parse(req.url)?.pathname.toLowerCase() or "" # loop over hem applications and call compile when there is a match - if url.match(/\.js|\.css/) - for hemapp in hem.apps + if url.match(/(\.js|\.css)$/) + for hemapp in applications if pkg = hemapp.isMatchingRoute(url) # TODO: keep (and return) in memory build if there hasn't been any changes?? str = pkg.build(false) diff --git a/src/utils.coffee b/src/utils.coffee index e9bf66d..cd207d8 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,5 +1,6 @@ sty = require('sty') path = require('path') +fs = require('fs') utils = {} utils.flatten = flatten = (array, results = []) -> @@ -14,10 +15,10 @@ utils.toArray = (value = []) -> if Array.isArray(value) then value else [value] utils.startsWith = (str, value) -> - str.slice(0, value.length) is value + str?.slice(0, value.length) is value utils.endsWith = (str, value) -> - str.slice(-value.length) is value + str?.slice(-value.length) is value utils.extend = extend = (a, b) -> for x of b @@ -28,23 +29,41 @@ utils.extend = extend = (a, b) -> a[x] = b[x] return a -utils.cleanPath = (paths...) -> - clean(paths, path.sep) +utils.loadAsset = (asset) -> + return require("../assets/" + asset) -utils.cleanRoute = (routes...) -> - clean(routes, "/") +utils.isDirectory = (dir) -> + try + stats = fs.lstatSync(dir) + stats.isDirectory() + catch e + false -clean = (values, sep) -> +# ------ Formatting urls and folder paths + +clean = (values, sep, trimStart = false) -> result = "" - for value in values + for value in values when value result = result + sep + value # clean duplicate sep regexp = new RegExp "#{sep}+","g" result = result.replace(regexp, sep) + # trim the starting path sep if there is one + if trimStart and utils.startsWith(result, sep) + result = result.slice(sep.length) # make sure doesn't end in sep + if utils.endsWith(result, sep) + result = result.slice(0, -sep.length) result - +utils.cleanPath = (paths...) -> + clean(paths, path.sep, true) + +utils.cleanRoute = (routes...) -> + clean(routes, "/") + + + # ------ Logging Helpers utils.log = (message) -> @@ -60,4 +79,7 @@ utils.errorAndExit = (error) -> utils.error(error) process.exit(1) +utils.parse = (message) -> + sty.parse(message) + module.exports = utils From bdb7c190cc87ad09b44b3034feed2fb60f383f84 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 26 Jun 2013 16:52:55 -0500 Subject: [PATCH 031/167] building and server seem to be functional again --- assets/defaults/spine.json | 3 +- lib/hem.js | 43 ++++++++++++------------ lib/package.js | 69 ++++++++++++++++++++------------------ lib/server.js | 14 ++++---- lib/utils.js | 2 +- src/hem.coffee | 20 +++++++---- src/package.coffee | 37 ++++++++++---------- src/server.coffee | 17 +++++----- src/utils.coffee | 2 +- 9 files changed, 109 insertions(+), 98 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 390517e..ab60730 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -10,7 +10,7 @@ "paths": [ "css" ], - "target": "public/appliation.css" + "target": "public/application.css" }, "js": { @@ -41,6 +41,7 @@ "test": { "identifier": "specs", + "libs": [], "paths": [ "test/specs" ], diff --git a/lib/hem.js b/lib/hem.js index 69465a6..867eb59 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -82,7 +82,7 @@ Hem.prototype.apps = []; function Hem(options) { - var config, key, name, slug, value, _base, _base1, _ref, _ref1; + var config, name, slug, _base, _base1, _ref; if (options == null) { options = {}; @@ -91,17 +91,13 @@ slug = options; } else { slug = argv.slug || './slug.json'; - for (key in options) { - value = options[key]; - this.options[key] = value; + if (options) { + this.options = utils.extend(options, this.options); } } if (fs.existsSync(slug)) { - _ref = this.readSlug(slug); - for (key in _ref) { - value = _ref[key]; - this.options[key] = value; - } + options = this.readSlug(slug); + this.options = utils.extend(options, this.options); this.homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)); process.chdir(this.homeDir); } else { @@ -111,10 +107,10 @@ this.options.hem.port = argv.port; } (_base = this.options.hem).host || (_base.host = ""); - (_base1 = this.options.hem).routes || (_base1.routes = []); - _ref1 = this.options; - for (name in _ref1) { - config = _ref1[name]; + (_base1 = this.options.hem).routes || (_base1.routes = {}); + _ref = this.options; + for (name in _ref) { + config = _ref[name]; if (name === "hem") { continue; } @@ -134,7 +130,6 @@ Hem.prototype.clean = function() { var app, cleanAll, targets, _i, _len, _ref, _ref1, _results; - utils.log("Clean applications..."); targets = argv.targets; cleanAll = targets.length === 0; _ref = this.apps; @@ -192,8 +187,17 @@ }; Hem.prototype.check = function() { - var app, printOptions, targetAll, targets, _i, _len, _ref, _ref1, _results; + var app, inspect, printOptions, targetAll, targets, _i, _len, _ref, _ref1, _results; + printOptions = { + showHidden: false, + colors: !argv.nocolors, + depth: null + }; + inspect = require('util').inspect; + utils.log("> Configuration for hem:"); + console.log(inspect(this.options.hem, printOptions)); + utils.log(""); targets = argv.targets; targetAll = targets.length === 0; _ref = this.apps; @@ -203,13 +207,8 @@ if (!((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || targetAll)) { continue; } - utils.log("> Configuration values for " + app.name + ""); - printOptions = { - showHidden: false, - colors: !argv.nocolors, - depth: null - }; - console.log(require('util').inspect(app, printOptions)); + utils.log("> Configuration values for " + app.name + ":"); + console.log(inspect(app, printOptions)); _results.push(utils.log("")); } return _results; diff --git a/lib/package.js b/lib/package.js index f2739a7..9b4d64f 100644 --- a/lib/package.js +++ b/lib/package.js @@ -21,7 +21,7 @@ Application = (function() { function Application(name, config) { - var defaults, err, files, key, packager, pkg, route, value, _base, _ref, _ref1, _ref2, + var defaults, err, files, key, packager, pkg, route, value, _base, _ref, _ref1, _this = this; if (config == null) { @@ -38,7 +38,6 @@ console.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); process.exit(1); } - utils.log(((_ref = global.ARGV) != null ? _ref.v : void 0) ? "Applying '" + config.defaults + "' defaults to configuration..." : void 0); config = utils.extend(defaults, config); } if (!this.root) { @@ -50,12 +49,12 @@ } } this.route || (this.route = this.applyBaseRoute("/")); - this.route = this.applyBaseRoute((_ref1 = config.hem) != null ? _ref1.baseAppRoute : void 0, this.route); + this.route = this.applyBaseRoute((_ref = config.hem) != null ? _ref.baseAppRoute : void 0, this.route); this["static"] = {}; this.packages = {}; - _ref2 = config["static"]; - for (route in _ref2) { - value = _ref2[route]; + _ref1 = config["static"]; + for (route in _ref1) { + value = _ref1[route]; this["static"][this.applyBaseRoute(this.route, route)] = this.applyRootDir(value)[0]; } for (key in config) { @@ -93,14 +92,14 @@ } Application.prototype.isMatchingRoute = function(route) { - var pkg, _i, _len, _ref; + var name, pkg, _ref; if (this.versioning) { route = this.versioning.module.trimVersion(route); } _ref = this.packages; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; + for (name in _ref) { + pkg = _ref[name]; if (route === pkg.route) { return pkg; } @@ -108,38 +107,39 @@ }; Application.prototype.unlink = function() { - var pkg, _i, _len, _ref, _results; + var key, pkg, _ref, _results; + utils.log("Removing application targets: " + this.name + ""); _ref = this.packages; _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; + for (key in _ref) { + pkg = _ref[key]; _results.push(pkg.unlink()); } return _results; }; Application.prototype.build = function() { - var pkg, _i, _len, _ref, _results; + var key, pkg, _ref, _results; - utils.log("Building application: " + this.name + ""); + utils.log("Building application targets: " + this.name + ""); _ref = this.packages; _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; + for (key in _ref) { + pkg = _ref[key]; _results.push(pkg.build()); } return _results; }; Application.prototype.watch = function() { - var pkg, _i, _len, _ref, _results; + var key, pkg, _ref, _results; utils.log("Watching application: " + this.name + ""); _ref = this.packages; _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; + for (key in _ref) { + pkg = _ref[key]; _results.push(pkg.watch()); } return _results; @@ -154,16 +154,14 @@ } }; - Application.prototype.applyRootDir = function() { + Application.prototype.applyRootDir = function(value) { var values, _this = this; - values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - if (this.root !== "") { - values = values.map(function(value) { - return utils.cleanPath(_this.root, value); - }); - } + values = utils.toArray(value); + values = values.map(function(value) { + return utils.cleanPath(_this.root, value); + }); return values; }; @@ -184,7 +182,7 @@ this.parent = parent; this.name = config.name; - this.paths = this.parent.applyRootDir(config.paths); + this.paths = this.parent.applyRootDir(config.paths || ""); this.target = this.parent.applyRootDir(config.target || "")[0]; if (utils.isDirectory(this.target)) { if (this.name === this.ext) { @@ -245,6 +243,7 @@ Package.prototype.unlink = function() { if (fs.existsSync(this.target)) { + utils.info("- removing " + this.target + ""); return fs.unlinkSync(this.target); } }; @@ -303,7 +302,7 @@ function JsPackage(parent, config) { JsPackage.__super__.constructor.call(this, parent, config); this.identifier = config.identifier || 'require'; - this.libs = this.parent.applyRootDir(config.libs); + this.libs = this.parent.applyRootDir(config.libs || []); this.after = config.after || ""; this.modules = utils.toArray(config.modules || []); } @@ -329,11 +328,15 @@ this.depend || (this.depend = new Dependency(this.modules)); _stitch = new Stitch(this.paths); _modules = this.depend.resolve().concat(_stitch.resolve()); - _template = utils.loadAsset('stitch'); - return _template({ - identifier: this.identifier, - modules: _modules - }); + if (_modules) { + _template = utils.loadAsset('stitch'); + return _template({ + identifier: this.identifier, + modules: _modules + }); + } else { + return ""; + } }; JsPackage.prototype.compileLibs = function(files, parentDir) { diff --git a/lib/server.js b/lib/server.js index 3194cd4..93a4493 100644 --- a/lib/server.js +++ b/lib/server.js @@ -25,7 +25,7 @@ }; server.middleware = function(applications, options) { - var hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; + var display, hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; for (_i = 0, _len = applications.length; _i < _len; _i++) { hemapp = applications[_i]; @@ -53,7 +53,8 @@ _ref2 = options.proxy; for (route in _ref2) { value = _ref2[route]; - utils.info("- Proxy requests " + route + " to " + value + ""); + display = "" + value.host + ":" + (value.port || 80) + value.path; + utils.info("- Proxy requests " + route + " to " + display + ""); statics.use(route, createRoutingProxy(value)); } return function(req, res, next) { @@ -80,12 +81,9 @@ createRoutingProxy = function(options) { var proxy; - if (options == null) { - options = {}; - } proxy = new httpProxy.RoutingProxy(); - options.hostPath || (options.hostPath = ""); - options.port || (options.port = 80); + options.path || (options.path = ""); + options.port || (options.port = url.port || 80); options.patchRedirect || (options.patchRedirect = true); if (options.patchRedirect) { proxy.once("start", function(req, res) { @@ -96,7 +94,7 @@ }); } return function(req, res, next) { - req.url = "" + options.hostPath + req.url; + req.url = "" + options.path + req.url; return proxy.proxyRequest(req, res, options); }; }; diff --git a/lib/utils.js b/lib/utils.js index c2cc4bf..4e04698 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -21,7 +21,7 @@ item = array[_i]; if (Array.isArray(item)) { flatten(item, results); - } else { + } else if (item) { results.push(item); } } diff --git a/src/hem.coffee b/src/hem.coffee index 4db5003..3776e47 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -87,11 +87,12 @@ class Hem slug = options else slug = argv.slug or './slug.json' - @options[key] = value for key, value of options + @options = utils.extend(options, @options) if options # quick check to make sure slug file exists if fs.existsSync(slug) - @options[key] = value for key, value of @readSlug(slug) + options = @readSlug(slug) + @options = utils.extend(options, @options) # make sure we are in same directory as slug @homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)) process.chdir(@homeDir) @@ -101,7 +102,7 @@ class Hem # allow overrides and set defaults @options.hem.port = argv.port if argv.port @options.hem.host or= "" - @options.hem.routes or= [] + @options.hem.routes or= {} # setup applications from options/slug for name, config of @options @@ -117,7 +118,6 @@ class Hem server.start(@apps, @options.hem) clean: -> - utils.log "Clean applications..." targets = argv.targets cleanAll = targets.length is 0 app.unlink() for app in @apps when app.name in targets or cleanAll @@ -151,12 +151,18 @@ class Hem @testTargets(targets, testOptions) check: -> + printOptions = showHidden: false, colors: !argv.nocolors, depth: null + inspect = require('util').inspect + # print hem configuration + utils.log "> Configuration for hem:" + console.log(inspect(@options.hem, printOptions)) + utils.log "" + # print app configurations targets = argv.targets targetAll = targets.length is 0 for app in @apps when app.name in targets or targetAll - utils.log "> Configuration values for #{app.name}" - printOptions = showHidden: false, colors: !argv.nocolors, depth: null - console.log(require('util').inspect(app, printOptions)) + utils.log "> Configuration values for #{app.name}:" + console.log(inspect(app, printOptions)) utils.log "" exec: (command = argv.command) -> diff --git a/src/package.coffee b/src/package.coffee index 1796406..1729151 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -21,7 +21,6 @@ class Application catch err console.error "ERROR: Invalid 'defaults' value provided: " + config.defaults process.exit 1 - utils.log("Applying '" + config.defaults + "' defaults to configuration..." if global.ARGV?.v) config = utils.extend(defaults, config) # set root variable @@ -83,21 +82,22 @@ class Application if @versioning route = @versioning.module.trimVersion(route) # compare against package route values - for pkg in @packages + for name, pkg of @packages return pkg if route is pkg.route # return nothing return unlink: -> - pkg.unlink() for pkg in @packages + utils.log("Removing application targets: #{@name}") + pkg.unlink() for key, pkg of @packages build: -> - utils.log("Building application: #{@name}") - pkg.build() for pkg in @packages + utils.log("Building application targets: #{@name}") + pkg.build() for key, pkg of @packages watch: -> utils.log("Watching application: #{@name}") - pkg.watch() for pkg in @packages + pkg.watch() for key, pkg of @packages version: -> utils.log("Versioning application: #{@name}") @@ -106,9 +106,9 @@ class Application else utils.errorAndExit "ERROR: Versioning not enabled in slug.json" - applyRootDir: (values...) -> - if @root isnt "" - values = values.map (value) => + applyRootDir: (value) -> + values = utils.toArray(value) + values = values.map (value) => utils.cleanPath(@root, value) values @@ -120,11 +120,9 @@ class Package constructor: (parent, config) -> @parent = parent @name = config.name - @paths = @parent.applyRootDir(config.paths) + @paths = @parent.applyRootDir(config.paths or "") @target = @parent.applyRootDir(config.target or "")[0] - # TODO: something fishy here <<<<<<< - # determine target filename if utils.isDirectory(@target) # determine actual file name @@ -169,9 +167,11 @@ class Package else process.exit(1) unlink: -> - fs.unlinkSync(@target) if fs.existsSync(@target) + if fs.existsSync(@target) + utils.info "- removing #{@target}" + fs.unlinkSync(@target) - build: (write = true) -> + build: (write = true) -> extra = (utils.COMPRESS and " --using compression") or "" utils.log("- Building target: #{@target}#{extra}") source = @compile() @@ -200,7 +200,7 @@ class JsPackage extends Package # javascript only configurations @identifier = config.identifier or 'require' - @libs = @parent.applyRootDir(config.libs) + @libs = @parent.applyRootDir(config.libs or []) @after = config.after or "" @modules = utils.toArray(config.modules or []) @@ -218,8 +218,11 @@ class JsPackage extends Package @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) _modules = @depend.resolve().concat(_stitch.resolve()) - _template = utils.loadAsset('stitch') - _template(identifier: @identifier, modules: _modules) + if _modules + _template = utils.loadAsset('stitch') + _template(identifier: @identifier, modules: _modules) + else + "" compileLibs: (files = @libs, parentDir = "") -> # check if folder or file diff --git a/src/server.coffee b/src/server.coffee index 5f7894c..26b266e 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -33,7 +33,8 @@ server.middleware = (applications, options) -> # setup proxy route for route, value of options.proxy - utils.info "- Proxy requests #{route} to #{value}" + display = "#{value.host}:#{value.port or 80}#{value.path}" + utils.info "- Proxy requests #{route} to #{display}" statics.use(route, createRoutingProxy(value)) # return the custom middleware for connect to use @@ -52,28 +53,28 @@ server.middleware = (applications, options) -> res.setHeader('Content-Length', Buffer.byteLength(str)) res.end((req.method is 'HEAD' and null) or str) return - + # check static content statics.handle(req, res, next) # ------- Private Functions -createRoutingProxy = (options = {}) -> +createRoutingProxy = (options) -> proxy = new httpProxy.RoutingProxy() - # additional options - options.hostPath or= "" - options.port or= 80 + # set options + options.path or= "" + options.port or= url.port or 80 options.patchRedirect or= true # handle redirects - if options.patchRedirect + if options.patchRedirect proxy.once "start", (req, res) -> # get the requesting hostname and port returnHost = req.headers.host patchServerResponseForRedirects(options.host, returnHost) # return function used by connect to access proxy return (req, res, next) -> - req.url = "#{options.hostPath}#{req.url}" + req.url = "#{options.path}#{req.url}" proxy.proxyRequest(req, res, options) patchServerResponseForRedirects = (fromHost, returnHost) -> diff --git a/src/utils.coffee b/src/utils.coffee index cd207d8..0020aa2 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -7,7 +7,7 @@ utils.flatten = flatten = (array, results = []) -> for item in array if Array.isArray(item) flatten(item, results) - else + else if item results.push(item) results From e7aa7af4f19ea76636f402aefdd967fbd030cc81 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 26 Jun 2013 17:32:29 -0500 Subject: [PATCH 032/167] handling the @after attribute a bit differently, allow array of strings --- assets/defaults/spine.json | 10 +++++++++- lib/package.js | 2 +- lib/utils.js | 18 ++++++++++++++++++ src/package.coffee | 3 +-- src/utils.coffee | 9 +++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index ab60730..bfd5d58 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -46,7 +46,15 @@ "test/specs" ], "target": "test/public/specs.js", - "type": "jasmine", + "after": [ + "require('lib/setup');", + "for (var key in specs.modules) specs(key);" + ], + "depends": [ + "#jasmine", + "common.js", + "js" + ], "runner": "karma" } } diff --git a/lib/package.js b/lib/package.js index 9b4d64f..bbccc61 100644 --- a/lib/package.js +++ b/lib/package.js @@ -303,7 +303,7 @@ JsPackage.__super__.constructor.call(this, parent, config); this.identifier = config.identifier || 'require'; this.libs = this.parent.applyRootDir(config.libs || []); - this.after = config.after || ""; + this.after = utils.arrayToString(config.after || ""); this.modules = utils.toArray(config.modules || []); } diff --git a/lib/utils.js b/lib/utils.js index 4e04698..fa9f67a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -28,6 +28,24 @@ return results; }; + utils.arrayToString = function(value) { + var line, result, _i, _len; + + if (value == null) { + value = ""; + } + if (Array.isArray(value)) { + result = ""; + for (_i = 0, _len = value.length; _i < _len; _i++) { + line = value[_i]; + result += line + "\n"; + } + return result; + } else { + return value; + } + }; + utils.toArray = function(value) { if (value == null) { value = []; diff --git a/src/package.coffee b/src/package.coffee index 1729151..13c67af 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -201,7 +201,7 @@ class JsPackage extends Package # javascript only configurations @identifier = config.identifier or 'require' @libs = @parent.applyRootDir(config.libs or []) - @after = config.after or "" + @after = utils.arrayToString(config.after or "") @modules = utils.toArray(config.modules or []) compile: -> @@ -214,7 +214,6 @@ class JsPackage extends Package compileModules: -> # TODO use detective....?? - # TODO cache results since this shouldn't change too much?? @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) _modules = @depend.resolve().concat(_stitch.resolve()) diff --git a/src/utils.coffee b/src/utils.coffee index 0020aa2..dc35338 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -11,6 +11,15 @@ utils.flatten = flatten = (array, results = []) -> results.push(item) results +utils.arrayToString = (value = "") -> + if Array.isArray(value) + result = "" + for line in value + result += line + "\n" + result + else + value + utils.toArray = (value = []) -> if Array.isArray(value) then value else [value] From 8ca6ae312d607fcfd3bfe0832de177af363c0f1f Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 27 Jun 2013 13:21:03 -0500 Subject: [PATCH 033/167] need to improve compile libs so that it only looks at compilable files --- lib/package.js | 6 +++--- src/package.coffee | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/package.js b/lib/package.js index bbccc61..a7fc2a7 100644 --- a/lib/package.js +++ b/lib/package.js @@ -340,7 +340,7 @@ }; JsPackage.prototype.compileLibs = function(files, parentDir) { - var dir, file, results, slash, stats, _i, _len; + var dir, file, results, slash, stats, _i, _len, _ref; if (files == null) { files = this.libs; @@ -358,7 +358,7 @@ if (stats.isDirectory()) { dir = fs.readdirSync(file); results.push(this.compileLibs(dir, file)); - } else if (stats.isFile()) { + } else if (stats.isFile() && ((_ref = path.extname(file)) === '.js' || _ref === '.coffee')) { results.push(fs.readFileSync(file, 'utf8')); } } @@ -381,7 +381,7 @@ function TestPackage(parent, config) { TestPackage.__super__.constructor.call(this, parent, config); - this.type = config.type; + this.depends = utils.toArray(config.depends); this.runner = config.runner; } diff --git a/src/package.coffee b/src/package.coffee index 13c67af..e9fee9d 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -224,6 +224,10 @@ class JsPackage extends Package "" compileLibs: (files = @libs, parentDir = "") -> + + # TODO: need to perform similar operation as stitch in that only + # compilable code is used... + # check if folder or file results = [] for file in files @@ -235,7 +239,7 @@ class JsPackage extends Package # get directory contents dir = fs.readdirSync(file) results.push @compileLibs(dir, file) - else if (stats.isFile()) + else if stats.isFile() and path.extname(file) in ['.js','.coffee'] results.push fs.readFileSync(file, 'utf8') results.join("\n") @@ -250,8 +254,8 @@ class TestPackage extends JsPackage super(parent, config) # TODO: use after in default spine json to setup specs... # TODO: testLibs = ['jasmine'] or ['test/public/lib'] - @type = config.type - @runner = config.runner + @depends = utils.toArray(config.depends) + @runner = config.runner class CssPackage extends Package From fc715659168cf3e71cb88e544f253aa20bda119d Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 27 Jun 2013 13:58:58 -0500 Subject: [PATCH 034/167] updated readme --- README.md | 251 +++++++++--------------------------------------------- 1 file changed, 41 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index 39b0ff4..06de0ef 100644 --- a/README.md +++ b/README.md @@ -1,210 +1,41 @@ -#Introduction - -Hem is a project for compiling CommonJS modules when building JavaScript web applications. You can think of Hem as [Bundler](http://gembundler.com/) for Node, or [Stitch](https://github.com/sstephenson/stitch) on steroids. Or it is kind of like [Yeoman](http://yeoman.io/), tailored specifically for spine.js - -This is rather awesome, as it means you don't need to faff around with coping around JavaScript files. jQuery can be a npm dependency, so can jQueryUI and all your custom components. Hem will resolve dependencies dynamically, bundling them together into one file to be served up. Upon deployment, you can serialize your application to disk and serve it statically. - -Hem was primarily designed for developing Spine.js based Single Page Web Applications (SPA's), so a major role it fills is to tie up some of the other lose ends of a frontend development project - things like running tests, precompiling code, and preparing it for deployment. It can even help out in the API connection stuff if your app needs that. - -#Installation - - npm install -g hem - -or - - npm install -g git://github.com/spine/hem.git - -or ...fun trick. - - hem install -g hem - git clone https://github.com/spine/hem.git - cd hem - npm link - -this last approach is great if you want to customize hem for your own use, (...or for developing npm packages in general). Just fork and use you own path! - -##Dependencies - -Hem has two types of dependency resolutions: Node modules and Stitch modules. - -Node modules: Hem will recursively resolve any external Node modules your code references. This means that Spine, jQuery etc, can all be external Node modules - you don't have lots of libraries floating around your application. This also has the advantage of explicit versioning; you'll have much more control over external libraries. - -Stitch modules: Hem will bundle up your whole application (without any static dependency analysis), automatically converting CoffeeScript (.coffee) and jQuery template (.tmpl) files. Hem doesn't use static analysis of your application to determine dependencies, as that's often not-viable considering the amount of dynamically loaded dependencies most applications use. - -In a nutshell, Hem will make sure your application and all its dependencies are wrapped up in a single file, ready to be served to web browsers. - -##CommonJS - -CommonJS modules are at the core of Hem, and are the format Hem aspects every module to adhere to. As well as ensuring your code is encapsulated and modular, CommonJS modules give you dependency management, scope isolation, and namespacing. They should be used in any JavaScript application that spans more than a few files. - -To find out more about why CommonJS modules are a great solution to JavaScript dependency management, see the CommonJS guide - -It's not that AMD pattern is bad by the way, just not the way hem went for now. - -###The Format - -The format is remarkably straightforward, but is something you'll have to adhere to in every file to make it work. CommonJS uses explicit exporting; so to expose a property inside a module to other modules, you'll need to do something like this: - -In app/controllers/users.coffee: - - class Users extends Spine.Controller - -Explicitly export the Users object - - module.exports = Users - -The format mandates that a module object will be available in every module. However, if you're targeting both the CommonJS format, and a normal environment, you can do a conditional export, checking that the module object exists. - - module?.exports = Users - -###Requiring modules - -Requiring other modules is just as straightforward; just use the require() function. - - Users = require("controllers/users") - -In Hem apps, all module paths are relative to the app folder - so don't require files relative to the specific module. -CSS - -##Styling your app - -Hem will also bundle up all your application's CSS into one file, ready to serve up to clients. CSS encapsulation and modularity is just as important as JavaScript de-coupling (and can get as equally messy if it's not done right); Hem goes some way to help you with this. To compile CSS, Hem uses an excellent library called Stylus. Stylus is mostly a superset of CSS, and the normal CSS syntax will work just fine if that's all you want. - -However the awesome part of Stylus is the extra syntactical sugar it brings to CSS. Features like optional braces and colons, mixins, variables and significant whitespace all vastly improve your application's CSS, and decreases the amount of typing necessary. In a nutshell, Stylus is to CSS as CoffeeScript is to JavaScript. - -Stylus files are indicated by the .styl extension, and are automatically compiled down to CSS by Hem. This all happens in the background, so you don't need to worry about it. - -Also in the pipeline is the ability to bundle up CSS from Node modules. - -##slug.json - -Hem has some good defaults (convention over configuration), but sometimes you'll need to change them, especially when adding libraries and dependencies. - -For configuration, Hem uses a slug.json file, located in the root of your application. Hem expects a certain directory structure. A main JavaScript/CoffeeScript file under app/index, a main CSS/Stylus file under css/index and a public directory to serve static assets from. If you're using Spine.app, these will all be generated for you. - -Hem also allows you to specify static JavaScript libraries to include, under the "libs" option: - - { - "libs": [ - "./lib/other.js" - ] - } - -These will be included before the rest of your JavaScript, and without being wrapped in the CommonJS module transport format. In addition, Hem lets you specify an array of npm/Node dependencies, to be included in your application. For example, in a default generated Spine.app slug.json file, you'll find the following dependencies: - - { - "dependencies": [ - "es5-shimify", - "json2ify", - "jqueryify", - "jquery.tmpl", - "spine" - ] - } - -These dependencies will be statically analyzed, to recursively resolve additional dependencies, and then wrapped in the CommonJS module format, being served up with the rest of your application's JavaScript. In other words, you don't have to have jquery.js, spine.js and json2.js floating around inside your application, they can be Node modules, installed through npm. - -##Usage - -Ok, so now we've looked at how to configure Hem, let's actually use it in an application. As I mentioned earlier, this step is much easier with an application previously generated by Spine.app, and I advise you go down this route if you're unfamiliar with Hem. - -Now, we can start a development server, which will dynamically build our application every request, using the server command: - - hem server - -By default, your spine application is served at http://localhost:9294. -You can configure the host and port from command line or as settings in your package.json - - hem server -p 9295 - -Would result in your application being served at http://localhost:9295/ - -If there's an index.html file under public, it'll be served up. Likewise, any calls to /application.js and /application.css will return the relevant JavaScript and CSS. - -For the sake of avoiding cross domain issues in development environments when your spine app is utilizing an ajax api there is a optional proxy server built into hem. -As of Hem 0.3 including a 'routes' block in your slug.json configures that: - - "server": { - "port" : 9294 - }, - "routes": [ - { "/myApiApp/mySpineApp" : "./public" }, - { "/myApiApp/mySpineApp/test" : "./test/public" }, - { "/myApiApp" : { "host": "127.0.0.1", "port": 8080, "hostPath": "/myApiApp", "patchRedirect": true } } - ], - "packages": { - "sampleApp": { - "libs" : ["lib/runtime.js"], - "modules" : [ - "es5-shimify", - "json2ify", - "jqueryify", - "spine", - "spine/lib/local", - "spine/lib/ajax", - "spine/lib/route", - "spine/lib/manager" - ], - "paths" : ["./app"], - "target" : "./public/application.js", - "jsAfter": "jade.rethrow = function rethrow(err, filename, lineno){ throw err; } " - }, - "css": { - "paths" : "./css", - "target" : "./public/application.css" - }, - "test": { - "identifier" : "specs", - "jsAfter" : "require('lib/setup'); for (var key in specs.modules) specs(key);", - "paths" : ["./test/specs"], - "target" : "./test/public/specs.js" - } - } - -now http://127.0.0.1:9294/myApiApp/mySpineApp/ will return the spine app. - -and http://127.0.0.1:9294/myApiApp/ will return your API App - -so relative links like @url = "../api/album/" from inside your spine app models can resolve against your apiapp without issue - -When you're ready to deploy, you should build your application, serializing it to disk. - - hem build - -This will write application.js and application.css and specs.js to the file system. You can then commit it with version control and have your server can statically serve your application, without having to use Node, or have any npm dependencies installed. - -**TODO**: hem build should have an option to version the js/css it producess and replace the references in index.html as well - -###Views - -Currently Hem supports three template options out of the box -* Straigt HTML - stringifed html... that you can render... -* [Eco](https://github.com/sstephenson/eco) - erb like syntax, like ejs, but with coffeeScript. Nice, but seems to be a somewhat abandoned project -* [Jade](https://github.com/visionmedia/jade) - haml like syntax with optional coffeescript filter - * to use jade templates you must include jades [runtime.js](https://github.com/visionmedia/jade/blob/master/runtime.js) as a lib in your spine projects slug.json - "libs": ["lib/runtime.js"], - -###Testing - -[Karma(formally Testacular)](http://karma-runner.github.io/0.8/index.html) is a neat little tool that we leverage with hem. - - hem test - -Will run tests in a spine projects test directory. Tests can be written in CoffeeScript! - - hem watch -t - -Will run tests as test files are updated. Karma makes it smart. Only previously failing tests run. If there were no previously failing tests all will run. -Default is to run tests in a new Chrome window. Firefox, Phantom or some others can be used as well. - - hem server - -will watch and compile jasmine tests, but you will have to go to localhost:9294/test (or whereever you configured hem to run...) and manually trigger page tests to run. - -#TODO - -* Better document Karma usage instructions. -* Make template and CSS pre-processor choices configurable -* This would be cool -> integrate with live-reload for changes. We should be able to inject [live-reload](https://github.com/livereload/livereload-js) while in server mode and then run the livereload-server inside hem or could strata handle the incoming requests? Looks like simple json requests. Would we need an option for the browser to regain its focus? Another option is instead of injecting the script into the page is to use the live reload plugin. - - +Experimental Hem (aka version 4) +================ + +This branch of hem is an experimental refactoring of the current hem code base. The changes in this branch are born from trying to get the current hem +to work with multiple frontend apps that shared a common code base but at the same time were sepearate web apps. + +Initially we had used multiple +hem projects for each frontend and git sub-tree to share the common code, but even that seems tedius. The goal going forward is to be able to +have multiple applications definied with the slug.json and have the ability to be able to build/server/test them all together or on a per app basis. +This allows us to generate a common.js file that contains the main spine/jquery javascript which then can be included in each project's html file. + +In addition, the current version of hem needs a bit of setup/configuration in the slug.json file. I am hoping that if a user follows certain +conventions and folder structures the amount of configuration should be minimal. The current spine structure seems to be... + +``` +spineapp + - app // js/coffee files that are stitched together using commonjs + - public // static files and final app js/css build destination + - lib // plain old js/coffee files appended to js file. + - node_modules + - test // test/spec files + - css // css and stylus files to compile +``` + +By using the above structure, everything should be hooked up and ready to go from hem's point of view. But I do also want to allow the ability to +override this struture, perhaps for cases when somebody is creating a non-spine web app and just wants to make use of the compiler/server. + +Other goals and features... +---- + +In addition to the above, hoping to get some of the other features below integrated into hem... + +- [ ] Easier to setup proxy +- [ ] source mapping +- [ ] easier testing setup and execution (karma/phantomjs) +- [ ] integrate spine.app commands into hem +- [ ] easier ways to add your own compilers/extensions +- [ ] update examples/documention +- [ ] livereload abilities for css and possibly js +- [ ] ability to act as middleware for connect/express apps +- [ ] versioning/deploying abilities From 9b3658b5095ba8d069c21c0ce1d46f7f4b9ea081 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 27 Jun 2013 14:01:28 -0500 Subject: [PATCH 035/167] updated readme --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 06de0ef..4baa196 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ conventions and folder structures the amount of configuration should be minimal. ``` spineapp - - app // js/coffee files that are stitched together using commonjs - - public // static files and final app js/css build destination - - lib // plain old js/coffee files appended to js file. - - node_modules - - test // test/spec files - - css // css and stylus files to compile + - app // js/coffee files that are stitched together using commonjs + - public // static files and final app js/css build destination + - lib // plain old js/coffee files appended to js file. + - node_modules // node modules to include + - test // test/spec files + - css // css and stylus files to compile ``` By using the above structure, everything should be hooked up and ready to go from hem's point of view. But I do also want to allow the ability to @@ -30,12 +30,14 @@ Other goals and features... In addition to the above, hoping to get some of the other features below integrated into hem... -- [ ] Easier to setup proxy -- [ ] source mapping +- [] Easier to setup proxy +- [] source mapping - [ ] easier testing setup and execution (karma/phantomjs) - [ ] integrate spine.app commands into hem - [ ] easier ways to add your own compilers/extensions - [ ] update examples/documention - [ ] livereload abilities for css and possibly js - [ ] ability to act as middleware for connect/express apps -- [ ] versioning/deploying abilities +- [ ] versioning abilities +- [] manifest creation +- [] possibly look at AMD vs commonjs??? From f086c7c63027c519f4b9e0da4d90fc77b511d859 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 27 Jun 2013 14:02:59 -0500 Subject: [PATCH 036/167] updated readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4baa196..2b4eeb2 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ Other goals and features... In addition to the above, hoping to get some of the other features below integrated into hem... -- [] Easier to setup proxy -- [] source mapping -- [ ] easier testing setup and execution (karma/phantomjs) +[ ] Easier to setup proxy +[ ] source mapping +[ ] easier testing setup and execution (karma/phantomjs) - [ ] integrate spine.app commands into hem - [ ] easier ways to add your own compilers/extensions - [ ] update examples/documention - [ ] livereload abilities for css and possibly js - [ ] ability to act as middleware for connect/express apps - [ ] versioning abilities -- [] manifest creation -- [] possibly look at AMD vs commonjs??? +- [ ] manifest creation +- [ ] possibly look at AMD vs commonjs??? From a5ba353199ce933a5ebcc1555cf6522e56ad7943 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 27 Jun 2013 14:05:15 -0500 Subject: [PATCH 037/167] updated readme --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2b4eeb2..da5592b 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,19 @@ Other goals and features... In addition to the above, hoping to get some of the other features below integrated into hem... -[ ] Easier to setup proxy -[ ] source mapping -[ ] easier testing setup and execution (karma/phantomjs) +- [ ] Easier to setup proxy +- [ ] livereload abilities for css and possibly js +- [ ] easier testing setup and execution (karma/phantomjs) - [ ] integrate spine.app commands into hem - [ ] easier ways to add your own compilers/extensions - [ ] update examples/documention -- [ ] livereload abilities for css and possibly js - [ ] ability to act as middleware for connect/express apps - [ ] versioning abilities - [ ] manifest creation + +Will look into but not 100% sure +--- + +- [ ] source mapping - [ ] possibly look at AMD vs commonjs??? +- [ ] jshint/lint checks?? From 821099da9543c6b020762ce890cecffa306c09c5 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 27 Jun 2013 14:20:30 -0500 Subject: [PATCH 038/167] updated readme --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index da5592b..9ebae53 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This branch of hem is an experimental refactoring of the current hem code base. to work with multiple frontend apps that shared a common code base but at the same time were sepearate web apps. Initially we had used multiple -hem projects for each frontend and git sub-tree to share the common code, but even that seems tedius. The goal going forward is to be able to +hem/spine projects for each frontend and git sub-tree to share the common code, but even that seems tedius. The goal going forward is to be able to have multiple applications definied with the slug.json and have the ability to be able to build/server/test them all together or on a per app basis. This allows us to generate a common.js file that contains the main spine/jquery javascript which then can be included in each project's html file. @@ -22,27 +22,36 @@ spineapp - css // css and stylus files to compile ``` -By using the above structure, everything should be hooked up and ready to go from hem's point of view. But I do also want to allow the ability to -override this struture, perhaps for cases when somebody is creating a non-spine web app and just wants to make use of the compiler/server. +By using the above structure, everything should be hooked up and ready to go from hem's point of view. But I also want to allow the ability to +easily override this struture, perhaps for cases when somebody is creating a non-spine web app and just wants to make use of the compiler/server. Other goals and features... ---- -In addition to the above, hoping to get some of the other features below integrated into hem... +In addition to the above ideas, I want to try to get some of the features below integrated into hem... - [ ] Easier to setup proxy - [ ] livereload abilities for css and possibly js - [ ] easier testing setup and execution (karma/phantomjs) - [ ] integrate spine.app commands into hem -- [ ] easier ways to add your own compilers/extensions - [ ] update examples/documention - [ ] ability to act as middleware for connect/express apps - [ ] versioning abilities - [ ] manifest creation +- [ ] really do need to write some tests for hem Will look into but not 100% sure --- +These would be nice to have things, will have to research it more in the future... + +- [ ] easier ways to add your own compilers/extensions - [ ] source mapping - [ ] possibly look at AMD vs commonjs??? - [ ] jshint/lint checks?? + +When will it be done?? +--- + +Thats a good question :) I think its possible to have the main features done, tested, and ready to go by the end of august or early september. + From 812925e066f533f1ad00ae52d34f180fec229690 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 12:50:26 -0500 Subject: [PATCH 039/167] trying to deal with window paths --- assets/defaults/spine.json | 1 + lib/utils.js | 14 +++++++++++--- src/utils.coffee | 13 +++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index bfd5d58..045063e 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -1,6 +1,7 @@ { "root": "", "route": "/", + "version": { "type": "package", "files": [ "public/index.html" ] diff --git a/lib/utils.js b/lib/utils.js index fa9f67a..cc9d253 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var clean, extend, flatten, fs, path, sty, utils, + var clean, extend, flatten, fs, isWin, path, sty, utils, __slice = [].slice; sty = require('sty'); @@ -11,6 +11,8 @@ utils = {}; + isWin = !!require('os').platform.match(/^win/); + utils.flatten = flatten = function(array, results) { var item, _i, _len; @@ -120,10 +122,16 @@ }; utils.cleanPath = function() { - var paths; + var cleanPath, paths, result; paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return clean(paths, path.sep, true); + result = clean(paths, path.sep, true); + if (isWin) { + cleanPath = new RegExp("/\//g"); + return result.replace(cleanPath, path.sep); + } else { + return result; + } }; utils.cleanRoute = function() { diff --git a/src/utils.coffee b/src/utils.coffee index dc35338..cb293bc 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -3,6 +3,9 @@ path = require('path') fs = require('fs') utils = {} +# check for windows :o(... +isWin = !!require('os').platform.match(/^win/) + utils.flatten = flatten = (array, results = []) -> for item in array if Array.isArray(item) @@ -39,7 +42,7 @@ utils.extend = extend = (a, b) -> return a utils.loadAsset = (asset) -> - return require("../assets/" + asset) + require("../assets/" + asset) utils.isDirectory = (dir) -> try @@ -66,7 +69,13 @@ clean = (values, sep, trimStart = false) -> result utils.cleanPath = (paths...) -> - clean(paths, path.sep, true) + result = clean(paths, path.sep, true) + # deal with windows paths :o(... + if isWin + cleanPath = new RegExp "/\//g" + result.replace(cleanPath, path.sep) + else + result utils.cleanRoute = (routes...) -> clean(routes, "/") From 1a3fa1121d65c9b828973906673a1431c6678628 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 12:50:55 -0500 Subject: [PATCH 040/167] some versioning refactoring, still not completely sold on approach yet... --- lib/hem.js | 4 +-- lib/package.js | 42 +++++++++++++++--------------- lib/versioning.js | 59 ++++++++++++++++++++++++++++--------------- src/hem.coffee | 2 +- src/package.coffee | 36 +++++++++++++------------- src/versioning.coffee | 35 +++++++++++++++---------- 6 files changed, 100 insertions(+), 78 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 867eb59..e26146b 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -7,10 +7,10 @@ optimist = require('optimist'); - utils = require('./utils'); - fs = require('fs'); + utils = require('./utils'); + compilers = require('./compilers'); server = require('./server'); diff --git a/lib/package.js b/lib/package.js index a7fc2a7..8fb488c 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglify, utils, versions, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglify, utils, versioning, __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -17,12 +17,11 @@ utils = require('./utils'); - versions = require('./versioning'); + versioning = require('./versioning'); Application = (function() { function Application(name, config) { - var defaults, err, files, key, packager, pkg, route, value, _base, _ref, _ref1, - _this = this; + var defaults, err, key, loadedDefaults, packager, pkg, route, value, verType, _ref, _ref1; if (config == null) { config = {}; @@ -32,10 +31,11 @@ this.root = config.root; if (config.defaults) { try { - defaults = utils.loadAsset('defaults/' + config.defaults); + loadedDefaults = utils.loadAsset('defaults/' + config.defaults); + defaults = utils.extend({}, loadedDefaults); } catch (_error) { err = _error; - console.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); + utils.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); process.exit(1); } config = utils.extend(defaults, config); @@ -76,18 +76,12 @@ config.test.name = "test"; this.packages.test = new TestPackage(this, config.test); } - this.versioning = config.version || void 0; - if (this.versioning) { - (_base = this.versioning).type || (_base.type = "package"); - this.versioning.module = versions[this.versioning.type]; - files = utils.toArray(this.versioning.files); - files = files.map(function(file) { - return _this.applyRootDir(file)[0]; - }); - this.versioning.files = files; - if (!this.versioning.module) { - utils.errorAndExit("Incorrect type value for versioning (" + this.versioning.type + ")"); + if (config.version) { + verType = versioning[config.version.type]; + if (!verType) { + utils.errorAndExit("Incorrect type value for version configuration: (" + config.version.type + ")"); } + this.versioning = new verType(this, config.version); } } @@ -95,7 +89,7 @@ var name, pkg, _ref; if (this.versioning) { - route = this.versioning.module.trimVersion(route); + route = this.versioning.trim(route); } _ref = this.packages; for (name in _ref) { @@ -148,7 +142,7 @@ Application.prototype.version = function() { utils.log("Versioning application: " + this.name + ""); if (this.versioning) { - return this.versioning.module.updateVersion(this); + return this.versioning.update(); } else { return utils.errorAndExit("ERROR: Versioning not enabled in slug.json"); } @@ -160,7 +154,11 @@ values = utils.toArray(value); values = values.map(function(value) { - return utils.cleanPath(_this.root, value); + if (utils.startsWith(value, "./")) { + return value; + } else { + return utils.cleanPath(_this.root, value); + } }); return values; }; @@ -228,10 +226,10 @@ utils.error(ex.message); } if (ex.path) { - console.error(ex.path); + utils.error(ex.path); } if (ex.location) { - console.error(ex.location); + utils.error(ex.location); } switch (utils.COMMAND) { case "server" || "watch": diff --git a/lib/versioning.js b/lib/versioning.js index 5d61ccd..42faecf 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var fs, path, replaceTargetInData, replaceTargetsInAppFiles, types, utils; + var NpmPackageVersion, fs, path, types, updateVersionInAppFiles, updateVersionInData, utils; fs = require('fs'); @@ -10,47 +10,66 @@ types = {}; - replaceTargetsInAppFiles = function(app, value) { - var data, file, files, pkg, _i, _j, _len, _len1, _ref, _results; + updateVersionInAppFiles = function(files, packages, value) { + var data, file, key, pkg, _i, _len, _results; - files = utils.toArray(app.versioning.files); - console.log(files); _results = []; for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; utils.log("- updating file " + file + " with version: " + value + ""); data = fs.readFileSync(file, 'utf8'); - _ref = app.packages; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - pkg = _ref[_j]; - data = replaceTargetInData(data, value, pkg); + for (key in packages) { + pkg = packages[key]; + data = updateVersionInData(data, value, pkg); } _results.push(fs.writeFileSync(file, data)); } return _results; }; - replaceTargetInData = function(data, value, pkg) { + updateVersionInData = function(data, value, pkg) { var ext, match, name, replace; ext = path.extname(pkg.target); name = path.basename(pkg.target, ext); match = new RegExp("=(\"|')" + name + "[^\"']?" + ext + "(\"|')"); replace = "=$1" + name + "." + value + ext + "$2"; - return data.replace(match, replace); + if (data.match(match)) { + utils.log("> found target: " + pkg.target); + return data.replace(match, replace); + } else { + return data; + } }; - types["package"] = { - getVersion: function() { + types["package"] = NpmPackageVersion = (function() { + function NpmPackageVersion(app, options) { + var _this = this; + + if (options == null) { + options = {}; + } + this.app = app; + this.files = utils.toArray(options.files).map(function(file) { + return _this.app.applyRootDir(file)[0]; + }); + } + + NpmPackageVersion.prototype.getVersion = function() { return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; - }, - updateVersion: function(app) { - return replaceTargetsInAppFiles(app, this.getVersion()); - }, - trimVersion: function(url) { + }; + + NpmPackageVersion.prototype.update = function() { + return updateVersionInAppFiles(this.files, this.app.packages, this.getVersion()); + }; + + NpmPackageVersion.prototype.trim = function(url) { return url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2"); - } - }; + }; + + return NpmPackageVersion; + + })(); module.exports = types; diff --git a/src/hem.coffee b/src/hem.coffee index 3776e47..ee5c12c 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,7 +1,7 @@ path = require('path') optimist = require('optimist') -utils = require('./utils') fs = require('fs') +utils = require('./utils') compilers = require('./compilers') server = require('./server') application = require('./package') diff --git a/src/package.coffee b/src/package.coffee index e9fee9d..2f20cf6 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -4,7 +4,7 @@ uglify = require('uglify-js') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') -versions = require('./versioning') +versioning = require('./versioning') # ------- Parent Classes @@ -17,9 +17,11 @@ class Application # apply defaults if (config.defaults) try - defaults = utils.loadAsset('defaults/' + config.defaults) + loadedDefaults = utils.loadAsset('defaults/' + config.defaults) + # make sure we don't modify the original assets (which is cached by require) + defaults = utils.extend({}, loadedDefaults) catch err - console.error "ERROR: Invalid 'defaults' value provided: " + config.defaults + utils.error "ERROR: Invalid 'defaults' value provided: " + config.defaults process.exit 1 config = utils.extend(defaults, config) @@ -64,23 +66,16 @@ class Application @packages.test = new TestPackage(@, config.test) # configure versioning - @versioning = config.version or undefined - if @versioning - # TODO: move to a consturctor!! <<<<, need to make this an instance of a class!!! << - @versioning.type or= "package" - @versioning.module = versions[@versioning.type] - # update file paths - files = utils.toArray(@versioning.files) - files = files.map (file) => - @applyRootDir(file)[0] - @versioning.files = files - if not (@versioning.module) - utils.errorAndExit "Incorrect type value for versioning (#{@versioning.type})" + if config.version + verType = versioning[config.version.type] + unless verType + utils.errorAndExit "Incorrect type value for version configuration: (#{config.version.type})" + @versioning = new verType(@, config.version) isMatchingRoute: (route) -> # strip out any versioning applied to file if @versioning - route = @versioning.module.trimVersion(route) + route = @versioning.trim(route) # compare against package route values for name, pkg of @packages return pkg if route is pkg.route @@ -102,13 +97,16 @@ class Application version: -> utils.log("Versioning application: #{@name}") if @versioning - @versioning.module.updateVersion(@) + @versioning.update() else utils.errorAndExit "ERROR: Versioning not enabled in slug.json" applyRootDir: (value) -> values = utils.toArray(value) values = values.map (value) => + if utils.startsWith(value, "./") + value + else utils.cleanPath(@root, value) values @@ -159,8 +157,8 @@ class Package utils.log(ex.stack) else utils.error(ex.message) - console.error ex.path if ex.path - console.error ex.location if ex.location + utils.error ex.path if ex.path + utils.error ex.location if ex.location # only return when in server/watch mode, otherwise exit switch utils.COMMAND when "server" or "watch" then return "console.log(\"HEM compile ERROR: #{ex}\");" diff --git a/src/versioning.coffee b/src/versioning.coffee index 6c6a8b5..c61354c 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -5,41 +5,48 @@ types = {} # private functions -replaceTargetsInAppFiles = (app, value) -> - files = utils.toArray(app.versioning.files) - console.log files +updateVersionInAppFiles = (files, packages, value) -> for file in files utils.log "- updating file #{file} with version: #{value}" data = fs.readFileSync(file, 'utf8') # match all target in packages - for pkg in app.packages - data = replaceTargetInData(data, value, pkg) + for key, pkg of packages + data = updateVersionInData(data, value, pkg) fs.writeFileSync(file, data) -replaceTargetInData = (data, value, pkg) -> +updateVersionInData = (data, value, pkg) -> ext = path.extname(pkg.target) name = path.basename(pkg.target, ext) match = new RegExp("=(\"|')#{name}[^\"']?#{ext}(\"|')") replace = "=$1#{name}.#{value}#{ext}$2" - data.replace(match, replace) + # perform replace + if data.match(match) + utils.log "> found target: #{pkg.target}" + data.replace(match, replace) + else + data # handle versioning based on package.json version (default) -types.package = +types.package = class NpmPackageVersion + + constructor: (app, options = {}) -> + @app = app + @files = utils.toArray(options.files).map (file) => + @app.applyRootDir(file)[0] getVersion: -> JSON.parse(fs.readFileSync('./package.json', 'utf8')).version - updateVersion: (app) -> - replaceTargetsInAppFiles(app, @getVersion()) + update: () -> + updateVersionInAppFiles(@files, @app.packages, @getVersion()) - trimVersion: (url) -> + trim: (url) -> url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2") # TODO: other types that could be made # 1) based on git commits/tags -# 2) backed on jenkinds builds -# 3) Allow build/version to happen with one command -# 4) allow command line options to version command +# 2) backed on jenkinds builds or env values +# 3) Allow build/version to happen with one command (deploy) module.exports = types From 7d095dbbc9bfb4d9879891b76cb15e354f7ca7b6 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 12:53:19 -0500 Subject: [PATCH 041/167] platform is function call --- package.json | 2 +- src/utils.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2109eab..4c5718a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.0", + "version": "0.4.1", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/utils.coffee b/src/utils.coffee index cb293bc..2a65e50 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -4,7 +4,7 @@ fs = require('fs') utils = {} # check for windows :o(... -isWin = !!require('os').platform.match(/^win/) +isWin = !!require('os').platform().match(/^win/) utils.flatten = flatten = (array, results = []) -> for item in array From 6c9f9d488367f4cb081c8b00269564e69a3d6fb3 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 12:53:55 -0500 Subject: [PATCH 042/167] need compiled js file --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index cc9d253..ed5c2ab 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -11,7 +11,7 @@ utils = {}; - isWin = !!require('os').platform.match(/^win/); + isWin = !!require('os').platform().match(/^win/); utils.flatten = flatten = function(array, results) { var item, _i, _len; From 22d54e80750194ff6fdf34819c3a7d68ea447620 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 13:11:02 -0500 Subject: [PATCH 043/167] debuggin windows issues --- lib/utils.js | 8 ++++---- package.json | 2 +- src/utils.coffee | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index ed5c2ab..0e5c055 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -126,12 +126,12 @@ paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; result = clean(paths, path.sep, true); - if (isWin) { + if (isWin || true) { cleanPath = new RegExp("/\//g"); - return result.replace(cleanPath, path.sep); - } else { - return result; + console.log(">> clean path:", result, result.replace(cleanPath, path.sep)); + result = result.replace(cleanPath, path.sep); } + return result; }; utils.cleanRoute = function() { diff --git a/package.json b/package.json index 4c5718a..cf5adfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.1", + "version": "0.4.1.1", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/utils.coffee b/src/utils.coffee index 2a65e50..58ed259 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -71,11 +71,11 @@ clean = (values, sep, trimStart = false) -> utils.cleanPath = (paths...) -> result = clean(paths, path.sep, true) # deal with windows paths :o(... - if isWin + if isWin or true cleanPath = new RegExp "/\//g" - result.replace(cleanPath, path.sep) - else - result + console.log ">> clean path:", result, result.replace(cleanPath, path.sep) + result = result.replace(cleanPath, path.sep) + result utils.cleanRoute = (routes...) -> clean(routes, "/") From b57ed9813ffc7337eb96795f5e21fe8f0c73e6f8 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 13:12:32 -0500 Subject: [PATCH 044/167] guess I can't do x.x.x.x versions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf5adfd..e45e812 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.1.1", + "version": "0.4.2", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 6c3145d2cf4b5b103ef77bfdd550894b0a0bfcb3 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 13:17:02 -0500 Subject: [PATCH 045/167] I am starting to really dislike windows --- lib/utils.js | 2 +- package.json | 2 +- src/utils.coffee | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 0e5c055..5fc0971 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -127,7 +127,7 @@ paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; result = clean(paths, path.sep, true); if (isWin || true) { - cleanPath = new RegExp("/\//g"); + cleanPath = new RegExp(/\//g); console.log(">> clean path:", result, result.replace(cleanPath, path.sep)); result = result.replace(cleanPath, path.sep); } diff --git a/package.json b/package.json index e45e812..c5ed4be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.2", + "version": "0.4.3", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/utils.coffee b/src/utils.coffee index 58ed259..0787787 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -72,7 +72,7 @@ utils.cleanPath = (paths...) -> result = clean(paths, path.sep, true) # deal with windows paths :o(... if isWin or true - cleanPath = new RegExp "/\//g" + cleanPath = new RegExp /\//g console.log ">> clean path:", result, result.replace(cleanPath, path.sep) result = result.replace(cleanPath, path.sep) result From 3e436f7f6ac00c646802e0fcf82d517144ccfd52 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 14:02:35 -0500 Subject: [PATCH 046/167] I think this should be the final windows change, famous last words.. --- lib/package.js | 2 +- lib/utils.js | 1 - package.json | 2 +- src/package.coffee | 2 +- src/utils.coffee | 1 - 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/package.js b/lib/package.js index 8fb488c..1877e2b 100644 --- a/lib/package.js +++ b/lib/package.js @@ -205,7 +205,7 @@ value = _ref[route]; if (!this.route) { if (utils.startsWith(this.target, value)) { - regexp = new RegExp("^" + value); + regexp = new RegExp("^" + (value.replace(/\\/g, "\\\\"))); targetUrl = this.target.replace(regexp, ""); this.route = this.parent.applyBaseRoute(route, targetUrl); } diff --git a/lib/utils.js b/lib/utils.js index 5fc0971..79895ff 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -128,7 +128,6 @@ result = clean(paths, path.sep, true); if (isWin || true) { cleanPath = new RegExp(/\//g); - console.log(">> clean path:", result, result.replace(cleanPath, path.sep)); result = result.replace(cleanPath, path.sep); } return result; diff --git a/package.json b/package.json index c5ed4be..f234f84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.3", + "version": "0.4.4", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/package.coffee b/src/package.coffee index 2f20cf6..3144b0e 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -144,7 +144,7 @@ class Package # use the static urls to determine the package @route for route, value of @parent.static when not @route if utils.startsWith(@target, value) - regexp = new RegExp("^#{value}") + regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}") targetUrl = @target.replace(regexp,"") @route = @parent.applyBaseRoute(route, targetUrl) diff --git a/src/utils.coffee b/src/utils.coffee index 0787787..1ec027b 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -73,7 +73,6 @@ utils.cleanPath = (paths...) -> # deal with windows paths :o(... if isWin or true cleanPath = new RegExp /\//g - console.log ">> clean path:", result, result.replace(cleanPath, path.sep) result = result.replace(cleanPath, path.sep) result From a22e4294fb523795cb87bb88153fd6f02b5fc98e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 14:10:05 -0500 Subject: [PATCH 047/167] sigh.. --- lib/package.js | 1 + package.json | 2 +- src/package.coffee | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/package.js b/lib/package.js index 1877e2b..61f22f0 100644 --- a/lib/package.js +++ b/lib/package.js @@ -208,6 +208,7 @@ regexp = new RegExp("^" + (value.replace(/\\/g, "\\\\"))); targetUrl = this.target.replace(regexp, ""); this.route = this.parent.applyBaseRoute(route, targetUrl); + console.log(regexp, this.target, targetUrl, this.route); } } } diff --git a/package.json b/package.json index f234f84..c67a223 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.4", + "version": "0.4.6", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/package.coffee b/src/package.coffee index 3144b0e..07f0312 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -147,6 +147,7 @@ class Package regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}") targetUrl = @target.replace(regexp,"") @route = @parent.applyBaseRoute(route, targetUrl) + console.log regexp, @target, targetUrl, @route # make sure we have a route to use when using server command if utils.COMMAND is "server" From 3206100cadb7fcc39e9e7227b862e3213b0fe8a6 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 14:24:29 -0500 Subject: [PATCH 048/167] I have a good feeling about this one --- lib/package.js | 2 +- package.json | 2 +- src/package.coffee | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/package.js b/lib/package.js index 61f22f0..a36b2a8 100644 --- a/lib/package.js +++ b/lib/package.js @@ -205,7 +205,7 @@ value = _ref[route]; if (!this.route) { if (utils.startsWith(this.target, value)) { - regexp = new RegExp("^" + (value.replace(/\\/g, "\\\\"))); + regexp = new RegExp("^" + (value.replace(/\\/g, "\\\\")) + "(\\\\|\/)?"); targetUrl = this.target.replace(regexp, ""); this.route = this.parent.applyBaseRoute(route, targetUrl); console.log(regexp, this.target, targetUrl, this.route); diff --git a/package.json b/package.json index c67a223..a42bc13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.6", + "version": "0.4.7", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/package.coffee b/src/package.coffee index 07f0312..164cafa 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -144,7 +144,7 @@ class Package # use the static urls to determine the package @route for route, value of @parent.static when not @route if utils.startsWith(@target, value) - regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}") + regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}(\\\\|\/)?") targetUrl = @target.replace(regexp,"") @route = @parent.applyBaseRoute(route, targetUrl) console.log regexp, @target, targetUrl, @route From 744f385632655f9b800bc684fc7259aff0652850 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 14:27:37 -0500 Subject: [PATCH 049/167] get rid of debugging statements --- lib/package.js | 1 - package.json | 2 +- src/package.coffee | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/package.js b/lib/package.js index a36b2a8..d50dbaa 100644 --- a/lib/package.js +++ b/lib/package.js @@ -208,7 +208,6 @@ regexp = new RegExp("^" + (value.replace(/\\/g, "\\\\")) + "(\\\\|\/)?"); targetUrl = this.target.replace(regexp, ""); this.route = this.parent.applyBaseRoute(route, targetUrl); - console.log(regexp, this.target, targetUrl, this.route); } } } diff --git a/package.json b/package.json index a42bc13..3bf84a6 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "url": "https://github.com/spine/hem.git" }, "engine": [ - "node >=0.6.0" + "node >=0.8.0" ], "main": "./lib/hem.js", "bin": { diff --git a/src/package.coffee b/src/package.coffee index 164cafa..797b5c3 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -147,7 +147,6 @@ class Package regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}(\\\\|\/)?") targetUrl = @target.replace(regexp,"") @route = @parent.applyBaseRoute(route, targetUrl) - console.log regexp, @target, targetUrl, @route # make sure we have a route to use when using server command if utils.COMMAND is "server" From 6438413173722f5520042e5f4bf898ea37604821 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 15:12:57 -0500 Subject: [PATCH 050/167] versioning can handle different paths inside file --- lib/package.js | 2 +- lib/versioning.js | 4 ++-- src/package.coffee | 2 +- src/versioning.coffee | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/package.js b/lib/package.js index d50dbaa..ba206b2 100644 --- a/lib/package.js +++ b/lib/package.js @@ -154,7 +154,7 @@ values = utils.toArray(value); values = values.map(function(value) { - if (utils.startsWith(value, "./")) { + if (utils.startsWith(value, "." + path.sep)) { return value; } else { return utils.cleanPath(_this.root, value); diff --git a/lib/versioning.js b/lib/versioning.js index 42faecf..07f3c00 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -32,8 +32,8 @@ ext = path.extname(pkg.target); name = path.basename(pkg.target, ext); - match = new RegExp("=(\"|')" + name + "[^\"']?" + ext + "(\"|')"); - replace = "=$1" + name + "." + value + ext + "$2"; + match = new RegExp("=(\"|')(.*/?)" + name + "[^\"']?" + ext + "(\"|')"); + replace = "=$1$2" + name + "." + value + ext + "$3"; if (data.match(match)) { utils.log("> found target: " + pkg.target); return data.replace(match, replace); diff --git a/src/package.coffee b/src/package.coffee index 797b5c3..cba43c7 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -104,7 +104,7 @@ class Application applyRootDir: (value) -> values = utils.toArray(value) values = values.map (value) => - if utils.startsWith(value, "./") + if utils.startsWith(value, "." + path.sep) value else utils.cleanPath(@root, value) diff --git a/src/versioning.coffee b/src/versioning.coffee index c61354c..7df727c 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -17,8 +17,8 @@ updateVersionInAppFiles = (files, packages, value) -> updateVersionInData = (data, value, pkg) -> ext = path.extname(pkg.target) name = path.basename(pkg.target, ext) - match = new RegExp("=(\"|')#{name}[^\"']?#{ext}(\"|')") - replace = "=$1#{name}.#{value}#{ext}$2" + match = new RegExp("=(\"|')(.*/?)#{name}[^\"']?#{ext}(\"|')") + replace = "=$1$2#{name}.#{value}#{ext}$3" # perform replace if data.match(match) utils.log "> found target: #{pkg.target}" From 0539d85d0e6e2dfa17a7c863c41300797823c309 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 2 Jul 2013 17:56:27 -0500 Subject: [PATCH 051/167] fixed issue with directories not redirecting with / --- lib/package.js | 22 +++++++++++++--------- lib/server.js | 19 ++++++++++++++++++- src/package.coffee | 30 ++++++++++++++++++++---------- src/server.coffee | 14 +++++++++++++- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/lib/package.js b/lib/package.js index ba206b2..bb81112 100644 --- a/lib/package.js +++ b/lib/package.js @@ -349,15 +349,19 @@ results = []; for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; - slash = parentDir === "" ? "" : path.sep; - file = parentDir + slash + file; - if (fs.existsSync(file)) { - stats = fs.lstatSync(file); - if (stats.isDirectory()) { - dir = fs.readdirSync(file); - results.push(this.compileLibs(dir, file)); - } else if (stats.isFile() && ((_ref = path.extname(file)) === '.js' || _ref === '.coffee')) { - results.push(fs.readFileSync(file, 'utf8')); + if (utils.endsWith(file, ";")) { + results.join(file); + } else { + slash = parentDir === "" ? "" : path.sep; + file = parentDir + slash + file; + if (fs.existsSync(file)) { + stats = fs.lstatSync(file); + if (stats.isDirectory()) { + dir = fs.readdirSync(file); + results.push(this.compileLibs(dir, file)); + } else if (stats.isFile() && ((_ref = path.extname(file)) === '.js' || _ref === '.coffee')) { + results.push(fs.readFileSync(file, 'utf8')); + } } } } diff --git a/lib/server.js b/lib/server.js index 93a4493..574d8d8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.2 (function() { - var connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server, utils; + var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server, utils; connect = require('connect'); @@ -45,6 +45,7 @@ value = _ref1[route]; if (fs.existsSync(value)) { utils.info("- Mapping static " + route + " to " + value + ""); + statics.use(route, checkForRedirect()); statics.use(route, connect["static"](value)); } else { utils.errorAndExit("The folder " + value + " does not exist."); @@ -78,6 +79,22 @@ }; }; + checkForRedirect = function() { + return function(req, res, next) { + var pathname; + + pathname = require("url").parse(req.originalUrl).pathname; + if (req.url === "/" && !utils.endsWith(pathname, "/")) { + pathname += '/'; + res.statusCode = 301; + res.setHeader('Location', pathname); + return res.end('Redirecting to ' + pathname); + } else { + return next(); + } + }; + }; + createRoutingProxy = function(options) { var proxy; diff --git a/src/package.coffee b/src/package.coffee index cba43c7..84c9213 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -211,7 +211,13 @@ class JsPackage extends Package @handleCompileError(ex) compileModules: -> + # TODO use detective....?? + # another approach is to just load everything placed in node_modules?? + # and then look for it locally require(Module._findPath("spine", [process.cwd() + '/node_modules'])) + # + # or should hem should just be installed along with project?? + @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) _modules = @depend.resolve().concat(_stitch.resolve()) @@ -229,16 +235,20 @@ class JsPackage extends Package # check if folder or file results = [] for file in files - slash = if parentDir is "" then "" else path.sep - file = parentDir + slash + file - if fs.existsSync(file) - stats = fs.lstatSync(file) - if (stats.isDirectory()) - # get directory contents - dir = fs.readdirSync(file) - results.push @compileLibs(dir, file) - else if stats.isFile() and path.extname(file) in ['.js','.coffee'] - results.push fs.readFileSync(file, 'utf8') + # treat as normal javascript + if utils.endsWith(file,";") + results.join(file) + # else load as file/dir + else + slash = if parentDir is "" then "" else path.sep + file = parentDir + slash + file + if fs.existsSync(file) + stats = fs.lstatSync(file) + if (stats.isDirectory()) + dir = fs.readdirSync(file) + results.push @compileLibs(dir, file) + else if stats.isFile() and path.extname(file) in ['.js','.coffee'] + results.push fs.readFileSync(file, 'utf8') results.join("\n") getWatchedDirs: -> diff --git a/src/server.coffee b/src/server.coffee index 26b266e..172b6d1 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -27,7 +27,8 @@ server.middleware = (applications, options) -> for route, value of options.routes if fs.existsSync(value) utils.info "- Mapping static #{route} to #{value}" - statics.use(route, connect.static(value)) + statics.use(route, checkForRedirect()) + statics.use(route, connect.static(value) ) else utils.errorAndExit "The folder #{value} does not exist." @@ -60,6 +61,17 @@ server.middleware = (applications, options) -> # ------- Private Functions +checkForRedirect = () -> + return (req, res, next) -> + pathname = require("url").parse(req.originalUrl).pathname + if (req.url is "/" and not utils.endsWith(pathname,"/")) + pathname += '/' + res.statusCode = 301 + res.setHeader('Location', pathname) + res.end('Redirecting to ' + pathname) + else + next() + createRoutingProxy = (options) -> proxy = new httpProxy.RoutingProxy() # set options From 7f2ed46be5e77dae49203f4f64e60c0c64edae3c Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 3 Jul 2013 17:16:22 -0500 Subject: [PATCH 052/167] make sure missing parent directories are created. --- lib/package.js | 6 +++++- package.json | 5 +++-- src/compilers.coffee | 9 +++++---- src/package.coffee | 16 ++++++++++------ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/package.js b/lib/package.js index bb81112..52b51c8 100644 --- a/lib/package.js +++ b/lib/package.js @@ -247,7 +247,7 @@ }; Package.prototype.build = function(write) { - var extra, source; + var dirname, extra, source; if (write == null) { write = true; @@ -256,6 +256,10 @@ utils.log("- Building target: " + this.target + "" + extra); source = this.compile(); if (source && write) { + dirname = path.dirname(this.target); + if (!fs.existsSync(dirname)) { + require('mkdirp').sync(dirname); + } fs.writeFileSync(this.target, source); } return source; diff --git a/package.json b/package.json index 3bf84a6..8dc72f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.7", + "version": "0.4.8", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -47,6 +47,7 @@ "jade": "~0.30", "stylus": "~0.32.0", "eco": "1.1.0-rc-3", - "karma": "~0.8.6" + "karma": "~0.8.6", + "mkdirp": "~0.3.5" } } diff --git a/src/compilers.coffee b/src/compilers.coffee index cce9959..7a1515e 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -3,6 +3,11 @@ path = require('path') utils = require('./utils') compilers = {} +# TODO: wondering if we should make it so that the additional compilers are checked at runtim +# if the are present. One approach is to install hem inside the project using it. Or another is +# to require from the process.cwd() node_modules folder, assuming what is needed is installed there. +# TODO: look for it locally require(Module._findPath("spine", [process.cwd() + '/node_modules'])) + compilers.js = compilers.css = (path) -> fs.readFileSync path, 'utf8' @@ -29,10 +34,6 @@ eco = require 'eco' compilers.eco = (path) -> content = eco.precompile fs.readFileSync path, 'utf8' - # TODO: wrap this in a function to be able to call jQuery - # and store the module.id and values in the data attribute, - # then have some way of calling replace with the same view - # and function call with livereload """ var content = #{content}; module.exports = content; diff --git a/src/package.coffee b/src/package.coffee index 84c9213..2318695 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -173,7 +173,11 @@ class Package extra = (utils.COMPRESS and " --using compression") or "" utils.log("- Building target: #{@target}#{extra}") source = @compile() - fs.writeFileSync(@target, source) if source and write + if source and write + dirname = path.dirname(@target) + unless fs.existsSync(dirname) + require('mkdirp').sync(dirname) + fs.writeFileSync(@target, source) source watch: -> @@ -212,11 +216,11 @@ class JsPackage extends Package compileModules: -> - # TODO use detective....?? - # another approach is to just load everything placed in node_modules?? - # and then look for it locally require(Module._findPath("spine", [process.cwd() + '/node_modules'])) - # - # or should hem should just be installed along with project?? + # TODO use detective to load only those modules that are required ("required")? + # or use the modules [] to specifiy which modules if any to load? or + # set to false to never load any node_modules even if they are required in + # javascript files. Would have to determine files needed from the stitched + # files first... @depend or= new Dependency(@modules) _stitch = new Stitch(@paths) From bddd2eb4498465accb3b69e887d8c8cd507993b4 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 10 Jul 2013 13:26:25 -0500 Subject: [PATCH 053/167] more colors --- lib/server.js | 2 +- src/server.coffee | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/server.js b/lib/server.js index 574d8d8..98b9376 100644 --- a/lib/server.js +++ b/lib/server.js @@ -48,7 +48,7 @@ statics.use(route, checkForRedirect()); statics.use(route, connect["static"](value)); } else { - utils.errorAndExit("The folder " + value + " does not exist."); + utils.errorAndExit("The folder " + value + " does not exist for static mapping " + route + ""); } } _ref2 = options.proxy; diff --git a/src/server.coffee b/src/server.coffee index 172b6d1..30d36bc 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -26,16 +26,16 @@ server.middleware = (applications, options) -> statics = connect() for route, value of options.routes if fs.existsSync(value) - utils.info "- Mapping static #{route} to #{value}" + utils.info "- Mapping static #{route} to #{value}" statics.use(route, checkForRedirect()) statics.use(route, connect.static(value) ) else - utils.errorAndExit "The folder #{value} does not exist." + utils.errorAndExit "The folder #{value} does not exist for static mapping #{route}" # setup proxy route for route, value of options.proxy display = "#{value.host}:#{value.port or 80}#{value.path}" - utils.info "- Proxy requests #{route} to #{display}" + utils.info "- Proxy requests #{route} to #{display}" statics.use(route, createRoutingProxy(value)) # return the custom middleware for connect to use @@ -57,7 +57,17 @@ server.middleware = (applications, options) -> # check static content statics.handle(req, res, next) + # TODO hey there, why doesn't htis work + + sometfunction: (options = "asdf") => + return "" + + test: () -> +# TODO why??? + # TODO: whyyyy??? + return "" + more: "asdf" # ------- Private Functions From 21aa9b6f4c7cbeac0d0b1c7482f06521685f1029 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 15 Jul 2013 20:51:52 -0500 Subject: [PATCH 054/167] minor bug fixes and refactorings from master branch --- lib/compilers.js | 11 +---------- lib/dependency.js | 8 +------- lib/hem.js | 16 +--------------- lib/package.js | 28 ++++++++------------------- lib/resolve.js | 14 +++++++++----- lib/server.js | 48 +++++++++++++++++++++++++++++++++------------- lib/stitch.js | 7 +------ lib/test.js | 4 +--- lib/utils.js | 9 +-------- lib/versioning.js | 5 +---- src/package.coffee | 3 ++- src/resolve.coffee | 9 ++++++++- src/server.coffee | 20 ++++++++++++++++--- 13 files changed, 86 insertions(+), 96 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 203b201..725b6a0 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var compileCoffeescript, compilers, cs, eco, err, fs, jade, path, stylus, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -17,7 +17,6 @@ require.extensions['.css'] = function(module, filename) { var source; - source = JSON.stringify(compilers.css(filename)); return module._compile("module.exports = " + source, filename); }; @@ -32,7 +31,6 @@ }; compileCoffeescript = function(path, literate) { var err; - if (literate == null) { literate = false; } @@ -59,14 +57,12 @@ compilers.eco = function(path) { var content; - content = eco.precompile(fs.readFileSync(path, 'utf8')); return "var content = " + content + ";\nmodule.exports = content;"; }; compilers.jeco = function(path) { var content; - content = eco.precompile(fs.readFileSync(path, 'utf8')); return "module.exports = function(values, data){ \n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; }; @@ -75,7 +71,6 @@ compilers.html = function(path) { var content; - content = fs.readFileSync(path, 'utf8'); return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; @@ -88,7 +83,6 @@ jade = require('jade'); compilers.jade = function(path) { var content, ex, source, template; - content = fs.readFileSync(path, 'utf8'); try { template = jade.compile(content, { @@ -113,7 +107,6 @@ stylus = require('stylus'); compilers.styl = function(_path) { var content, result; - content = fs.readFileSync(_path, 'utf8'); result = ''; stylus(content).include(path.dirname(_path)).set('include css', (__indexOf.call(process.argv, '--includeCss') >= 0)).set('compress', utils.COMPRESS).render(function(err, css) { @@ -126,7 +119,6 @@ }; require.extensions['.styl'] = function(module, filename) { var source; - source = JSON.stringify(compilers.styl(filename)); return module._compile("module.exports = " + source, filename); }; @@ -136,7 +128,6 @@ compilers.env = function(path) { var content, envhash, key, packjson; - content = fs.readFileSync(path, 'utf8'); envhash = JSON.parse(content); packjson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); diff --git a/lib/dependency.js b/lib/dependency.js index 767e05d..1dc3a4c 100644 --- a/lib/dependency.js +++ b/lib/dependency.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var Dependency, Module, compilers, detective, extname, fs, mtime, resolve, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -22,7 +22,6 @@ function Module(request, parent) { var _ref; - _ref = resolve(request, parent), this.id = _ref[0], this.filename = _ref[1]; this.ext = extname(this.filename).slice(1); this.mtime = mtime(this.filename); @@ -50,7 +49,6 @@ Module.prototype.resolve = function() { var path, _i, _len, _ref, _results; - _ref = this.calls(); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -62,7 +60,6 @@ Module.prototype.calls = function() { var _ref; - if (_ref = this.ext, __indexOf.call(this.constructor.walk, _ref) >= 0) { return detective(this.compile()); } else { @@ -84,10 +81,8 @@ Dependency.prototype.resolve = function() { var path; - this.modules || (this.modules = (function() { var _i, _len, _ref, _results; - _ref = this.paths; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -101,7 +96,6 @@ Dependency.prototype.deepResolve = function(modules, result, search) { var module, _i, _len; - if (modules == null) { modules = []; } diff --git a/lib/hem.js b/lib/hem.js index e26146b..00542f9 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var Hem, application, argv, compilers, fs, help, optimist, path, server, testing, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -39,7 +39,6 @@ help = function() { var _ref; - utils.log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); optimist.showHelp(); return process.exit(); @@ -52,7 +51,6 @@ Hem.include = function(props) { var key, value, _results; - _results = []; for (key in props) { value = props[key]; @@ -63,7 +61,6 @@ Hem.middleware = function(slugFile) { var hem; - hem = new Hem(slugFile); return server.middleware(hem.apps, hem.options.server); }; @@ -83,7 +80,6 @@ function Hem(options) { var config, name, slug, _base, _base1, _ref; - if (options == null) { options = {}; } @@ -121,7 +117,6 @@ Hem.prototype.server = function() { var value; - value = "http://" + (this.options.hem.host || "localhost") + ":" + this.options.hem.port; utils.log("Starting Server at " + value + ""); return server.start(this.apps, this.options.hem); @@ -129,7 +124,6 @@ Hem.prototype.clean = function() { var app, cleanAll, targets, _i, _len, _ref, _ref1, _results; - targets = argv.targets; cleanAll = targets.length === 0; _ref = this.apps; @@ -154,7 +148,6 @@ Hem.prototype.watch = function() { var app, targets, watchAll, _i, _len, _ref, _ref1, _results; - targets = argv.targets; this.buildTargets(targets); watchAll = targets.length === 0; @@ -171,7 +164,6 @@ Hem.prototype.test = function() { var targets, testOptions; - targets = argv.targets; testOptions = { basePath: this.homeDir @@ -188,7 +180,6 @@ Hem.prototype.check = function() { var app, inspect, printOptions, targetAll, targets, _i, _len, _ref, _ref1, _results; - printOptions = { showHidden: false, colors: !argv.nocolors, @@ -233,7 +224,6 @@ Hem.prototype.getTargetApps = function(targets) { var app, targetAll, _i, _len, _ref, _ref1, _results; - if (targets == null) { targets = []; } @@ -251,7 +241,6 @@ Hem.prototype.testTargets = function(targets, options) { var app, testApps; - if (targets == null) { targets = []; } @@ -260,7 +249,6 @@ } testApps = (function() { var _i, _len, _ref, _results; - _ref = this.getTargetApps(targets); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -276,7 +264,6 @@ Hem.prototype.buildTargets = function(targets) { var app, _i, _len, _ref, _results; - if (targets == null) { targets = []; } @@ -291,7 +278,6 @@ Hem.prototype.versionTargets = function(targets) { var app, _i, _len, _ref, _results; - if (targets == null) { targets = []; } diff --git a/lib/package.js b/lib/package.js index 52b51c8..b00816f 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglify, utils, versioning, __slice = [].slice, @@ -22,7 +22,6 @@ Application = (function() { function Application(name, config) { var defaults, err, key, loadedDefaults, packager, pkg, route, value, verType, _ref, _ref1; - if (config == null) { config = {}; } @@ -87,7 +86,6 @@ Application.prototype.isMatchingRoute = function(route) { var name, pkg, _ref; - if (this.versioning) { route = this.versioning.trim(route); } @@ -102,7 +100,6 @@ Application.prototype.unlink = function() { var key, pkg, _ref, _results; - utils.log("Removing application targets: " + this.name + ""); _ref = this.packages; _results = []; @@ -115,7 +112,6 @@ Application.prototype.build = function() { var key, pkg, _ref, _results; - utils.log("Building application targets: " + this.name + ""); _ref = this.packages; _results = []; @@ -128,7 +124,6 @@ Application.prototype.watch = function() { var key, pkg, _ref, _results; - utils.log("Watching application: " + this.name + ""); _ref = this.packages; _results = []; @@ -151,7 +146,6 @@ Application.prototype.applyRootDir = function(value) { var values, _this = this; - values = utils.toArray(value); values = values.map(function(value) { if (utils.startsWith(value, "." + path.sep)) { @@ -165,7 +159,6 @@ Application.prototype.applyBaseRoute = function() { var values; - values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; return utils.cleanRoute.apply(utils, values); }; @@ -177,7 +170,6 @@ Package = (function() { function Package(parent, config) { var regexp, route, targetFile, targetUrl, value, _ref; - this.parent = parent; this.name = config.name; this.paths = this.parent.applyRootDir(config.paths || ""); @@ -248,7 +240,6 @@ Package.prototype.build = function(write) { var dirname, extra, source; - if (write == null) { write = true; } @@ -266,9 +257,13 @@ }; Package.prototype.watch = function() { - var dir, _i, _len, _ref, _results, + var dir, watchOptions, _i, _len, _ref, _results, _this = this; - + watchOptions = { + persistent: true, + interval: 1000, + ignoreDotFiles: true + }; _ref = this.getWatchedDirs(); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -276,10 +271,7 @@ if (!fs.existsSync(dir)) { continue; } - _results.push(require('watch').watchTree(dir, { - persistent: true, - interval: 1000 - }, function(file, curr, prev) { + _results.push(require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { return _this.build(); } @@ -311,7 +303,6 @@ JsPackage.prototype.compile = function() { var ex, result; - try { result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); if (utils.COMPRESS) { @@ -326,7 +317,6 @@ JsPackage.prototype.compileModules = function() { var _modules, _stitch, _template; - this.depend || (this.depend = new Dependency(this.modules)); _stitch = new Stitch(this.paths); _modules = this.depend.resolve().concat(_stitch.resolve()); @@ -343,7 +333,6 @@ JsPackage.prototype.compileLibs = function(files, parentDir) { var dir, file, results, slash, stats, _i, _len, _ref; - if (files == null) { files = this.libs; } @@ -404,7 +393,6 @@ CssPackage.prototype.compile = function() { var ex, result, _i, _len, _path, _ref; - try { result = []; _ref = this.paths; diff --git a/lib/resolve.js b/lib/resolve.js index 5cf44f8..aa4f91c 100644 --- a/lib/resolve.js +++ b/lib/resolve.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var Module, basename, dirname, extname, invalidDirs, isAbsolute, join, modulePaths, modulerize, repl, resolve, sep, _ref, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -12,13 +12,18 @@ }; modulerize = function(id, filename) { - var ext, modName; - + var baseName, dirName, ext, modName; if (filename == null) { filename = id; } ext = extname(filename); - modName = join(dirname(id), basename(id, ext)); + dirName = dirname(id); + baseName = basename(id, ext); + if (dirName === baseName) { + modName = baseName; + } else { + modName = join(dirname(id), basename(id, ext)); + } return modName.replace(/\\/g, '/'); }; @@ -34,7 +39,6 @@ module.exports = function(request, parent) { var dir, filename, id, index, paths, _, _ref1; - if (parent == null) { parent = repl; } diff --git a/lib/server.js b/lib/server.js index 98b9376..8a1bbfd 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server, utils; @@ -18,15 +18,14 @@ server.start = function(applications, options) { var app; - app = connect(); app.use(server.middleware(applications, options)); return http.createServer(app).listen(options.port, options.host); }; server.middleware = function(applications, options) { - var display, hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; - + var display, hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2, + _this = this; for (_i = 0, _len = applications.length; _i < _len; _i++) { hemapp = applications[_i]; utils.info("> Apply route mappings for application: " + hemapp.name + ""); @@ -44,9 +43,26 @@ for (route in _ref1) { value = _ref1[route]; if (fs.existsSync(value)) { - utils.info("- Mapping static " + route + " to " + value + ""); - statics.use(route, checkForRedirect()); - statics.use(route, connect["static"](value)); + if (fs.lstatSync(value).isDirectory()) { + utils.info("- Mapping static " + route + " to dir " + value + ""); + statics.use(route, checkForRedirect()); + statics.use(route, connect["static"](value)); + } else { + utils.info("- Mapping static " + route + " to resource " + value + ""); + statics.use(route, (function(value) { + return function(req, res) { + return fs.readFile(value, function(err, data) { + if (err) { + res.writeHead(404); + res.end(JSON.stringify(err)); + return; + } + res.writeHead(200); + return res.end(data); + }); + }; + })(value)); + } } else { utils.errorAndExit("The folder " + value + " does not exist for static mapping " + route + ""); } @@ -60,7 +76,6 @@ } return function(req, res, next) { var str, url, _k, _len2, _ref3; - url = ((_ref3 = require("url").parse(req.url)) != null ? _ref3.pathname.toLowerCase() : void 0) || ""; if (url.match(/(\.js|\.css)$/)) { for (_k = 0, _len2 = applications.length; _k < _len2; _k++) { @@ -77,12 +92,23 @@ } return statics.handle(req, res, next); }; + return { + sometfunction: function(options) { + if (options == null) { + options = "asdf"; + } + return ""; + }, + test: function() { + return ""; + }, + more: "asdf" + }; }; checkForRedirect = function() { return function(req, res, next) { var pathname; - pathname = require("url").parse(req.originalUrl).pathname; if (req.url === "/" && !utils.endsWith(pathname, "/")) { pathname += '/'; @@ -97,7 +123,6 @@ createRoutingProxy = function(options) { var proxy; - proxy = new httpProxy.RoutingProxy(); options.path || (options.path = ""); options.port || (options.port = url.port || 80); @@ -105,7 +130,6 @@ if (options.patchRedirect) { proxy.once("start", function(req, res) { var returnHost; - returnHost = req.headers.host; return patchServerResponseForRedirects(options.host, returnHost); }); @@ -118,11 +142,9 @@ patchServerResponseForRedirects = function(fromHost, returnHost) { var writeHead; - writeHead = http.ServerResponse.prototype.writeHead; return http.ServerResponse.prototype.writeHead = function(status) { var headers, newLocation, oldLocation; - if (status === 301 || status === 302) { headers = this._headers; oldLocation = new RegExp(":\/\/" + fromHost + ":?[0-9]*"); diff --git a/lib/stitch.js b/lib/stitch.js index 5a53726..fa3e641 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var Module, Stitch, compilers, flatten, fs, modulerize, npath; @@ -15,11 +15,9 @@ Stitch = (function() { function Stitch(paths) { var path; - this.paths = paths != null ? paths : []; this.paths = (function() { var _i, _len, _ref, _results; - _ref = this.paths; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -32,10 +30,8 @@ Stitch.prototype.resolve = function() { var path; - return flatten((function() { var _i, _len, _ref, _results; - _ref = this.paths; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -48,7 +44,6 @@ Stitch.prototype.walk = function(path, parent, result) { var child, module, stat, _i, _len, _ref; - if (parent == null) { parent = path; } diff --git a/lib/test.js b/lib/test.js index e854dc0..695f7b3 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var createKarmaFileList, fs, run, runKarma, runPhantomjs, utils; @@ -21,7 +21,6 @@ runKarma = function(apps, options) { var testConfig; - if (options == null) { options = {}; } @@ -40,7 +39,6 @@ createKarmaFileList = function(apps) { var app, _i, _len; - for (_i = 0, _len = apps.length; _i < _len; _i++) { app = apps[_i]; return [app.test.target, app.js.target]; diff --git a/lib/utils.js b/lib/utils.js index 79895ff..77dbb0e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var clean, extend, flatten, fs, isWin, path, sty, utils, __slice = [].slice; @@ -15,7 +15,6 @@ utils.flatten = flatten = function(array, results) { var item, _i, _len; - if (results == null) { results = []; } @@ -32,7 +31,6 @@ utils.arrayToString = function(value) { var line, result, _i, _len; - if (value == null) { value = ""; } @@ -69,7 +67,6 @@ utils.extend = extend = function(a, b) { var x; - for (x in b) { if (typeof b[x] === 'object' && !Array.isArray(b[x])) { a[x] || (a[x] = {}); @@ -87,7 +84,6 @@ utils.isDirectory = function(dir) { var e, stats; - try { stats = fs.lstatSync(dir); return stats.isDirectory(); @@ -99,7 +95,6 @@ clean = function(values, sep, trimStart) { var regexp, result, value, _i, _len; - if (trimStart == null) { trimStart = false; } @@ -123,7 +118,6 @@ utils.cleanPath = function() { var cleanPath, paths, result; - paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; result = clean(paths, path.sep, true); if (isWin || true) { @@ -135,7 +129,6 @@ utils.cleanRoute = function() { var routes; - routes = 1 <= arguments.length ? __slice.call(arguments, 0) : []; return clean(routes, "/"); }; diff --git a/lib/versioning.js b/lib/versioning.js index 07f3c00..8ff7309 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.6.3 (function() { var NpmPackageVersion, fs, path, types, updateVersionInAppFiles, updateVersionInData, utils; @@ -12,7 +12,6 @@ updateVersionInAppFiles = function(files, packages, value) { var data, file, key, pkg, _i, _len, _results; - _results = []; for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; @@ -29,7 +28,6 @@ updateVersionInData = function(data, value, pkg) { var ext, match, name, replace; - ext = path.extname(pkg.target); name = path.basename(pkg.target, ext); match = new RegExp("=(\"|')(.*/?)" + name + "[^\"']?" + ext + "(\"|')"); @@ -45,7 +43,6 @@ types["package"] = NpmPackageVersion = (function() { function NpmPackageVersion(app, options) { var _this = this; - if (options == null) { options = {}; } diff --git a/src/package.coffee b/src/package.coffee index 2318695..177020e 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -181,9 +181,10 @@ class Package source watch: -> + watchOptions = { persistent: true, interval: 1000, ignoreDotFiles: true } for dir in @getWatchedDirs() continue unless fs.existsSync(dir) - require('watch').watchTree dir, { persistent: true, interval: 1000 }, (file, curr, prev) => + require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() diff --git a/src/resolve.coffee b/src/resolve.coffee index 8eecfd0..091dd82 100644 --- a/src/resolve.coffee +++ b/src/resolve.coffee @@ -7,7 +7,14 @@ isAbsolute = (path) -> /^\//.test(path) # to create valid CommonJS module names modulerize = (id, filename = id) -> ext = extname(filename) - modName = join(dirname(id), basename(id, ext)) + dirName = dirname(id) + baseName = basename(id, ext) + # Do not allow names like 'underscore/underscore' + if dirName is baseName + modName = baseName + else + modName = join(dirname(id), basename(id, ext)) + # deal with window path separator modName.replace(/\\/g, '/') modulePaths = Module._nodeModulePaths(process.cwd()) diff --git a/src/server.coffee b/src/server.coffee index 30d36bc..4fdb0c4 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -26,9 +26,23 @@ server.middleware = (applications, options) -> statics = connect() for route, value of options.routes if fs.existsSync(value) - utils.info "- Mapping static #{route} to #{value}" - statics.use(route, checkForRedirect()) - statics.use(route, connect.static(value) ) + # test if file is directory or file.... + if fs.lstatSync(value).isDirectory() + utils.info "- Mapping static #{route} to dir #{value}" + statics.use(route, checkForRedirect()) + statics.use(route, connect.static(value) ) + else + utils.info "- Mapping static #{route} to resource #{value}" + statics.use route, do (value) -> + (req, res) -> + fs.readFile value, (err, data) -> + if err + res.writeHead(404) + res.end(JSON.stringify(err)) + return + res.writeHead(200) + res.end(data) + else utils.errorAndExit "The folder #{value} does not exist for static mapping #{route}" From b41f61d280f718675fe66fdd782eedb6484a35e0 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 8 Aug 2013 16:37:32 -0500 Subject: [PATCH 055/167] not sure how that stuff got committed, perhaps a sign of sleep deprivation?? --- src/server.coffee | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/server.coffee b/src/server.coffee index 4fdb0c4..b7ed024 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -71,17 +71,6 @@ server.middleware = (applications, options) -> # check static content statics.handle(req, res, next) - # TODO hey there, why doesn't htis work - - sometfunction: (options = "asdf") => - return "" - - test: () -> -# TODO why??? - # TODO: whyyyy??? - return "" - - more: "asdf" # ------- Private Functions From 7b941ba38317ac0741113a88826f3e9594a9a98a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 8 Aug 2013 16:37:46 -0500 Subject: [PATCH 056/167] fixing way the default port/host are handled --- src/hem.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hem.coffee b/src/hem.coffee index ee5c12c..58caf75 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -92,7 +92,9 @@ class Hem # quick check to make sure slug file exists if fs.existsSync(slug) options = @readSlug(slug) - @options = utils.extend(options, @options) + options.hem?.port or= @options.hem.port + options.hem?.host or= @options.hem.host + @options = options # make sure we are in same directory as slug @homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)) process.chdir(@homeDir) From 68848186310e8b3d5febd3bebc1139378b92f2dc Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 8 Aug 2013 16:37:57 -0500 Subject: [PATCH 057/167] compiled js and version bump --- lib/hem.js | 16 +++++++++++----- lib/server.js | 15 +-------------- package.json | 2 +- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 00542f9..0064708 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -79,7 +79,7 @@ Hem.prototype.apps = []; function Hem(options) { - var config, name, slug, _base, _base1, _ref; + var config, name, slug, _base, _base1, _ref, _ref1, _ref2; if (options == null) { options = {}; } @@ -93,7 +93,13 @@ } if (fs.existsSync(slug)) { options = this.readSlug(slug); - this.options = utils.extend(options, this.options); + if ((_ref = options.hem) != null) { + _ref.port || (_ref.port = this.options.hem.port); + } + if ((_ref1 = options.hem) != null) { + _ref1.host || (_ref1.host = this.options.hem.host); + } + this.options = options; this.homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)); process.chdir(this.homeDir); } else { @@ -104,9 +110,9 @@ } (_base = this.options.hem).host || (_base.host = ""); (_base1 = this.options.hem).routes || (_base1.routes = {}); - _ref = this.options; - for (name in _ref) { - config = _ref[name]; + _ref2 = this.options; + for (name in _ref2) { + config = _ref2[name]; if (name === "hem") { continue; } diff --git a/lib/server.js b/lib/server.js index 8a1bbfd..51c7036 100644 --- a/lib/server.js +++ b/lib/server.js @@ -24,8 +24,7 @@ }; server.middleware = function(applications, options) { - var display, hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2, - _this = this; + var display, hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; for (_i = 0, _len = applications.length; _i < _len; _i++) { hemapp = applications[_i]; utils.info("> Apply route mappings for application: " + hemapp.name + ""); @@ -92,18 +91,6 @@ } return statics.handle(req, res, next); }; - return { - sometfunction: function(options) { - if (options == null) { - options = "asdf"; - } - return ""; - }, - test: function() { - return ""; - }, - more: "asdf" - }; }; checkForRedirect = function() { diff --git a/package.json b/package.json index 8dc72f1..798bff3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.8", + "version": "0.4.9", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 53aedfa91e7bd885b8771d4075bbb808b6b588bf Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 12 Aug 2013 12:25:22 -0500 Subject: [PATCH 058/167] leaving host blank as default so it will bind to both localhost and ip address --- lib/hem.js | 5 ++--- src/hem.coffee | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 0064708..199e24a 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -71,8 +71,7 @@ Hem.prototype.options = { hem: { - port: 9294, - host: "localhost" + port: 9294 } }; @@ -123,7 +122,7 @@ Hem.prototype.server = function() { var value; - value = "http://" + (this.options.hem.host || "localhost") + ":" + this.options.hem.port; + value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; utils.log("Starting Server at " + value + ""); return server.start(this.apps, this.options.hem); }; diff --git a/src/hem.coffee b/src/hem.coffee index 58caf75..5a03466 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -74,7 +74,6 @@ class Hem options: hem: port: 9294 - host: "localhost" # emtpy applications list apps: [] @@ -115,7 +114,7 @@ class Hem # ------- Command Functions server: -> - value = "http://#{@options.hem.host or "localhost"}:#{@options.hem.port}" + value = "http://#{@options.hem.host or "*"}:#{@options.hem.port}" utils.log "Starting Server at #{value}" server.start(@apps, @options.hem) From 7e356e7c9fc3e2019cb931f7d77acc3e27025ea7 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:39:14 -0500 Subject: [PATCH 059/167] updated packages to latest/greatest --- package.json | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 798bff3..7423332 100644 --- a/package.json +++ b/package.json @@ -36,18 +36,27 @@ }, "preferGlobal": true, "dependencies": { - "uglify-js": "~1.3.3", "fast-detective": "~0.0.2", - "optimist": "~0.4.0", - "coffee-script": "~1.6.0", - "watch": "~0.7.0", - "connect": "~2.7.0", - "http-proxy": "~0.10", + "http-proxy": "~0.10.3", "sty": "~0.6.1", - "jade": "~0.30", - "stylus": "~0.32.0", "eco": "1.1.0-rc-3", - "karma": "~0.8.6", - "mkdirp": "~0.3.5" + "uglifycss": "0.0.5", + "karma-script-launcher": "~0.1.0", + "karma-firefox-launcher": "~0.1.0", + "karma-chrome-launcher": "~0.1.0", + "karma-html2js-preprocessor": "~0.1.0", + "karma-coffee-preprocessor": "~0.1.0", + "karma-jasmine": "~0.1.0", + "karma-requirejs": "~0.1.0", + "karma-phantomjs-launcher": "~0.1.0", + "karma": "~0.10.1", + "stylus": "~0.37.0", + "jade": "~0.35.0", + "mkdirp": "~0.3.5", + "watch": "~0.8.0", + "connect": "~2.8.5", + "optimist": "~0.6.0", + "coffee-script": "~1.6.3", + "uglify-js": "~2.3.6" } } From 451381001deac2cf4bfdd76569990c2e78e028f0 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:39:50 -0500 Subject: [PATCH 060/167] removing compress option from stylus compiler, will compress at end of css compile --- lib/compilers.js | 5 ++--- src/compilers.coffee | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 725b6a0..1c0c20f 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,7 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var compileCoffeescript, compilers, cs, eco, err, fs, jade, path, stylus, utils, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + var compileCoffeescript, compilers, cs, eco, err, fs, jade, path, stylus, utils; fs = require('fs'); @@ -109,7 +108,7 @@ var content, result; content = fs.readFileSync(_path, 'utf8'); result = ''; - stylus(content).include(path.dirname(_path)).set('include css', (__indexOf.call(process.argv, '--includeCss') >= 0)).set('compress', utils.COMPRESS).render(function(err, css) { + stylus(content).include(path.dirname(_path)).render(function(err, css) { if (err) { throw err; } diff --git a/src/compilers.coffee b/src/compilers.coffee index 7a1515e..f4ec125 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -3,7 +3,7 @@ path = require('path') utils = require('./utils') compilers = {} -# TODO: wondering if we should make it so that the additional compilers are checked at runtim +# TODO: wondering if we should make it so that the additional compilers are checked at runtime # if the are present. One approach is to install hem inside the project using it. Or another is # to require from the process.cwd() node_modules folder, assuming what is needed is installed there. # TODO: look for it locally require(Module._findPath("spine", [process.cwd() + '/node_modules'])) @@ -25,7 +25,7 @@ try catch err err.message = "Coffeescript Error: " + err.message err.path = "Coffeescript Path: " + path - err.path = err.path + ":" + (err.location.first_line + 1) if err.location + err.path = err.path + ":" + (err.location.first_line + 1) if err.location throw err catch err @@ -56,8 +56,8 @@ compilers.jeco = (path) -> }; """ -require.extensions['.jeco'] = require.extensions['.eco'] # require.extensions['.eco'] in eco package contains the function +require.extensions['.jeco'] = require.extensions['.eco'] compilers.html = (path) -> content = fs.readFileSync(path, 'utf8') @@ -94,8 +94,6 @@ try stylus(content) .include(path.dirname(_path)) # TODO: are there other settings we should be looking at?? - .set('include css', ('--includeCss' in process.argv)) - .set('compress', utils.COMPRESS) .render((err, css) -> throw err if err result = css From 1a64973c7f6910b906e60fad23d0465cabc37505 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:40:14 -0500 Subject: [PATCH 061/167] make sure we only watch directories --- src/package.coffee | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/package.coffee b/src/package.coffee index 177020e..0c3fe1f 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -182,7 +182,15 @@ class Package watch: -> watchOptions = { persistent: true, interval: 1000, ignoreDotFiles: true } - for dir in @getWatchedDirs() + # get dirs to watch + for fileOrDir in @getWatchedDirs() + if utils.isDirectory(fileOrDir) + dirs.push fileOrDir + else + dirs.push path.direname(fileOrDir) + dirs = Utils.removeDuplicateValues(dir) + # start watch process + for dir in dirs continue unless fs.existsSync(dir) require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) From 0a85cbf9659941fbdc903d7601aaa429b8300c9f Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:40:33 -0500 Subject: [PATCH 062/167] little bit cleaner error output --- src/package.coffee | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/package.coffee b/src/package.coffee index 0c3fe1f..b560067 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -153,15 +153,13 @@ class Package utils.errorAndExit("Unable to determine route for #{@target}") unless @route handleCompileError: (ex) -> - if ex.stack - utils.log(ex.stack) - else - utils.error(ex.message) - utils.error ex.path if ex.path - utils.error ex.location if ex.location + # TODO: construct better error message... + # TOTO: having some problems with sty here, hmmm.... + utils.error(ex.message) + utils.error(ex.path) if ex.path # only return when in server/watch mode, otherwise exit switch utils.COMMAND - when "server" or "watch" then return "console.log(\"HEM compile ERROR: #{ex}\");" + when "server" or "watch" then return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" else process.exit(1) unlink: -> From a207130590b6c0085b2507d8334505ae27e97d08 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:41:07 -0500 Subject: [PATCH 063/167] user uglifycss to minify css --- lib/package.js | 39 +++++++++++++++++++++++---------------- lib/utils.js | 20 ++++++++++++++++++-- src/package.coffee | 8 +++++--- src/utils.coffee | 11 +++++++++-- 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/lib/package.js b/lib/package.js index b00816f..d05265a 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglify, utils, versioning, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglifycss, uglifyjs, utils, versioning, __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -9,7 +9,9 @@ path = require('path'); - uglify = require('uglify-js'); + uglifyjs = require('uglify-js'); + + uglifycss = require('uglifycss'); Dependency = require('./dependency'); @@ -212,20 +214,13 @@ } Package.prototype.handleCompileError = function(ex) { - if (ex.stack) { - utils.log(ex.stack); - } else { - utils.error(ex.message); - } + utils.error(ex.message); if (ex.path) { utils.error(ex.path); } - if (ex.location) { - utils.error(ex.location); - } switch (utils.COMMAND) { case "server" || "watch": - return "console.log(\"HEM compile ERROR: " + ex + "\");"; + return "console.log(\"HEM compile ERROR: " + ex + "\n" + ex.path + "\");"; default: return process.exit(1); } @@ -257,7 +252,7 @@ }; Package.prototype.watch = function() { - var dir, watchOptions, _i, _len, _ref, _results, + var dir, dirs, fileOrDir, watchOptions, _i, _j, _len, _len1, _ref, _results, _this = this; watchOptions = { persistent: true, @@ -265,9 +260,18 @@ ignoreDotFiles: true }; _ref = this.getWatchedDirs(); - _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - dir = _ref[_i]; + fileOrDir = _ref[_i]; + if (utils.isDirectory(fileOrDir)) { + dirs.push(fileOrDir); + } else { + dirs.push(path.direname(fileOrDir)); + } + } + dirs = Utils.removeDuplicateValues(dir); + _results = []; + for (_j = 0, _len1 = dirs.length; _j < _len1; _j++) { + dir = dirs[_j]; if (!fs.existsSync(dir)) { continue; } @@ -306,7 +310,7 @@ try { result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); if (utils.COMPRESS) { - result = uglify(result); + result = uglifyjs(result); } return result; } catch (_error) { @@ -402,7 +406,10 @@ delete require.cache[_path]; result.push(require(_path)); } - return result.join("\n"); + result.join("\n"); + if (utils.COMPRESS) { + return uglifycss(result); + } } catch (_error) { ex = _error; return this.handleCompileError(ex); diff --git a/lib/utils.js b/lib/utils.js index 77dbb0e..c08a63a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ // Generated by CoffeeScript 1.6.3 (function() { var clean, extend, flatten, fs, isWin, path, sty, utils, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; sty = require('sty'); @@ -46,6 +47,18 @@ } }; + utils.removeDuplicateValues = function(array) { + var newArray, value, _i, _len; + newArray = []; + for (_i = 0, _len = array.length; _i < _len; _i++) { + value = array[_i]; + if (__indexOf.call(array, value) < 0) { + newArray.push(value); + } + } + return newArray; + }; + utils.toArray = function(value) { if (value == null) { value = []; @@ -143,8 +156,11 @@ } }; - utils.error = function(message) { - return console.log("" + (sty.red('ERROR:')) + " " + (sty.parse(message))); + utils.error = function(message, parse) { + if (parse == null) { + parse = true; + } + return console.log("" + (sty.red('ERROR:')) + " " + (parse && sty.parse(message) || message)); }; utils.errorAndExit = function(error) { diff --git a/src/package.coffee b/src/package.coffee index b560067..fda9bd0 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -1,6 +1,7 @@ fs = require('fs') path = require('path') -uglify = require('uglify-js') +uglifyjs = require('uglify-js') +uglifycss = require('uglifycss') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') @@ -216,7 +217,7 @@ class JsPackage extends Package compile: -> try result = [@compileLibs(), @compileModules(), @after].join("\n") - result = uglify(result) if utils.COMPRESS + result = uglifyjs(result) if utils.COMPRESS result catch ex @handleCompileError(ex) @@ -291,8 +292,9 @@ class CssPackage extends Package _path = require.resolve(path.resolve(_path)) delete require.cache[_path] result.push require(_path) - # TODO: do we want a minify option for css or is that built into the compilers?? result.join("\n") + # minify + uglifycss(result) if utils.COMPRESS catch ex @handleCompileError(ex) diff --git a/src/utils.coffee b/src/utils.coffee index 1ec027b..854ae39 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -23,6 +23,13 @@ utils.arrayToString = (value = "") -> else value +utils.removeDuplicateValues = (array) -> + newArray = [] + for value in array + if value not in array + newArray.push(value) + newArray + utils.toArray = (value = []) -> if Array.isArray(value) then value else [value] @@ -89,8 +96,8 @@ utils.log = (message) -> utils.info = (message) -> console.log sty.parse(message) if @VERBOSE -utils.error = (message) -> - console.log "#{sty.red 'ERROR:'} #{sty.parse(message)}" +utils.error = (message, parse = true) -> + console.log "#{sty.red 'ERROR:'} #{parse and sty.parse(message) or message}" utils.errorAndExit = (error) -> utils.error(error) From b31d30296721969e6e5a03eebb7bd39e4b35f5a3 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:50:51 -0500 Subject: [PATCH 064/167] adding less to css compilers --- lib/compilers.js | 25 ++++++++++++++++++++++++- package.json | 3 ++- src/compilers.coffee | 16 ++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 1c0c20f..68fc007 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var compileCoffeescript, compilers, cs, eco, err, fs, jade, path, stylus, utils; + var compileCoffeescript, compilers, cs, eco, err, fs, jade, less, path, stylus, utils; fs = require('fs'); @@ -125,6 +125,29 @@ err = _error; } + try { + less = require('less'); + compilers.less = function(_path) { + var content, result; + content = fs.readFileSync(_path, 'utf8'); + result = ''; + less.render(content, function(err, css) { + if (err) { + throw err; + } + return result = css; + }); + return result; + }; + require.extensions['.less'] = function(module, filename) { + var source; + source = JSON.stringify(compilers.less(filename)); + return module._compile("module.exports = " + source, filename); + }; + } catch (_error) { + err = _error; + } + compilers.env = function(path) { var content, envhash, key, packjson; content = fs.readFileSync(path, 'utf8'); diff --git a/package.json b/package.json index 7423332..916e299 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "connect": "~2.8.5", "optimist": "~0.6.0", "coffee-script": "~1.6.3", - "uglify-js": "~2.3.6" + "uglify-js": "~2.3.6", + "less": "~1.4.2" } } diff --git a/src/compilers.coffee b/src/compilers.coffee index f4ec125..58f7d45 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -105,6 +105,22 @@ try module._compile "module.exports = #{source}", filename catch err +try + less = require('less') + + compilers.less = (_path) -> + content = fs.readFileSync(_path, 'utf8') + result = '' + less.render content, (err, css) -> + throw err if err + result = css + result + + require.extensions['.less'] = (module, filename) -> + source = JSON.stringify(compilers.less(filename)) + module._compile "module.exports = #{source}", filename +catch err + # create a javascript module based off key values found in environment compilers.env = (path) -> From 1eac148bbda03de08eaa764fee5040a0392a0212 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:53:06 -0500 Subject: [PATCH 065/167] used wrong function call for uglifycss --- src/package.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.coffee b/src/package.coffee index fda9bd0..763d682 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -294,7 +294,7 @@ class CssPackage extends Package result.push require(_path) result.join("\n") # minify - uglifycss(result) if utils.COMPRESS + uglifycss.processString(result) if utils.COMPRESS catch ex @handleCompileError(ex) From dcc2132101db213eab85a6491161bfc6731fd970 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 12:53:24 -0500 Subject: [PATCH 066/167] needed to compile js --- lib/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/package.js b/lib/package.js index d05265a..87d3e8e 100644 --- a/lib/package.js +++ b/lib/package.js @@ -408,7 +408,7 @@ } result.join("\n"); if (utils.COMPRESS) { - return uglifycss(result); + return uglifycss.processString(result); } } catch (_error) { ex = _error; From 59d9dd2ac500d58bf9ad062f575cf1496b289d95 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 13:40:15 -0500 Subject: [PATCH 067/167] redoing the css compile to work with dirs and individual files --- lib/package.js | 31 +++++++++++++++++++++++-------- src/package.coffee | 33 ++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/lib/package.js b/lib/package.js index 87d3e8e..2260cd1 100644 --- a/lib/package.js +++ b/lib/package.js @@ -396,20 +396,35 @@ } CssPackage.prototype.compile = function() { - var ex, result, _i, _len, _path, _ref; + var ex, file, fileOrDir, output, requireCss, result, _i, _j, _len, _len1, _ref, _ref1; try { - result = []; + output = []; + requireCss = function(filepath) { + filepath = require.resolve(path.resolve(filepath)); + delete require.cache[filepath]; + return require(filepath); + }; _ref = this.paths; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - _path = _ref[_i]; - _path = require.resolve(path.resolve(_path)); - delete require.cache[_path]; - result.push(require(_path)); + fileOrDir = _ref[_i]; + if (utils.isDirectory(fileOrDir)) { + _ref1 = fs.readdirSync(fileOrDir); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + file = _ref1[_j]; + file = path.resolve(fileOrDir, file); + if (!utils.isDirectory(file)) { + output.push(requireCss(file)); + } + } + } else { + output.push(requireCss(fileOrDir)); + } } - result.join("\n"); + result = output.join("\n"); if (utils.COMPRESS) { - return uglifycss.processString(result); + result = uglifycss.processString(result); } + return result; } catch (_error) { ex = _error; return this.handleCompileError(ex); diff --git a/src/package.coffee b/src/package.coffee index 763d682..2817748 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -284,17 +284,28 @@ class CssPackage extends Package compile: () -> try - result = [] - for _path in @paths - # TODO: currently this only works with index files, perhaps someday loop - # over the directory contents and pickup the other files?? though with - # stylus can always get other content by mixins - _path = require.resolve(path.resolve(_path)) - delete require.cache[_path] - result.push require(_path) - result.join("\n") - # minify - uglifycss.processString(result) if utils.COMPRESS + output = [] + + # helper function to perform compiles + requireCss = (filepath) -> + filepath = require.resolve(path.resolve(filepath)) + delete require.cache[filepath] + require(filepath) + + # loop over path values + for fileOrDir in @paths + # if directory loop over all top level files only + if utils.isDirectory(fileOrDir) + for file in fs.readdirSync(fileOrDir) + file = path.resolve(fileOrDir, file) + output.push requireCss(file) unless utils.isDirectory(file) + else + output.push requireCss(fileOrDir) + + # join and minify + result = output.join("\n") + result = uglifycss.processString(result) if utils.COMPRESS + result catch ex @handleCompileError(ex) From e1400a9db1d335d163722ee6d3f96b9031cacdb1 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 13:48:32 -0500 Subject: [PATCH 068/167] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 916e299..1138b82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.9", + "version": "0.4.10", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 02ee8ea57947c339e87648b8904052f87f6b4dd7 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 14:13:19 -0500 Subject: [PATCH 069/167] make sure we only look at files we can actuall require/compile --- lib/package.js | 3 +++ src/package.coffee | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/package.js b/lib/package.js index 2260cd1..7bffff0 100644 --- a/lib/package.js +++ b/lib/package.js @@ -411,6 +411,9 @@ _ref1 = fs.readdirSync(fileOrDir); for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { file = _ref1[_j]; + if (!require.extensions[path.extname(file)]) { + continue; + } file = path.resolve(fileOrDir, file); if (!utils.isDirectory(file)) { output.push(requireCss(file)); diff --git a/src/package.coffee b/src/package.coffee index 2817748..751ab99 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -296,7 +296,7 @@ class CssPackage extends Package for fileOrDir in @paths # if directory loop over all top level files only if utils.isDirectory(fileOrDir) - for file in fs.readdirSync(fileOrDir) + for file in fs.readdirSync(fileOrDir) when require.extensions[path.extname(file)] file = path.resolve(fileOrDir, file) output.push requireCss(file) unless utils.isDirectory(file) else From 2eafae61b093c25db6999b244a0663ca2214d00b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 14:15:19 -0500 Subject: [PATCH 070/167] simplify code --- lib/package.js | 4 +--- src/package.coffee | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/package.js b/lib/package.js index 7bffff0..161a240 100644 --- a/lib/package.js +++ b/lib/package.js @@ -415,9 +415,7 @@ continue; } file = path.resolve(fileOrDir, file); - if (!utils.isDirectory(file)) { - output.push(requireCss(file)); - } + output.push(requireCss(file)); } } else { output.push(requireCss(fileOrDir)); diff --git a/src/package.coffee b/src/package.coffee index 751ab99..6f721e3 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -298,7 +298,7 @@ class CssPackage extends Package if utils.isDirectory(fileOrDir) for file in fs.readdirSync(fileOrDir) when require.extensions[path.extname(file)] file = path.resolve(fileOrDir, file) - output.push requireCss(file) unless utils.isDirectory(file) + output.push requireCss(file) else output.push requireCss(fileOrDir) From 53f4c40132c375bf1ce36880ad1411f668e03a88 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 16:47:34 -0500 Subject: [PATCH 071/167] fixed some issues with the hem watch feature --- lib/package.js | 11 ++++++----- lib/utils.js | 2 +- src/package.coffee | 7 ++++--- src/utils.coffee | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/package.js b/lib/package.js index 161a240..fb92532 100644 --- a/lib/package.js +++ b/lib/package.js @@ -259,22 +259,23 @@ interval: 1000, ignoreDotFiles: true }; + dirs = []; _ref = this.getWatchedDirs(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { fileOrDir = _ref[_i]; + if (!fs.existsSync(fileOrDir)) { + continue; + } if (utils.isDirectory(fileOrDir)) { dirs.push(fileOrDir); } else { - dirs.push(path.direname(fileOrDir)); + dirs.push(path.dirname(fileOrDir)); } } - dirs = Utils.removeDuplicateValues(dir); + dirs = utils.removeDuplicateValues(dirs); _results = []; for (_j = 0, _len1 = dirs.length; _j < _len1; _j++) { dir = dirs[_j]; - if (!fs.existsSync(dir)) { - continue; - } _results.push(require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { return _this.build(); diff --git a/lib/utils.js b/lib/utils.js index c08a63a..b51f2f7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -52,7 +52,7 @@ newArray = []; for (_i = 0, _len = array.length; _i < _len; _i++) { value = array[_i]; - if (__indexOf.call(array, value) < 0) { + if (__indexOf.call(newArray, value) < 0) { newArray.push(value); } } diff --git a/src/package.coffee b/src/package.coffee index 6f721e3..a1c16f3 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -182,15 +182,16 @@ class Package watch: -> watchOptions = { persistent: true, interval: 1000, ignoreDotFiles: true } # get dirs to watch + dirs = [] for fileOrDir in @getWatchedDirs() + continue unless fs.existsSync(fileOrDir) if utils.isDirectory(fileOrDir) dirs.push fileOrDir else - dirs.push path.direname(fileOrDir) - dirs = Utils.removeDuplicateValues(dir) + dirs.push path.dirname(fileOrDir) + dirs = utils.removeDuplicateValues(dirs) # start watch process for dir in dirs - continue unless fs.existsSync(dir) require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() diff --git a/src/utils.coffee b/src/utils.coffee index 854ae39..58be241 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -25,8 +25,8 @@ utils.arrayToString = (value = "") -> utils.removeDuplicateValues = (array) -> newArray = [] - for value in array - if value not in array + for value in array + if value not in newArray newArray.push(value) newArray From 87443f24e2ea6b0dd9d4995d004eb699193b6948 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 17:03:37 -0500 Subject: [PATCH 072/167] additional tweaks to hem watch service --- lib/compilers.js | 2 -- lib/package.js | 31 +++++++++++++++++++------------ src/compilers.coffee | 2 -- src/package.coffee | 9 ++++++++- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 68fc007..e75f81f 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -156,11 +156,9 @@ for (key in envhash) { if (process.env[key]) { envhash[key] = process.env[key]; - utils.info("- Set env " + key + " to " + envhash[key] + ""); } if (packjson[key]) { envhash[key] = packjson[key]; - utils.info(" - Set env " + key + " to " + envhash[key] + ""); } } return "module.exports = " + JSON.stringify(envhash); diff --git a/lib/package.js b/lib/package.js index fb92532..f488edf 100644 --- a/lib/package.js +++ b/lib/package.js @@ -125,15 +125,23 @@ }; Application.prototype.watch = function() { - var key, pkg, _ref, _results; + var dirs, key, pkg; utils.log("Watching application: " + this.name + ""); - _ref = this.packages; - _results = []; - for (key in _ref) { - pkg = _ref[key]; - _results.push(pkg.watch()); + dirs = (function() { + var _ref, _results; + _ref = this.packages; + _results = []; + for (key in _ref) { + pkg = _ref[key]; + _results.push(pkg.watch()); + } + return _results; + }).call(this); + if (dirs.length) { + return utils.info("- Watching directories: " + dirs + ""); + } else { + return utils.info("- No directories to watch..."); } - return _results; }; Application.prototype.version = function() { @@ -252,7 +260,7 @@ }; Package.prototype.watch = function() { - var dir, dirs, fileOrDir, watchOptions, _i, _j, _len, _len1, _ref, _results, + var dir, dirs, fileOrDir, watchOptions, _i, _j, _len, _len1, _ref, _this = this; watchOptions = { persistent: true, @@ -273,16 +281,15 @@ } } dirs = utils.removeDuplicateValues(dirs); - _results = []; for (_j = 0, _len1 = dirs.length; _j < _len1; _j++) { dir = dirs[_j]; - _results.push(require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { + require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { return _this.build(); } - })); + }); } - return _results; + return dirs; }; Package.prototype.getWatchedDirs = function() { diff --git a/src/compilers.coffee b/src/compilers.coffee index 58f7d45..479c232 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -131,10 +131,8 @@ compilers.env = (path) -> for key of envhash if process.env[key] envhash[key] = process.env[key] - utils.info "- Set env #{key} to #{envhash[key]}" if packjson[key] envhash[key] = packjson[key] - utils.info " - Set env #{key} to #{envhash[key]}" # return javascript module return "module.exports = " + JSON.stringify(envhash) diff --git a/src/package.coffee b/src/package.coffee index a1c16f3..1c273eb 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -93,7 +93,13 @@ class Application watch: -> utils.log("Watching application: #{@name}") - pkg.watch() for key, pkg of @packages + dirs = (pkg.watch() for key, pkg of @packages) + # make sure dirs has valid values + if dirs.length + utils.info("- Watching directories: #{dirs}") + else + utils.info("- No directories to watch...") + version: -> utils.log("Versioning application: #{@name}") @@ -195,6 +201,7 @@ class Package require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() + dirs getWatchedDirs: -> return @paths From c837105cb13c85de6d5a67fc6781b75f54927c70 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 19:08:11 -0500 Subject: [PATCH 073/167] first take at loading local modules for the compiling --- lib/compilers.js | 250 ++++++++++++++++++++++--------------------- src/compilers.coffee | 216 +++++++++++++++++++++---------------- 2 files changed, 254 insertions(+), 212 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index e75f81f..c3661dd 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var compileCoffeescript, compilers, cs, eco, err, fs, jade, less, path, stylus, utils; + var compileCoffeescript, compilers, cs, fs, lmCache, path, projectPath, requireLocalModule, utils; fs = require('fs'); @@ -10,8 +10,27 @@ compilers = {}; - compilers.js = compilers.css = function(path) { - return fs.readFileSync(path, 'utf8'); + lmCache = {}; + + projectPath = path.resolve(process.cwd()); + + requireLocalModule = function(localModule) { + var error, modulePath; + modulePath = "" + projectPath + "/node_modules/" + localModule; + try { + return lmCache[localModule] || (lmCache[localModule] = require(modulePath)); + } catch (_error) { + error = _error; + utils.error("Unable to load " + localModule + " module. Try to use 'npm install " + localModule + "' in your project directory."); + if (utils.VERBOSE) { + console.log(error); + } + return process.exit(); + } + }; + + compilers.js = compilers.css = function(_path) { + return fs.readFileSync(_path, 'utf8'); }; require.extensions['.css'] = function(module, filename) { @@ -20,146 +39,135 @@ return module._compile("module.exports = " + source, filename); }; - try { - cs = require('coffee-script'); - compilers.coffee = function(path) { - return compileCoffeescript(path); - }; - compilers.litcoffee = function(path) { - return compileCoffeescript(path, true); - }; - compileCoffeescript = function(path, literate) { - var err; - if (literate == null) { - literate = false; - } - try { - return cs.compile(fs.readFileSync(path, 'utf8'), { - filename: path, - literate: literate - }); - } catch (_error) { - err = _error; - err.message = "Coffeescript Error: " + err.message; - err.path = "Coffeescript Path: " + path; - if (err.location) { - err.path = err.path + ":" + (err.location.first_line + 1); - } - throw err; - } - }; - } catch (_error) { - err = _error; - } + compilers.html = function(_path) { + var content; + content = fs.readFileSync(_path, 'utf8'); + return "module.exports = " + (JSON.stringify(content)) + ";\n"; + }; + + require.extensions['.html'] = function(module, filename) { + return module._compile(compilers.html(filename), filename); + }; - eco = require('eco'); + cs = require('coffee-script'); - compilers.eco = function(path) { - var content; - content = eco.precompile(fs.readFileSync(path, 'utf8')); + compilers.coffee = function(_path) { + return compileCoffeescript(_path); + }; + + compilers.litcoffee = function(_path) { + return compileCoffeescript(_path, true); + }; + + compileCoffeescript = function(_path, literate) { + var err; + if (literate == null) { + literate = false; + } + try { + return cs.compile(fs.readFileSync(_path, 'utf8'), { + filename: _path, + literate: literate + }); + } catch (_error) { + err = _error; + err.message = "Coffeescript Error: " + err.message; + err.path = "Coffeescript Path: " + _path; + if (err.location) { + err.path = err.path + ":" + (err.location.first_line + 1); + } + throw err; + } + }; + + compilers.eco = function(_path) { + var content, eco; + eco = requireLocalModule('eco'); + content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "var content = " + content + ";\nmodule.exports = content;"; }; - compilers.jeco = function(path) { + compilers.jeco = function(_path) { var content; - content = eco.precompile(fs.readFileSync(path, 'utf8')); + content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "module.exports = function(values, data){ \n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; }; require.extensions['.jeco'] = require.extensions['.eco']; - compilers.html = function(path) { - var content; - content = fs.readFileSync(path, 'utf8'); - return "module.exports = " + (JSON.stringify(content)) + ";\n"; + compilers.jade = function(_path) { + var content, ex, jade, source, template; + jade = requireLocalModule('jade'); + content = fs.readFileSync(_path, 'utf8'); + try { + template = jade.compile(content, { + filename: _path, + client: true + }); + source = template.toString(); + return "module.exports = " + source + ";"; + } catch (_error) { + ex = _error; + throw new Error("" + ex + " in " + _path); + } }; - require.extensions['.html'] = function(module, filename) { - return module._compile(compilers.html(filename), filename); + require.extensions['.jade'] = function(module, filename) { + return module._compile(compilers.jade(filename), filename); }; - try { - jade = require('jade'); - compilers.jade = function(path) { - var content, ex, source, template; - content = fs.readFileSync(path, 'utf8'); - try { - template = jade.compile(content, { - filename: path, - client: true - }); - source = template.toString(); - return "module.exports = " + source + ";"; - } catch (_error) { - ex = _error; - throw new Error("" + ex + " in " + path); + compilers.stylus = function(_path) { + var content, result, stylus; + stylus = requireLocalModule('stylus'); + content = fs.readFileSync(_path, 'utf8'); + result = ''; + stylus(content).include(path.dirname(_path)).render(function(err, css) { + if (err) { + throw err; } - }; - require.extensions['.jade'] = function(module, filename) { - return module._compile(compilers.jade(filename), filename); - }; - } catch (_error) { - err = _error; - } - - try { - stylus = require('stylus'); - compilers.styl = function(_path) { - var content, result; - content = fs.readFileSync(_path, 'utf8'); - result = ''; - stylus(content).include(path.dirname(_path)).render(function(err, css) { - if (err) { - throw err; - } - return result = css; - }); - return result; - }; - require.extensions['.styl'] = function(module, filename) { - var source; - source = JSON.stringify(compilers.styl(filename)); - return module._compile("module.exports = " + source, filename); - }; - } catch (_error) { - err = _error; - } - - try { - less = require('less'); - compilers.less = function(_path) { - var content, result; - content = fs.readFileSync(_path, 'utf8'); - result = ''; - less.render(content, function(err, css) { - if (err) { - throw err; - } - return result = css; - }); - return result; - }; - require.extensions['.less'] = function(module, filename) { - var source; - source = JSON.stringify(compilers.less(filename)); - return module._compile("module.exports = " + source, filename); - }; - } catch (_error) { - err = _error; - } - - compilers.env = function(path) { + return result = css; + }); + return result; + }; + + require.extensions['.styl'] = function(module, filename) { + var source; + source = JSON.stringify(compilers.stylus(filename)); + return module._compile("module.exports = " + source, filename); + }; + + compilers.less = function(_path) { + var content, less, result; + less = requireLocalModule('less'); + content = fs.readFileSync(_path, 'utf8'); + result = ''; + less.render(content, function(err, css) { + if (err) { + throw err; + } + return result = css; + }); + return result; + }; + + require.extensions['.less'] = function(module, filename) { + var source; + source = JSON.stringify(compilers.less(filename)); + return module._compile("module.exports = " + source, filename); + }; + + compilers.env = function(_path) { var content, envhash, key, packjson; - content = fs.readFileSync(path, 'utf8'); + content = fs.readFileSync(_path, 'utf8'); envhash = JSON.parse(content); - packjson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + packjson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf8')); for (key in envhash) { - if (process.env[key]) { - envhash[key] = process.env[key]; - } if (packjson[key]) { envhash[key] = packjson[key]; } + if (process.env[key]) { + envhash[key] = process.env[key]; + } } return "module.exports = " + JSON.stringify(envhash); }; diff --git a/src/compilers.coffee b/src/compilers.coffee index 479c232..9645e22 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -2,45 +2,78 @@ fs = require('fs') path = require('path') utils = require('./utils') compilers = {} +lmCache = {} -# TODO: wondering if we should make it so that the additional compilers are checked at runtime -# if the are present. One approach is to install hem inside the project using it. Or another is -# to require from the process.cwd() node_modules folder, assuming what is needed is installed there. -# TODO: look for it locally require(Module._findPath("spine", [process.cwd() + '/node_modules'])) +# Load the modules from the project directory (instead of from the hem +# node_modules). This allows a lot of the different javascript/css pre +# compilers to be installed in the project vs having to be included with +# the hem package. -compilers.js = compilers.css = (path) -> - fs.readFileSync path, 'utf8' +# setup project path +projectPath = path.resolve process.cwd() + +# helper fuction to perform load/caching of modules +requireLocalModule = (localModule) -> + modulePath = "#{projectPath}/node_modules/#{localModule}" + try + lmCache[localModule] or= require modulePath + catch error + utils.error("Unable to load #{localModule} module. Try to use 'npm install #{localModule}' in your project directory.") + console.log error if utils.VERBOSE + process.exit() + +## +## Basic javascript/css files +## + +compilers.js = compilers.css = (_path) -> + fs.readFileSync _path, 'utf8' require.extensions['.css'] = (module, filename) -> source = JSON.stringify(compilers.css(filename)) module._compile "module.exports = #{source}", filename -try - cs = require 'coffee-script' - compilers.coffee = (path) -> compileCoffeescript(path) - compilers.litcoffee = (path) -> compileCoffeescript(path, true) - compileCoffeescript = (path, literate = false) -> - try - cs.compile(fs.readFileSync(path, 'utf8'), filename: path, literate: literate) - catch err - err.message = "Coffeescript Error: " + err.message - err.path = "Coffeescript Path: " + path - err.path = err.path + ":" + (err.location.first_line + 1) if err.location - throw err -catch err - -# TODO: make eco conditional with try/catch -eco = require 'eco' - -compilers.eco = (path) -> - content = eco.precompile fs.readFileSync path, 'utf8' +## +## HTML files +## + +compilers.html = (_path) -> + content = fs.readFileSync(_path, 'utf8') + "module.exports = #{JSON.stringify(content)};\n" + +require.extensions['.html'] = (module, filename) -> + module._compile compilers.html(filename), filename + +## +## Compile Coffeescript +## + +cs = require 'coffee-script' +compilers.coffee = (_path) -> compileCoffeescript(_path) +compilers.litcoffee = (_path) -> compileCoffeescript(_path, true) +compileCoffeescript = (_path, literate = false) -> + try + cs.compile(fs.readFileSync(_path, 'utf8'), filename: _path, literate: literate) + catch err + err.message = "Coffeescript Error: " + err.message + err.path = "Coffeescript Path: " + _path + err.path = err.path + ":" + (err.location.first_line + 1) if err.location + throw err + +## +## Eco and Jeco Compiler +## + +compilers.eco = (_path) -> + eco = requireLocalModule('eco') + content = eco.precompile fs.readFileSync _path, 'utf8' """ var content = #{content}; module.exports = content; """ -compilers.jeco = (path) -> - content = eco.precompile fs.readFileSync path, 'utf8' +compilers.jeco = (_path) -> + content = eco.precompile fs.readFileSync _path, 'utf8' """ module.exports = function(values, data){ var $ = jQuery, result = $(); @@ -59,80 +92,81 @@ compilers.jeco = (path) -> # require.extensions['.eco'] in eco package contains the function require.extensions['.jeco'] = require.extensions['.eco'] -compilers.html = (path) -> - content = fs.readFileSync(path, 'utf8') - "module.exports = #{JSON.stringify(content)};\n" - -require.extensions['.html'] = (module, filename) -> - module._compile compilers.html(filename), filename - -try - jade = require('jade') - - compilers.jade = (path) -> - content = fs.readFileSync(path, 'utf8') - try - template = jade.compile content, - filename: path - # compileDebug: compilers.DEBUG - client: true - source = template.toString() - "module.exports = #{source};" - catch ex - throw new Error("#{ex} in #{path}") - - require.extensions['.jade'] = (module, filename) -> - module._compile compilers.jade(filename), filename -catch err - -try - stylus = require('stylus') - - compilers.styl = (_path) -> - content = fs.readFileSync(_path, 'utf8') - result = '' - stylus(content) - .include(path.dirname(_path)) - # TODO: are there other settings we should be looking at?? - .render((err, css) -> - throw err if err - result = css - ) - result - - require.extensions['.styl'] = (module, filename) -> - source = JSON.stringify(compilers.styl(filename)) - module._compile "module.exports = #{source}", filename -catch err - -try - less = require('less') - - compilers.less = (_path) -> - content = fs.readFileSync(_path, 'utf8') - result = '' - less.render content, (err, css) -> +## +## Jade Compiler +## + +compilers.jade = (_path) -> + jade = requireLocalModule('jade') + content = fs.readFileSync(_path, 'utf8') + try + template = jade.compile content, + filename: _path + # compileDebug: compilers.DEBUG + client: true + source = template.toString() + "module.exports = #{source};" + catch ex + throw new Error("#{ex} in #{_path}") + +require.extensions['.jade'] = (module, filename) -> + module._compile compilers.jade(filename), filename + +## +## Stylus Compiler +## + +compilers.stylus = (_path) -> + stylus = requireLocalModule('stylus') + content = fs.readFileSync(_path, 'utf8') + result = '' + stylus(content) + .include(path.dirname(_path)) + .render((err, css) -> throw err if err result = css - result + ) + result + +require.extensions['.styl'] = (module, filename) -> + source = JSON.stringify(compilers.stylus(filename)) + module._compile "module.exports = #{source}", filename - require.extensions['.less'] = (module, filename) -> - source = JSON.stringify(compilers.less(filename)) - module._compile "module.exports = #{source}", filename -catch err +## +## Less Compiler +## + +compilers.less = (_path) -> + less = requireLocalModule('less') + content = fs.readFileSync(_path, 'utf8') + result = '' + less.render content, (err, css) -> + throw err if err + result = css + result + +require.extensions['.less'] = (module, filename) -> + source = JSON.stringify(compilers.less(filename)) + module._compile "module.exports = #{source}", filename + +## +## Environment Compiler +## -# create a javascript module based off key values found in environment +# This creates a javascript module based off key values found in environment +# variables or in the package.json file. Usefule for inserting build info +# that would come frome a CI server (like jenkins) -compilers.env = (path) -> - content = fs.readFileSync(path, 'utf8') +compilers.env = (_path) -> + content = fs.readFileSync(_path, 'utf8') envhash = JSON.parse(content) - packjson = JSON.parse(fs.readFileSync('./package.json', 'utf8')) + packjson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf8')) # loop over values in file for key of envhash - if process.env[key] - envhash[key] = process.env[key] if packjson[key] envhash[key] = packjson[key] + if process.env[key] + envhash[key] = process.env[key] # return javascript module return "module.exports = " + JSON.stringify(envhash) From 5bf0a9a0286625d917ec9c2f3a50eb54229bd766 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 21 Aug 2013 19:08:45 -0500 Subject: [PATCH 074/167] removing less --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 1138b82..b6cd4bc 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "connect": "~2.8.5", "optimist": "~0.6.0", "coffee-script": "~1.6.3", - "uglify-js": "~2.3.6", - "less": "~1.4.2" + "uglify-js": "~2.3.6" } } From 4e5ba5447b19e8f6fc1c0a5ed9fee430da83fa41 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 11:40:11 -0500 Subject: [PATCH 075/167] removing most precompilers from hem, install locally --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index b6cd4bc..db03758 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,6 @@ "karma-requirejs": "~0.1.0", "karma-phantomjs-launcher": "~0.1.0", "karma": "~0.10.1", - "stylus": "~0.37.0", - "jade": "~0.35.0", "mkdirp": "~0.3.5", "watch": "~0.8.0", "connect": "~2.8.5", From 32e77a175acecb0a63ff47c6ae5ca6ff93801504 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 11:40:37 -0500 Subject: [PATCH 076/167] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db03758..43bc9e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.10", + "version": "0.4.11", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 9a89c3b58a2b362f88281de74f83b0d2a5e3bdc9 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 11:46:23 -0500 Subject: [PATCH 077/167] ok, still need eco for the time being but will remove it soon --- lib/compilers.js | 7 ++++--- src/compilers.coffee | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index c3661dd..adae8f9 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var compileCoffeescript, compilers, cs, fs, lmCache, path, projectPath, requireLocalModule, utils; + var compileCoffeescript, compilers, cs, eco, fs, lmCache, path, projectPath, requireLocalModule, utils; fs = require('fs'); @@ -80,9 +80,10 @@ } }; + eco = require('eco'); + compilers.eco = function(_path) { - var content, eco; - eco = requireLocalModule('eco'); + var content; content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "var content = " + content + ";\nmodule.exports = content;"; }; diff --git a/src/compilers.coffee b/src/compilers.coffee index 9645e22..1dcaaf3 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -64,8 +64,8 @@ compileCoffeescript = (_path, literate = false) -> ## Eco and Jeco Compiler ## +eco = require('eco') compilers.eco = (_path) -> - eco = requireLocalModule('eco') content = eco.precompile fs.readFileSync _path, 'utf8' """ var content = #{content}; From d35e052f45d3e7b6f50f12190339aed51303af95 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 11:55:12 -0500 Subject: [PATCH 078/167] some additional comments --- src/compilers.coffee | 2 +- src/package.coffee | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/compilers.coffee b/src/compilers.coffee index 1dcaaf3..ff2b6da 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -64,7 +64,7 @@ compileCoffeescript = (_path, literate = false) -> ## Eco and Jeco Compiler ## -eco = require('eco') +eco = require 'eco' compilers.eco = (_path) -> content = eco.precompile fs.readFileSync _path, 'utf8' """ diff --git a/src/package.coffee b/src/package.coffee index 1c273eb..955653d 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -160,14 +160,17 @@ class Package utils.errorAndExit("Unable to determine route for #{@target}") unless @route handleCompileError: (ex) -> - # TODO: construct better error message... - # TOTO: having some problems with sty here, hmmm.... + # TODO: construct better error message, one that works for all precompilers, + # having some problems with sty here, hmmm.... utils.error(ex.message) utils.error(ex.path) if ex.path # only return when in server/watch mode, otherwise exit switch utils.COMMAND - when "server" or "watch" then return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" - else process.exit(1) + when "server" or "watch" + # TODO: only return this for javascript + return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" + else + process.exit(1) unlink: -> if fs.existsSync(@target) From 750113b463521fdc9fd0568462fea8a758f83b79 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 12:29:51 -0500 Subject: [PATCH 079/167] goodbye eco --- lib/compilers.js | 10 ++++---- lib/package.js | 8 ++---- lib/stitch.js | 13 ++++++++++ package.json | 3 +-- src/compilers.coffee | 3 ++- src/package.coffee | 3 +-- src/stitch.coffee | 60 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 84 insertions(+), 16 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index adae8f9..2632ef2 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var compileCoffeescript, compilers, cs, eco, fs, lmCache, path, projectPath, requireLocalModule, utils; + var compileCoffeescript, compilers, cs, fs, lmCache, path, projectPath, requireLocalModule, utils; fs = require('fs'); @@ -80,16 +80,16 @@ } }; - eco = require('eco'); - compilers.eco = function(_path) { - var content; + var content, eco; + eco = requireLocalModule('eco'); content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "var content = " + content + ";\nmodule.exports = content;"; }; compilers.jeco = function(_path) { - var content; + var content, eco; + eco = requireLocalModule('eco'); content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "module.exports = function(values, data){ \n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; }; diff --git a/lib/package.js b/lib/package.js index f488edf..b1c2138 100644 --- a/lib/package.js +++ b/lib/package.js @@ -328,16 +328,12 @@ }; JsPackage.prototype.compileModules = function() { - var _modules, _stitch, _template; + var _modules, _stitch; this.depend || (this.depend = new Dependency(this.modules)); _stitch = new Stitch(this.paths); _modules = this.depend.resolve().concat(_stitch.resolve()); if (_modules) { - _template = utils.loadAsset('stitch'); - return _template({ - identifier: this.identifier, - modules: _modules - }); + return _stitch.template(this.identifier, _modules); } else { return ""; } diff --git a/lib/stitch.js b/lib/stitch.js index fa3e641..0c29109 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -70,6 +70,19 @@ return result; }; + Stitch.prototype.template = function(identifier, modules) { + var module; + return "(function(/*! Stitch !*/) {\n if (!this." + identifier + ") {\n var modules = {}, cache = {}, require = function(name, root) {\n var path = expand(root, name), indexPath = expand(path, './index'), module, fn;\n module = cache[path] || cache[indexPath]\n if (module) {\n return module.exports;\n } else if (fn = modules[path] || modules[path = indexPath]) {\n module = {id: path, exports: {}};\n try {\n cache[path] = module;\n fn(module.exports, function(name) {\n return require(name, dirname(path));\n }, module);\n return module.exports;\n } catch (err) {\n delete cache[path];\n throw err;\n }\n } else {\n throw 'module ' + name + ' not found';\n }\n }, expand = function(root, name) {\n var results = [], parts, part;\n if (/^\.\.?(\\/|$)/.test(name)) {\n parts = [root, name].join('/').split('/');\n } else {\n parts = name.split('/');\n }\n for (var i = 0, length = parts.length; i < length; i++) {\n part = parts[i];\n if (part == '..') {\n results.pop();\n } else if (part != '.' && part != '') {\n results.push(part);\n }\n }\n return results.join('/');\n }, dirname = function(path) {\n return path.split('/').slice(0, -1).join('/');\n };\n this." + identifier + " = function(name) {\n return require(name, '');\n }\n this." + identifier + ".define = function(bundle) {\n for (var key in bundle)\n modules[key] = bundle[key];\n };\n this." + identifier + ".modules = modules;\n this." + identifier + ".cache = cache;\n }\n return this." + identifier + ".define;\n}).call(this)({\n " + (((function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = modules.length; _i < _len; _i++) { + module = modules[_i]; + _results.push(JSON.stringify(module.id) + (": function(exports, require, module) {" + (module.compile()) + "}")); + } + return _results; + })()).join(', ')) + "\n});"; + }; + return Stitch; })(); diff --git a/package.json b/package.json index 43bc9e9..55aafea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.11", + "version": "0.4.12", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -39,7 +39,6 @@ "fast-detective": "~0.0.2", "http-proxy": "~0.10.3", "sty": "~0.6.1", - "eco": "1.1.0-rc-3", "uglifycss": "0.0.5", "karma-script-launcher": "~0.1.0", "karma-firefox-launcher": "~0.1.0", diff --git a/src/compilers.coffee b/src/compilers.coffee index ff2b6da..aec8fdf 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -64,8 +64,8 @@ compileCoffeescript = (_path, literate = false) -> ## Eco and Jeco Compiler ## -eco = require 'eco' compilers.eco = (_path) -> + eco = requireLocalModule('eco') content = eco.precompile fs.readFileSync _path, 'utf8' """ var content = #{content}; @@ -73,6 +73,7 @@ compilers.eco = (_path) -> """ compilers.jeco = (_path) -> + eco = requireLocalModule('eco') content = eco.precompile fs.readFileSync _path, 'utf8' """ module.exports = function(values, data){ diff --git a/src/package.coffee b/src/package.coffee index 955653d..0784aba 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -245,8 +245,7 @@ class JsPackage extends Package _stitch = new Stitch(@paths) _modules = @depend.resolve().concat(_stitch.resolve()) if _modules - _template = utils.loadAsset('stitch') - _template(identifier: @identifier, modules: _modules) + _stitch.template(@identifier, _modules) else "" diff --git a/src/stitch.coffee b/src/stitch.coffee index e29d552..68f591d 100644 --- a/src/stitch.coffee +++ b/src/stitch.coffee @@ -25,6 +25,65 @@ class Stitch result.push(module) if module.valid() result + template: (identifier, modules) -> + """ + (function(/*! Stitch !*/) { + if (!this.#{identifier}) { + var modules = {}, cache = {}, require = function(name, root) { + var path = expand(root, name), indexPath = expand(path, './index'), module, fn; + module = cache[path] || cache[indexPath] + if (module) { + return module.exports; + } else if (fn = modules[path] || modules[path = indexPath]) { + module = {id: path, exports: {}}; + try { + cache[path] = module; + fn(module.exports, function(name) { + return require(name, dirname(path)); + }, module); + return module.exports; + } catch (err) { + delete cache[path]; + throw err; + } + } else { + throw 'module ' + name + ' not found'; + } + }, expand = function(root, name) { + var results = [], parts, part; + if (/^\.\.?(\\/|$)/.test(name)) { + parts = [root, name].join('/').split('/'); + } else { + parts = name.split('/'); + } + for (var i = 0, length = parts.length; i < length; i++) { + part = parts[i]; + if (part == '..') { + results.pop(); + } else if (part != '.' && part != '') { + results.push(part); + } + } + return results.join('/'); + }, dirname = function(path) { + return path.split('/').slice(0, -1).join('/'); + }; + this.#{identifier} = function(name) { + return require(name, ''); + } + this.#{identifier}.define = function(bundle) { + for (var key in bundle) + modules[key] = bundle[key]; + }; + this.#{identifier}.modules = modules; + this.#{identifier}.cache = cache; + } + return this.#{identifier}.define; + }).call(this)({ + #{(JSON.stringify(module.id) + ": function(exports, require, module) {#{module.compile()}}" for module in modules).join(', ')} + }); + """ + class Module constructor: (@filename, @parent) -> @ext = npath.extname(@filename).slice(1) @@ -35,5 +94,6 @@ class Module valid: -> !!compilers[@ext] + module.exports = Stitch From b7a9b7a363c20c8b1130ed4dc5d3ab85dfa099f4 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 15:38:38 -0500 Subject: [PATCH 080/167] removing the stitch eco template file --- assets/stitch.eco | 56 -------------------------------------------- src/compilers.coffee | 6 +++-- 2 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 assets/stitch.eco diff --git a/assets/stitch.eco b/assets/stitch.eco deleted file mode 100644 index de825b2..0000000 --- a/assets/stitch.eco +++ /dev/null @@ -1,56 +0,0 @@ -<% @identifier or= 'require' %> -(function(/*! Stitch !*/) { - if (!this.<%= @identifier %>) { - var modules = {}, cache = {}, require = function(name, root) { - var path = expand(root, name), indexPath = expand(path, './index'), module, fn; - module = cache[path] || cache[indexPath] - if (module) { - return module.exports; - } else if (fn = modules[path] || modules[path = indexPath]) { - module = {id: path, exports: {}}; - try { - cache[path] = module; - fn(module.exports, function(name) { - return require(name, dirname(path)); - }, module); - return module.exports; - } catch (err) { - delete cache[path]; - throw err; - } - } else { - throw 'module \'' + name + '\' not found'; - } - }, expand = function(root, name) { - var results = [], parts, part; - if (/^\.\.?(\/|$)/.test(name)) { - parts = [root, name].join('/').split('/'); - } else { - parts = name.split('/'); - } - for (var i = 0, length = parts.length; i < length; i++) { - part = parts[i]; - if (part == '..') { - results.pop(); - } else if (part != '.' && part != '') { - results.push(part); - } - } - return results.join('/'); - }, dirname = function(path) { - return path.split('/').slice(0, -1).join('/'); - }; - this.<%= @identifier %> = function(name) { - return require(name, ''); - } - this.<%= @identifier %>.define = function(bundle) { - for (var key in bundle) - modules[key] = bundle[key]; - }; - this.<%= @identifier %>.modules = modules; - this.<%= @identifier %>.cache = cache; - } - return this.<%= @identifier %>.define; -}).call(this)({ - <%- (JSON.stringify(module.id) + ": function(exports, require, module) {#{module.compile()}}" for module in @modules).join(', ') %> -}); diff --git a/src/compilers.coffee b/src/compilers.coffee index aec8fdf..ffcc967 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -12,14 +12,16 @@ lmCache = {} # setup project path projectPath = path.resolve process.cwd() +# TODO: test to make sure the project path contains a node_modules folder?? + # helper fuction to perform load/caching of modules requireLocalModule = (localModule) -> - modulePath = "#{projectPath}/node_modules/#{localModule}" + modulePath = "#{projectPath}/node_modules/#{localModule}" try lmCache[localModule] or= require modulePath catch error utils.error("Unable to load #{localModule} module. Try to use 'npm install #{localModule}' in your project directory.") - console.log error if utils.VERBOSE + console.log(error) if utils.VERBOSE process.exit() ## From 411c89cc798fac8e42d59e43b1870dbd47b62441 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 22 Aug 2013 16:35:41 -0500 Subject: [PATCH 081/167] only have jade debug during server mode --- lib/compilers.js | 1 + src/compilers.coffee | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/compilers.js b/lib/compilers.js index 2632ef2..25cd780 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -103,6 +103,7 @@ try { template = jade.compile(content, { filename: _path, + compileDebug: utils.COMMAND === "server", client: true }); source = template.toString(); diff --git a/src/compilers.coffee b/src/compilers.coffee index ffcc967..99be25a 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -13,6 +13,7 @@ lmCache = {} projectPath = path.resolve process.cwd() # TODO: test to make sure the project path contains a node_modules folder?? +# TODO: provide compiler options in slug file! # helper fuction to perform load/caching of modules requireLocalModule = (localModule) -> @@ -105,7 +106,7 @@ compilers.jade = (_path) -> try template = jade.compile content, filename: _path - # compileDebug: compilers.DEBUG + compileDebug: utils.COMMAND is "server" client: true source = template.toString() "module.exports = #{source};" From 18f31aa0125e79135aa2e19a4c866be03d69730b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 26 Aug 2013 15:58:21 -0500 Subject: [PATCH 082/167] fixed issue with minifyjs not being called correctly --- lib/package.js | 4 +++- package.json | 2 +- src/package.coffee | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/package.js b/lib/package.js index b1c2138..4a961d3 100644 --- a/lib/package.js +++ b/lib/package.js @@ -318,7 +318,9 @@ try { result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); if (utils.COMPRESS) { - result = uglifyjs(result); + result = uglifyjs.minify(result, { + fromString: true + }); } return result; } catch (_error) { diff --git a/package.json b/package.json index 55aafea..049333f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.12", + "version": "0.4.13", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/package.coffee b/src/package.coffee index 0784aba..dc59938 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -228,7 +228,7 @@ class JsPackage extends Package compile: -> try result = [@compileLibs(), @compileModules(), @after].join("\n") - result = uglifyjs(result) if utils.COMPRESS + result = uglifyjs.minify(result, {fromString: true}) if utils.COMPRESS result catch ex @handleCompileError(ex) From cff1f7eaf10379307245706f774bce609b5da668 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Aug 2013 10:29:56 -0500 Subject: [PATCH 083/167] updated with better compiler errors --- README.md | 6 +++--- lib/compilers.js | 18 ++++++++++-------- src/compilers.coffee | 16 +++++++++------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9ebae53..89f8a38 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,12 @@ Other goals and features... In addition to the above ideas, I want to try to get some of the features below integrated into hem... -- [ ] Easier to setup proxy +- [*] Easier to setup proxy - [ ] livereload abilities for css and possibly js - [ ] easier testing setup and execution (karma/phantomjs) - [ ] integrate spine.app commands into hem - [ ] update examples/documention -- [ ] ability to act as middleware for connect/express apps +- [*] ability to act as middleware for connect/express apps - [ ] versioning abilities - [ ] manifest creation - [ ] really do need to write some tests for hem @@ -45,7 +45,7 @@ Will look into but not 100% sure These would be nice to have things, will have to research it more in the future... -- [ ] easier ways to add your own compilers/extensions +- [*] easier ways to add your own compilers/extensions - [ ] source mapping - [ ] possibly look at AMD vs commonjs??? - [ ] jshint/lint checks?? diff --git a/lib/compilers.js b/lib/compilers.js index 25cd780..fd0789c 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -14,14 +14,16 @@ projectPath = path.resolve(process.cwd()); - requireLocalModule = function(localModule) { - var error, modulePath; + requireLocalModule = function(localModule, _path) { + var error, modulePath, relativePath; modulePath = "" + projectPath + "/node_modules/" + localModule; try { return lmCache[localModule] || (lmCache[localModule] = require(modulePath)); } catch (_error) { error = _error; - utils.error("Unable to load " + localModule + " module. Try to use 'npm install " + localModule + "' in your project directory."); + relativePath = path.relative(projectPath, _path); + utils.error("Unable to load " + localModule + " module to compile " + relativePath + ""); + utils.error("Try to use 'npm install " + localModule + "' in your project directory."); if (utils.VERBOSE) { console.log(error); } @@ -82,14 +84,14 @@ compilers.eco = function(_path) { var content, eco; - eco = requireLocalModule('eco'); + eco = requireLocalModule('eco', _path); content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "var content = " + content + ";\nmodule.exports = content;"; }; compilers.jeco = function(_path) { var content, eco; - eco = requireLocalModule('eco'); + eco = requireLocalModule('eco', _path); content = eco.precompile(fs.readFileSync(_path, 'utf8')); return "module.exports = function(values, data){ \n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; }; @@ -98,7 +100,7 @@ compilers.jade = function(_path) { var content, ex, jade, source, template; - jade = requireLocalModule('jade'); + jade = requireLocalModule('jade', _path); content = fs.readFileSync(_path, 'utf8'); try { template = jade.compile(content, { @@ -120,7 +122,7 @@ compilers.stylus = function(_path) { var content, result, stylus; - stylus = requireLocalModule('stylus'); + stylus = requireLocalModule('stylus', _path); content = fs.readFileSync(_path, 'utf8'); result = ''; stylus(content).include(path.dirname(_path)).render(function(err, css) { @@ -140,7 +142,7 @@ compilers.less = function(_path) { var content, less, result; - less = requireLocalModule('less'); + less = requireLocalModule('less', _path); content = fs.readFileSync(_path, 'utf8'); result = ''; less.render(content, function(err, css) { diff --git a/src/compilers.coffee b/src/compilers.coffee index 99be25a..4b756c8 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -16,12 +16,14 @@ projectPath = path.resolve process.cwd() # TODO: provide compiler options in slug file! # helper fuction to perform load/caching of modules -requireLocalModule = (localModule) -> +requireLocalModule = (localModule, _path) -> modulePath = "#{projectPath}/node_modules/#{localModule}" try lmCache[localModule] or= require modulePath catch error - utils.error("Unable to load #{localModule} module. Try to use 'npm install #{localModule}' in your project directory.") + relativePath = path.relative(projectPath, _path) + utils.error("Unable to load #{localModule} module to compile #{relativePath}") + utils.error("Try to use 'npm install #{localModule}' in your project directory.") console.log(error) if utils.VERBOSE process.exit() @@ -68,7 +70,7 @@ compileCoffeescript = (_path, literate = false) -> ## compilers.eco = (_path) -> - eco = requireLocalModule('eco') + eco = requireLocalModule('eco', _path) content = eco.precompile fs.readFileSync _path, 'utf8' """ var content = #{content}; @@ -76,7 +78,7 @@ compilers.eco = (_path) -> """ compilers.jeco = (_path) -> - eco = requireLocalModule('eco') + eco = requireLocalModule('eco', _path) content = eco.precompile fs.readFileSync _path, 'utf8' """ module.exports = function(values, data){ @@ -101,7 +103,7 @@ require.extensions['.jeco'] = require.extensions['.eco'] ## compilers.jade = (_path) -> - jade = requireLocalModule('jade') + jade = requireLocalModule('jade', _path) content = fs.readFileSync(_path, 'utf8') try template = jade.compile content, @@ -121,7 +123,7 @@ require.extensions['.jade'] = (module, filename) -> ## compilers.stylus = (_path) -> - stylus = requireLocalModule('stylus') + stylus = requireLocalModule('stylus', _path) content = fs.readFileSync(_path, 'utf8') result = '' stylus(content) @@ -141,7 +143,7 @@ require.extensions['.styl'] = (module, filename) -> ## compilers.less = (_path) -> - less = requireLocalModule('less') + less = requireLocalModule('less', _path) content = fs.readFileSync(_path, 'utf8') result = '' less.render content, (err, css) -> From 4da00e72d0faa69afe6017747e53876b17df5a34 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Aug 2013 10:30:22 -0500 Subject: [PATCH 084/167] fixed issue with uglify, wasn't getting the minified string correctly. --- lib/package.js | 2 +- package.json | 2 +- src/package.coffee | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/package.js b/lib/package.js index 4a961d3..99004b7 100644 --- a/lib/package.js +++ b/lib/package.js @@ -320,7 +320,7 @@ if (utils.COMPRESS) { result = uglifyjs.minify(result, { fromString: true - }); + }).code; } return result; } catch (_error) { diff --git a/package.json b/package.json index 049333f..fe7fa51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.13", + "version": "0.4.14", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/package.coffee b/src/package.coffee index dc59938..be3705f 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -228,7 +228,7 @@ class JsPackage extends Package compile: -> try result = [@compileLibs(), @compileModules(), @after].join("\n") - result = uglifyjs.minify(result, {fromString: true}) if utils.COMPRESS + result = uglifyjs.minify(result, {fromString: true}).code if utils.COMPRESS result catch ex @handleCompileError(ex) From e232284e5df9d545c7794fe3ef8c2c93e93be44a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 25 Sep 2013 18:55:36 -0500 Subject: [PATCH 085/167] better compile error handling for eco/jeco templates --- lib/compilers.js | 24 ++++++++++++++++++++---- src/compilers.coffee | 16 ++++++++++++++-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index fd0789c..afb40be 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -83,16 +83,32 @@ }; compilers.eco = function(_path) { - var content, eco; + var content, eco, err; eco = requireLocalModule('eco', _path); - content = eco.precompile(fs.readFileSync(_path, 'utf8')); + try { + content = eco.precompile(fs.readFileSync(_path, 'utf8')); + } catch (_error) { + err = _error; + err = new Error(err); + err.message = "eco Error: " + err.message; + err.path = "eco Path: " + _path; + throw err; + } return "var content = " + content + ";\nmodule.exports = content;"; }; compilers.jeco = function(_path) { - var content, eco; + var content, eco, err; eco = requireLocalModule('eco', _path); - content = eco.precompile(fs.readFileSync(_path, 'utf8')); + try { + content = eco.precompile(fs.readFileSync(_path, 'utf8')); + } catch (_error) { + err = _error; + err = new Error(err); + err.message = "jeco Error: " + err.message; + err.path = "jeco Path: " + _path; + throw err; + } return "module.exports = function(values, data){ \n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; }; diff --git a/src/compilers.coffee b/src/compilers.coffee index 4b756c8..19e0c4c 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -71,7 +71,13 @@ compileCoffeescript = (_path, literate = false) -> compilers.eco = (_path) -> eco = requireLocalModule('eco', _path) - content = eco.precompile fs.readFileSync _path, 'utf8' + try + content = eco.precompile fs.readFileSync _path, 'utf8' + catch err + err = new Error(err) + err.message = "eco Error: " + err.message + err.path = "eco Path: " + _path + throw err """ var content = #{content}; module.exports = content; @@ -79,7 +85,13 @@ compilers.eco = (_path) -> compilers.jeco = (_path) -> eco = requireLocalModule('eco', _path) - content = eco.precompile fs.readFileSync _path, 'utf8' + try + content = eco.precompile fs.readFileSync _path, 'utf8' + catch err + err = new Error(err) + err.message = "jeco Error: " + err.message + err.path = "jeco Path: " + _path + throw err """ module.exports = function(values, data){ var $ = jQuery, result = $(); From 49556a76d054464e986a9aaadb75746f915565f1 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:13:14 -0500 Subject: [PATCH 086/167] have hem look for local copy and then global. --- bin/hem | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bin/hem b/bin/hem index f70a328..4567860 100755 --- a/bin/hem +++ b/bin/hem @@ -1,9 +1,15 @@ #!/usr/bin/env node -var Module = require('module'), - fs = require('fs'); - -if (fs.existsSync('./slug.js')) { - Module._load('./slug.js'); + +var path = require('path'); +var fs = require('fs'); + +// Try to find a local install +var hem = path.resolve(process.cwd(), 'node_modules', 'hem', 'lib', 'hem'); + +// Check if the local install exists else we use the install we are in +if (!fs.existsSync(hem)) { + hem = path.join('..', 'lib', 'hem'); } -require('../lib/hem').exec(); +// execute hem +require(hem).exec(); From 1afb0502d08b2e6e5fc76d4d38a59bc5ee710f80 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:13:35 -0500 Subject: [PATCH 087/167] move logging to its own module --- src/log.coffee | 21 +++++++++++++++++++++ src/utils.coffee | 21 --------------------- 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 src/log.coffee diff --git a/src/log.coffee b/src/log.coffee new file mode 100644 index 0000000..943598e --- /dev/null +++ b/src/log.coffee @@ -0,0 +1,21 @@ +sty = require('sty') + +# ------ Logging Helpers + +log = (message, parse = true) -> + console.log(parse and sty.parse(message) or message) + +log.info = (message, parse = true) -> + console.log(parse and sty.parse(message) or message) if @VERBOSE + +log.error = (message, parse = true) -> + console.log "#{sty.red 'ERROR:'} #{parse and sty.parse(message) or message}" + +log.errorAndExit = (error, parse = true) -> + utils.error(error, parse) + process.exit(1) + +log.parse = (message) -> + sty.parse(message) + +module.exports = log diff --git a/src/utils.coffee b/src/utils.coffee index 58be241..789bf0c 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,4 +1,3 @@ -sty = require('sty') path = require('path') fs = require('fs') utils = {} @@ -86,24 +85,4 @@ utils.cleanPath = (paths...) -> utils.cleanRoute = (routes...) -> clean(routes, "/") - - -# ------ Logging Helpers - -utils.log = (message) -> - console.log sty.parse(message) - -utils.info = (message) -> - console.log sty.parse(message) if @VERBOSE - -utils.error = (message, parse = true) -> - console.log "#{sty.red 'ERROR:'} #{parse and sty.parse(message) or message}" - -utils.errorAndExit = (error) -> - utils.error(error) - process.exit(1) - -utils.parse = (message) -> - sty.parse(message) - module.exports = utils From 1729bbff5e786b86796e14c79906c1e46e85e493 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:13:55 -0500 Subject: [PATCH 088/167] using logging module --- src/compilers.coffee | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/compilers.coffee b/src/compilers.coffee index 19e0c4c..fe54ec4 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -1,10 +1,11 @@ fs = require('fs') path = require('path') -utils = require('./utils') +log = require('./log') +hem = require('./hem') compilers = {} lmCache = {} -# Load the modules from the project directory (instead of from the hem +# Load the modules from the project directory (instead of from the hem # node_modules). This allows a lot of the different javascript/css pre # compilers to be installed in the project vs having to be included with # the hem package. @@ -18,17 +19,17 @@ projectPath = path.resolve process.cwd() # helper fuction to perform load/caching of modules requireLocalModule = (localModule, _path) -> modulePath = "#{projectPath}/node_modules/#{localModule}" - try + try lmCache[localModule] or= require modulePath catch error relativePath = path.relative(projectPath, _path) - utils.error("Unable to load #{localModule} module to compile #{relativePath}") - utils.error("Try to use 'npm install #{localModule}' in your project directory.") - console.log(error) if utils.VERBOSE + log.error("Unable to load #{localModule} module to compile #{relativePath}") + log.error("Try to use 'npm install #{localModule}' in your project directory.") + log.error(error, false) if log.VERBOSE process.exit() ## -## Basic javascript/css files +## Basic javascript/css files ## compilers.js = compilers.css = (_path) -> @@ -83,7 +84,7 @@ compilers.eco = (_path) -> module.exports = content; """ -compilers.jeco = (_path) -> +compilers.jeco = (_path) -> eco = requireLocalModule('eco', _path) try content = eco.precompile fs.readFileSync _path, 'utf8' @@ -93,7 +94,7 @@ compilers.jeco = (_path) -> err.path = "jeco Path: " + _path throw err """ - module.exports = function(values, data){ + module.exports = function(values, data){ var $ = jQuery, result = $(); values = $.makeArray(values); data = data || {}; @@ -120,7 +121,7 @@ compilers.jade = (_path) -> try template = jade.compile content, filename: _path - compileDebug: utils.COMMAND is "server" + compileDebug: hem.argv.command is "server" client: true source = template.toString() "module.exports = #{source};" @@ -140,13 +141,13 @@ compilers.stylus = (_path) -> result = '' stylus(content) .include(path.dirname(_path)) - .render((err, css) -> + .render((err, css) -> throw err if err result = css ) result -require.extensions['.styl'] = (module, filename) -> +require.extensions['.styl'] = (module, filename) -> source = JSON.stringify(compilers.stylus(filename)) module._compile "module.exports = #{source}", filename @@ -163,13 +164,13 @@ compilers.less = (_path) -> result = css result -require.extensions['.less'] = (module, filename) -> +require.extensions['.less'] = (module, filename) -> source = JSON.stringify(compilers.less(filename)) module._compile "module.exports = #{source}", filename ## ## Environment Compiler -## +## # This creates a javascript module based off key values found in environment # variables or in the package.json file. Usefule for inserting build info From 2867d27e9d22a9cc2b8a144a4db4ce4c0e624b63 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:14:25 -0500 Subject: [PATCH 089/167] updated package dependencies to latest versions --- package.json | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index fe7fa51..2f7704a 100644 --- a/package.json +++ b/package.json @@ -37,23 +37,15 @@ "preferGlobal": true, "dependencies": { "fast-detective": "~0.0.2", - "http-proxy": "~0.10.3", - "sty": "~0.6.1", "uglifycss": "0.0.5", - "karma-script-launcher": "~0.1.0", - "karma-firefox-launcher": "~0.1.0", - "karma-chrome-launcher": "~0.1.0", - "karma-html2js-preprocessor": "~0.1.0", - "karma-coffee-preprocessor": "~0.1.0", - "karma-jasmine": "~0.1.0", - "karma-requirejs": "~0.1.0", - "karma-phantomjs-launcher": "~0.1.0", - "karma": "~0.10.1", "mkdirp": "~0.3.5", - "watch": "~0.8.0", - "connect": "~2.8.5", - "optimist": "~0.6.0", "coffee-script": "~1.6.3", - "uglify-js": "~2.3.6" + "connect": "~2.9.0", + "http-proxy": "~0.10.3", + "uglify-js": "~2.4.0", + "karma": "~0.10.2", + "sty": "~0.6.1", + "optimist": "~0.6.0", + "watch": "~0.8.0" } } From 521b275645f5ab83efad6edce48f9654a09501fe Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:15:09 -0500 Subject: [PATCH 090/167] learn to spall, I mean spell --- src/hem.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hem.coffee b/src/hem.coffee index 5a03466..dd2e030 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -20,7 +20,7 @@ argv = optimist.usage([ ' check :check slug file values' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') -.alias('c', 'compress').describe('c',':all complications are compressed/minified') +.alias('c', 'compress').describe('c',':all compilations are compressed/minified') .alias('w', 'watch').describe('w',':watch files when running tests') .alias('s', 'slug').describe('s',':run hem using a specified slug file') .alias('n', 'nocolors').describe('n',':disable color in console output') From 1435c18b7a1e2ce83cc9a692ca999e5c1dcb5c28 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:15:39 -0500 Subject: [PATCH 091/167] using logs module --- src/server.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/server.coffee b/src/server.coffee index b7ed024..9e5af31 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -3,11 +3,13 @@ mime = require('connect').static.mime http = require('http') fs = require('fs') utils = require('./utils') +log = require('./log') httpProxy = require('http-proxy') server = {} # ------- Public Functions +# TODO: pass in hem class instead??? server.start = (applications, options) -> app = connect() app.use(server.middleware(applications, options)) @@ -16,9 +18,9 @@ server.start = (applications, options) -> server.middleware = (applications, options) -> # determine if there is any dynamic or static routes to add for hemapp in applications - utils.info "> Apply route mappings for application: #{hemapp.name}" + log.info "> Apply route mappings for application: #{hemapp.name}" for pkg in hemapp.packages - utils.info "- Mapping route #{pkg.route} to #{pkg.target}" + log.info "- Mapping route #{pkg.route} to #{pkg.target}" if hemapp.static options.routes = utils.extend(hemapp.static, options.routes) @@ -28,11 +30,11 @@ server.middleware = (applications, options) -> if fs.existsSync(value) # test if file is directory or file.... if fs.lstatSync(value).isDirectory() - utils.info "- Mapping static #{route} to dir #{value}" + log.info "- Mapping static #{route} to dir #{value}" statics.use(route, checkForRedirect()) statics.use(route, connect.static(value) ) else - utils.info "- Mapping static #{route} to resource #{value}" + log.info "- Mapping static #{route} to resource #{value}" statics.use route, do (value) -> (req, res) -> fs.readFile value, (err, data) -> @@ -42,14 +44,13 @@ server.middleware = (applications, options) -> return res.writeHead(200) res.end(data) - else - utils.errorAndExit "The folder #{value} does not exist for static mapping #{route}" + log.errorAndExit "The folder #{value} does not exist for static mapping #{route}" # setup proxy route for route, value of options.proxy display = "#{value.host}:#{value.port or 80}#{value.path}" - utils.info "- Proxy requests #{route} to #{display}" + log.info "- Proxy requests #{route} to #{display}" statics.use(route, createRoutingProxy(value)) # return the custom middleware for connect to use From 75be79d3e16f7e525b1337bd7db251545ddc640a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Oct 2013 19:15:51 -0500 Subject: [PATCH 092/167] using log module --- src/package.coffee | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/package.coffee b/src/package.coffee index be3705f..37fc56f 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -5,6 +5,8 @@ uglifycss = require('uglifycss') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') +log = require('./log') +hem = require('./hem') versioning = require('./versioning') # ------- Parent Classes @@ -22,7 +24,7 @@ class Application # make sure we don't modify the original assets (which is cached by require) defaults = utils.extend({}, loadedDefaults) catch err - utils.error "ERROR: Invalid 'defaults' value provided: " + config.defaults + log.error "ERROR: Invalid 'defaults' value provided: " + config.defaults process.exit 1 config = utils.extend(defaults, config) @@ -36,6 +38,8 @@ class Application else @root = "" + # TODO: do we need to set the resolve the root to a full path? + # make sure route has a value @route or= @applyBaseRoute("/") @route = @applyBaseRoute(config.hem?.baseAppRoute, @route) @@ -70,7 +74,7 @@ class Application if config.version verType = versioning[config.version.type] unless verType - utils.errorAndExit "Incorrect type value for version configuration: (#{config.version.type})" + log.errorAndExit "Incorrect type value for version configuration: (#{config.version.type})" @versioning = new verType(@, config.version) isMatchingRoute: (route) -> @@ -84,29 +88,29 @@ class Application return unlink: -> - utils.log("Removing application targets: #{@name}") + log("Removing application targets: #{@name}") pkg.unlink() for key, pkg of @packages build: -> - utils.log("Building application targets: #{@name}") + log("Building application targets: #{@name}") pkg.build() for key, pkg of @packages watch: -> - utils.log("Watching application: #{@name}") + log("Watching application: #{@name}") dirs = (pkg.watch() for key, pkg of @packages) # make sure dirs has valid values if dirs.length - utils.info("- Watching directories: #{dirs}") + log.info("- Watching directories: #{dirs}") else - utils.info("- No directories to watch...") + log.info("- No directories to watch...") version: -> - utils.log("Versioning application: #{@name}") + log("Versioning application: #{@name}") if @versioning @versioning.update() else - utils.errorAndExit "ERROR: Versioning not enabled in slug.json" + log.errorAndExit "ERROR: Versioning not enabled in slug.json" applyRootDir: (value) -> values = utils.toArray(value) @@ -156,16 +160,16 @@ class Package @route = @parent.applyBaseRoute(route, targetUrl) # make sure we have a route to use when using server command - if utils.COMMAND is "server" - utils.errorAndExit("Unable to determine route for #{@target}") unless @route + if hem.argv.command is "server" + log.errorAndExit("Unable to determine route for #{@target}") unless @route handleCompileError: (ex) -> # TODO: construct better error message, one that works for all precompilers, # having some problems with sty here, hmmm.... - utils.error(ex.message) - utils.error(ex.path) if ex.path + log.error(ex.message) + log.error(ex.path) if ex.path # only return when in server/watch mode, otherwise exit - switch utils.COMMAND + switch hem.argv.command when "server" or "watch" # TODO: only return this for javascript return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" @@ -174,12 +178,12 @@ class Package unlink: -> if fs.existsSync(@target) - utils.info "- removing #{@target}" + log.info "- removing #{@target}" fs.unlinkSync(@target) build: (write = true) -> - extra = (utils.COMPRESS and " --using compression") or "" - utils.log("- Building target: #{@target}#{extra}") + extra = (hem.argv.compress and " --using compression") or "" + log("- Building target: #{@target}#{extra}") source = @compile() if source and write dirname = path.dirname(@target) @@ -228,7 +232,7 @@ class JsPackage extends Package compile: -> try result = [@compileLibs(), @compileModules(), @after].join("\n") - result = uglifyjs.minify(result, {fromString: true}).code if utils.COMPRESS + result = uglifyjs.minify(result, {fromString: true}).code if hem.argv.compress result catch ex @handleCompileError(ex) @@ -314,7 +318,7 @@ class CssPackage extends Package # join and minify result = output.join("\n") - result = uglifycss.processString(result) if utils.COMPRESS + result = uglifycss.processString(result) if hem.argv.compress result catch ex @handleCompileError(ex) From 4eabb4848c82a9387a2467dff8765d636e723d6a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 4 Oct 2013 19:52:26 -0500 Subject: [PATCH 093/167] lots of refactoring going on --- src/compilers.coffee | 4 +- src/hem.coffee | 178 ++++++++++++++++++++++--------------------- src/package.coffee | 86 +++++++++++---------- src/server.coffee | 57 ++++++++------ 4 files changed, 172 insertions(+), 153 deletions(-) diff --git a/src/compilers.coffee b/src/compilers.coffee index fe54ec4..5dbde5b 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -1,7 +1,7 @@ fs = require('fs') path = require('path') log = require('./log') -hem = require('./hem') +argv = require('./utils').ARGV compilers = {} lmCache = {} @@ -121,7 +121,7 @@ compilers.jade = (_path) -> try template = jade.compile content, filename: _path - compileDebug: hem.argv.command is "server" + compileDebug: argv.command is "server" client: true source = template.toString() "module.exports = #{source};" diff --git a/src/hem.coffee b/src/hem.coffee index dd2e030..ba236f4 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,15 +1,6 @@ -path = require('path') -optimist = require('optimist') -fs = require('fs') -utils = require('./utils') -compilers = require('./compilers') -server = require('./server') -application = require('./package') -testing = require('./test') - # ------- Commandline arguments -argv = optimist.usage([ +argv = require('optimist').usage([ 'usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', @@ -24,7 +15,7 @@ argv = optimist.usage([ .alias('w', 'watch').describe('w',':watch files when running tests') .alias('s', 'slug').describe('s',':run hem using a specified slug file') .alias('n', 'nocolors').describe('n',':disable color in console output') -.describe('v',':make hem more talkative(verbose)') +.alias('v', 'verbose').describe('v',':make hem more talkative(verbose)') .argv # set command and targets properties @@ -34,18 +25,27 @@ argv.targets = argv._[1..] # disable colors require("sty").disable() if !!argv.nocolors -# expose argv -utils.ARGV = argv +# turn on/off verbose logging +require("./log").VERBOSE = argv.v = !!argv.v -# always have a value for these argv options -utils.COMPRESS = argv.compress = !!argv.compress -utils.VERBOSE = argv.v = !!argv.v -utils.COMMAND = argv.command +# save argv to utils class to allow access by other modules +require("./utils").ARGV = argv + +# ------- perform requires + +path = require('path') +fs = require('fs') +compilers = require('./compilers') +server = require('./server') +testing = require('./test') +application = require('./package') +log = require('./log') +utils = require('./utils') # ------- Global Functions help = -> - utils.log "HEM Version: " + require('../package.json')?.version + "\n" + log "HEM Version: " + require('../package.json')?.version + "\n" optimist.showHelp() process.exit() @@ -56,97 +56,87 @@ class Hem @exec: (command, options) -> (new @(options)).exec(command) - @include: (props) -> - @::[key] = value for key, value of props - - @middleware: (slugFile) -> - hem = new Hem(slugFile) - server.middleware(hem.apps, hem.options.server) - - # ------- instance variables - - compilers: compilers + @middleware: (slug) -> + hem = new Hem(slug) + server.middleware(hem) - # the slug directory - homeDir: '' + @compilers: compilers # default values for server - options: + @defaults: hem: port: 9294 + host: "localhost" + routes: {} - # emtpy applications list - apps: [] + # ------- instance variables - # ------- Constructor + # emtpy options map and applications list + options : {} + apps : [] + home : process.cwd() - constructor: (options = {}) -> - # handle slug file - if options is "string" - slug = options - else - slug = argv.slug or './slug.json' - @options = utils.extend(options, @options) if options - - # quick check to make sure slug file exists - if fs.existsSync(slug) - options = @readSlug(slug) - options.hem?.port or= @options.hem.port - options.hem?.host or= @options.hem.host - @options = options - # make sure we are in same directory as slug - @homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)) - process.chdir(@homeDir) - else - utils.errorAndExit "Unable to find #{slug} file in current directory" + # ------- Constructor - # allow overrides and set defaults + constructor: (options) -> + # handle slug file options + switch typeof options + when "string" + slug = options + when "object" + @options = options + else + slug or= argv.slug or 'slug' + + # if given a slug file, attempt to load + @options = @readSlug(slug) if slug + + # make sure some defaults are present + @options.hem or= {} + @options.hem.port or= Hem.defaults.hem.port + @options.hem.host or= Hem.defaults.hem.host + @options.hem.routes or= Hem.defaults.hem.routes + + # allow overrides from command line @options.hem.port = argv.port if argv.port - @options.hem.host or= "" - @options.hem.routes or= {} # setup applications from options/slug for name, config of @options continue if name is "hem" - config.hem = @options.hem @apps.push application.createApplication(name, config) # ------- Command Functions server: -> value = "http://#{@options.hem.host or "*"}:#{@options.hem.port}" - utils.log "Starting Server at #{value}" - server.start(@apps, @options.hem) + log "Starting Server at #{value}" + server.start(@) clean: -> - targets = argv.targets - cleanAll = targets.length is 0 - app.unlink() for app in @apps when app.name in targets or cleanAll + app.unlink() for app in @apps build: -> @clean() - @buildTargets(argv.targets) + @compile() version: -> - @versionTargets(argv.targets) + app.version() for app in @apps watch: -> - targets = argv.targets - @buildTargets(targets) - watchAll = targets.length is 0 - app.watch() for app in @apps when app.name in targets or watchAll + @compile() + app.watch() for app in @apps test: -> targets = argv.targets # set test options - testOptions = + testOptions = basePath: @homeDir # check for watch mode if argv.watch @watch() testOptions.singleRun = false else - @buildTargets(targets) + @compile() testOptions.singleRun = true # run tests @testTargets(targets, testOptions) @@ -155,40 +145,56 @@ class Hem printOptions = showHidden: false, colors: !argv.nocolors, depth: null inspect = require('util').inspect # print hem configuration - utils.log "> Configuration for hem:" + log "> Configuration for hem:" console.log(inspect(@options.hem, printOptions)) - utils.log "" + log "" # print app configurations - targets = argv.targets - targetAll = targets.length is 0 - for app in @apps when app.name in targets or targetAll - utils.log "> Configuration values for #{app.name}:" + for app in @apps + log "> Configuration values for #{app.name}:" console.log(inspect(app, printOptions)) - utils.log "" + log "" exec: (command = argv.command) -> return help() unless @[command] + # reset the apps list based on command line args + @apps = @getTargetApps() + # hope this works :o) @[command]() # ------- Private Functions readSlug: (slug) -> - return {} unless slug and fs.existsSync(slug) - JSON.parse(fs.readFileSync(slug, 'utf-8')) - - getTargetApps: (targets = []) -> + # first make sure slug file exists + slugPath = path.resolve(slug) + try + slugPath = require.resolve(slugPath) + catch error + log.errorAndExit("Couldn't find slug file #{slugPath}. #{error}") + + # set home directory to slug directory + Hem.home = path.dirname(slugPath) + + # next try to require + try + delete require.cache[slugPath] + slug = require(slugPath) + slug.customize?(@) # allow any customizations to hem before running + slug + catch error + log.errorAndExit("Couldn't load slug file #{slugPath}. #{error}") + + getTargetApps: (targets = argv.targets) -> targetAll = targets.length is 0 (app for app in @apps when app.name in targets or targetAll) + compile: () -> + app.build() for app in @apps() + testTargets: (targets = [], options = {}) -> - testApps = (app for app in @getTargetApps(targets) when app.test) + testApps = (app for app in @apps when app.test) testing.run(testApps, options) - buildTargets: (targets = []) -> - app.build() for app in @getTargetApps(targets) - versionTargets: (targets = []) -> - app.version() for app in @getTargetApps(targets) module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index 37fc56f..432c2be 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -5,11 +5,11 @@ uglifycss = require('uglifycss') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') +argv = require('./utils').ARGV log = require('./log') -hem = require('./hem') versioning = require('./versioning') -# ------- Parent Classes +# ------- Application Class class Application constructor: (name, config = {}) -> @@ -20,37 +20,35 @@ class Application # apply defaults if (config.defaults) try - loadedDefaults = utils.loadAsset('defaults/' + config.defaults) # make sure we don't modify the original assets (which is cached by require) + loadedDefaults = utils.loadAsset('defaults/' + config.defaults) defaults = utils.extend({}, loadedDefaults) catch err log.error "ERROR: Invalid 'defaults' value provided: " + config.defaults process.exit 1 + # create updated config mapping by merging with default values config = utils.extend(defaults, config) # set root variable unless @root - # if name is also directory assume that is root + # if application name is also a directory then assume that is root if utils.isDirectory(@name) @root = @name - @route or= @applyBaseRoute("/#{@name}") + @route or= "/#{@name}" # otherwise just work from top level directory else - @root = "" - - # TODO: do we need to set the resolve the root to a full path? + @root = "/" + @route or= "/" # make sure route has a value - @route or= @applyBaseRoute("/") - @route = @applyBaseRoute(config.hem?.baseAppRoute, @route) @static = {} @packages = {} - # configure static routes + # configure static routes with base root and route values for route, value of config.static - @static[@applyBaseRoute(@route, route)] = @applyRootDir(value)[0] + @static[@applyBaseRoute(route)] = @applyRootDir(value)[0] - # configure js/css/test packages + # configure js/css packages for key, value of config packager = undefined # determine package type @@ -113,6 +111,8 @@ class Application log.errorAndExit "ERROR: Versioning not enabled in slug.json" applyRootDir: (value) -> + # TODO: eventually use the Hem.home directory value if the home + # TODO: value is different from the process.cwd() value?! values = utils.toArray(value) values = values.map (value) => if utils.startsWith(value, "." + path.sep) @@ -122,46 +122,48 @@ class Application values applyBaseRoute: (values...) -> + values.unshift(@route) if @route utils.cleanRoute.apply(utils, values) +# ------- Package Classes + class Package - constructor: (parent, config) -> - @parent = parent + constructor: (app, config) -> + @app = app @name = config.name - @paths = @parent.applyRootDir(config.paths or "") - @target = @parent.applyRootDir(config.target or "")[0] + @src = @app.applyRootDir(config.src or "") + @target = @app.applyRootDir(config.target or "")[0] # determine target filename if utils.isDirectory(@target) # determine actual file name if @name is @ext - targetFile = parent.name + targetFile = @app.name else targetFile = @name @target = utils.cleanPath(@target, targetFile) - # make sure correct extension is present unless utils.endsWith(@target, ".#{@ext}") @target = "#{@target}.#{@ext}" - # determine url + # determine url from configuration if config.route if utils.startsWith(@target,"/") @route = config.route else - @route = @parent.applyBaseRoute(parent.route, config.route) + @route = @app.applyBaseRoute(config.route) + # use the static urls to determine the package @route else - # use the static urls to determine the package @route - for route, value of @parent.static when not @route + for route, value of @app.static when not @route if utils.startsWith(@target, value) regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}(\\\\|\/)?") targetUrl = @target.replace(regexp,"") - @route = @parent.applyBaseRoute(route, targetUrl) + @route = utils.cleanRoute(route, targetUrl) # make sure we have a route to use when using server command - if hem.argv.command is "server" - log.errorAndExit("Unable to determine route for #{@target}") unless @route + if argv.command is "server" and not @route + log.errorAndExit("Unable to determine route for #{@target}") handleCompileError: (ex) -> # TODO: construct better error message, one that works for all precompilers, @@ -169,7 +171,7 @@ class Package log.error(ex.message) log.error(ex.path) if ex.path # only return when in server/watch mode, otherwise exit - switch hem.argv.command + switch argv.command when "server" or "watch" # TODO: only return this for javascript return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" @@ -182,7 +184,7 @@ class Package fs.unlinkSync(@target) build: (write = true) -> - extra = (hem.argv.compress and " --using compression") or "" + extra = (argv.compress and " --using compression") or "" log("- Building target: #{@target}#{extra}") source = @compile() if source and write @@ -205,13 +207,13 @@ class Package dirs = utils.removeDuplicateValues(dirs) # start watch process for dir in dirs - require('watch').watchTree dir, watchOptions, (file, curr, prev) => + require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() dirs getWatchedDirs: -> - return @paths + return @src ext: "" @@ -219,20 +221,20 @@ class Package class JsPackage extends Package - constructor: (parent, config) -> + constructor: (app, config) -> # call parent - super(parent, config) + super(app, config) # javascript only configurations @identifier = config.identifier or 'require' - @libs = @parent.applyRootDir(config.libs or []) + @libs = @app.applyRootDir(config.libs or []) @after = utils.arrayToString(config.after or "") @modules = utils.toArray(config.modules or []) compile: -> try result = [@compileLibs(), @compileModules(), @after].join("\n") - result = uglifyjs.minify(result, {fromString: true}).code if hem.argv.compress + result = uglifyjs.minify(result, {fromString: true}).code if argv.compress result catch ex @handleCompileError(ex) @@ -246,7 +248,7 @@ class JsPackage extends Package # files first... @depend or= new Dependency(@modules) - _stitch = new Stitch(@paths) + _stitch = new Stitch(@src) _modules = @depend.resolve().concat(_stitch.resolve()) if _modules _stitch.template(@identifier, _modules) @@ -278,14 +280,14 @@ class JsPackage extends Package results.join("\n") getWatchedDirs: -> - @paths.concat @libs + @src.concat @libs ext: "js" class TestPackage extends JsPackage - constructor: (parent, config) -> - super(parent, config) + constructor: (app, config) -> + super(app, config) # TODO: use after in default spine json to setup specs... # TODO: testLibs = ['jasmine'] or ['test/public/lib'] @depends = utils.toArray(config.depends) @@ -293,8 +295,8 @@ class TestPackage extends JsPackage class CssPackage extends Package - constructor: (parent, config) -> - super(parent, config) + constructor: (app, config) -> + super(app, config) compile: () -> try @@ -307,7 +309,7 @@ class CssPackage extends Package require(filepath) # loop over path values - for fileOrDir in @paths + for fileOrDir in @src # if directory loop over all top level files only if utils.isDirectory(fileOrDir) for file in fs.readdirSync(fileOrDir) when require.extensions[path.extname(file)] @@ -318,7 +320,7 @@ class CssPackage extends Package # join and minify result = output.join("\n") - result = uglifycss.processString(result) if hem.argv.compress + result = uglifycss.processString(result) if argv.compress result catch ex @handleCompileError(ex) diff --git a/src/server.coffee b/src/server.coffee index 9e5af31..664588f 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -1,40 +1,51 @@ connect = require('connect') mime = require('connect').static.mime +httpProxy = require('http-proxy') http = require('http') fs = require('fs') utils = require('./utils') log = require('./log') -httpProxy = require('http-proxy') -server = {} # ------- Public Functions -# TODO: pass in hem class instead??? -server.start = (applications, options) -> - app = connect() - app.use(server.middleware(applications, options)) - http.createServer(app).listen(options.port, options.host) +server = {} + +server.start = (hem) -> + # create app to configure + app = connect() + app.use(server.middleware(hem)) + # start server + options = hem.options.hem + http.createServer(app).listen(options.port, options.host) + +server.middleware = (hem) -> + statics = connect() + options = hem.options.hem -server.middleware = (applications, options) -> # determine if there is any dynamic or static routes to add - for hemapp in applications - log.info "> Apply route mappings for application: #{hemapp.name}" - for pkg in hemapp.packages - log.info "- Mapping route #{pkg.route} to #{pkg.target}" - if hemapp.static - options.routes = utils.extend(hemapp.static, options.routes) + for app in hem.apps + + # if verbose then print our mappings and apply the baseAppRoute if present + log.info "> Apply route mappings for application: #{app.name}" + for name, pkg of app.packages + pkg.route = utils.cleanRoute(options.baseAppRoute, pkg.route) if options.baseAppRoute + log.info " - Mapping route #{pkg.route} to #{pkg.target}" + for route, value of app.static + route = utils.cleanRoute(options.baseAppRoute, route) if options.baseAppRoute + log.info " - Mapping static #{route} to #{value}" + + # add static routes to main options.route collection + if app.static + options.routes = utils.extend(app.static, options.routes) # setup separate connect app for static routes and proxy middleware - statics = connect() for route, value of options.routes if fs.existsSync(value) # test if file is directory or file.... if fs.lstatSync(value).isDirectory() - log.info "- Mapping static #{route} to dir #{value}" statics.use(route, checkForRedirect()) statics.use(route, connect.static(value) ) else - log.info "- Mapping static #{route} to resource #{value}" statics.use route, do (value) -> (req, res) -> fs.readFile value, (err, data) -> @@ -50,7 +61,7 @@ server.middleware = (applications, options) -> # setup proxy route for route, value of options.proxy display = "#{value.host}:#{value.port or 80}#{value.path}" - log.info "- Proxy requests #{route} to #{display}" + log.info "> Proxy requests #{route} to #{display}" statics.use(route, createRoutingProxy(value)) # return the custom middleware for connect to use @@ -58,10 +69,10 @@ server.middleware = (applications, options) -> # get url path url = require("url").parse(req.url)?.pathname.toLowerCase() or "" - # loop over hem applications and call compile when there is a match + # loop over applications and call compile when there is a match if url.match(/(\.js|\.css)$/) - for hemapp in applications - if pkg = hemapp.isMatchingRoute(url) + for app in hem.apps + if pkg = app.isMatchingRoute(url) # TODO: keep (and return) in memory build if there hasn't been any changes?? str = pkg.build(false) res.charset = 'utf-8' @@ -70,7 +81,7 @@ server.middleware = (applications, options) -> res.end((req.method is 'HEAD' and null) or str) return - # check static content + # pass request to static connect app to handle static/proxy requests statics.handle(req, res, next) # ------- Private Functions @@ -90,7 +101,7 @@ createRoutingProxy = (options) -> proxy = new httpProxy.RoutingProxy() # set options options.path or= "" - options.port or= url.port or 80 + options.port or= 80 options.patchRedirect or= true # handle redirects if options.patchRedirect From 405ff9a9f06ff83e4bae24a5a1f6156a422cdfa5 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 4 Oct 2013 19:52:46 -0500 Subject: [PATCH 094/167] using 'src' instead of path, seems like a better name --- assets/defaults/spine.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 045063e..195ce39 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -8,7 +8,7 @@ }, "css": { - "paths": [ + "src": [ "css" ], "target": "public/application.css" @@ -29,7 +29,7 @@ "spine/lib/manager", "spine/lib/relation" ], - "paths": [ + "src": [ "app" ], "target": "public/application.js" @@ -43,7 +43,7 @@ "test": { "identifier": "specs", "libs": [], - "paths": [ + "src": [ "test/specs" ], "target": "test/public/specs.js", From dffc7db4876fd6225c08e31216d75f31e7e54fe5 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 7 Oct 2013 14:17:58 -0500 Subject: [PATCH 095/167] lots of refactoring on the server/hem/package classes, opening door for other features... --- src/hem.coffee | 19 +++++++------- src/log.coffee | 2 +- src/package.coffee | 32 ++++++++++++----------- src/server.coffee | 64 ++++++++++++++++++++++++++-------------------- 4 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index ba236f4..08c2b15 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,6 +1,8 @@ +optimist = require('optimist') + # ------- Commandline arguments -argv = require('optimist').usage([ +argv = optimist.usage([ 'usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', @@ -67,7 +69,6 @@ class Hem hem: port: 9294 host: "localhost" - routes: {} # ------- instance variables @@ -95,7 +96,6 @@ class Hem @options.hem or= {} @options.hem.port or= Hem.defaults.hem.port @options.hem.host or= Hem.defaults.hem.host - @options.hem.routes or= Hem.defaults.hem.routes # allow overrides from command line @options.hem.port = argv.port if argv.port @@ -117,13 +117,13 @@ class Hem build: -> @clean() - @compile() + @buildApps() version: -> app.version() for app in @apps watch: -> - @compile() + @buildApps() app.watch() for app in @apps test: -> @@ -136,7 +136,7 @@ class Hem @watch() testOptions.singleRun = false else - @compile() + @buildApps() testOptions.singleRun = true # run tests @testTargets(targets, testOptions) @@ -178,8 +178,7 @@ class Hem try delete require.cache[slugPath] slug = require(slugPath) - slug.customize?(@) # allow any customizations to hem before running - slug + slug?(Hem) or slug catch error log.errorAndExit("Couldn't load slug file #{slugPath}. #{error}") @@ -187,8 +186,8 @@ class Hem targetAll = targets.length is 0 (app for app in @apps when app.name in targets or targetAll) - compile: () -> - app.build() for app in @apps() + buildApps: () -> + app.build() for app in @apps testTargets: (targets = [], options = {}) -> testApps = (app for app in @apps when app.test) diff --git a/src/log.coffee b/src/log.coffee index 943598e..9cbe77c 100644 --- a/src/log.coffee +++ b/src/log.coffee @@ -12,7 +12,7 @@ log.error = (message, parse = true) -> console.log "#{sty.red 'ERROR:'} #{parse and sty.parse(message) or message}" log.errorAndExit = (error, parse = true) -> - utils.error(error, parse) + log.error(error, parse) process.exit(1) log.parse = (message) -> diff --git a/src/package.coffee b/src/package.coffee index 432c2be..06905fb 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -9,6 +9,7 @@ argv = require('./utils').ARGV log = require('./log') versioning = require('./versioning') + # ------- Application Class class Application @@ -41,12 +42,14 @@ class Application @route or= "/" # make sure route has a value - @static = {} - @packages = {} + @static = [] + @packages = [] # configure static routes with base root and route values for route, value of config.static - @static[@applyBaseRoute(route)] = @applyRootDir(value)[0] + @static.push + url : @applyBaseRoute(route) + path : @applyRootDir(value)[0] # configure js/css packages for key, value of config @@ -60,13 +63,12 @@ class Application value.name = key # add to @packages array if packager - pkg = new packager(@, value) - @packages[pkg.name] = pkg + @packages.push(new packager(@, value)) # configure test structure if config.test config.test.name = "test" - @packages.test = new TestPackage(@, config.test) + @packages.push(new TestPackage(@, config.test)) # configure versioning if config.version @@ -80,22 +82,22 @@ class Application if @versioning route = @versioning.trim(route) # compare against package route values - for name, pkg of @packages + for pkg in @packages return pkg if route is pkg.route # return nothing return unlink: -> log("Removing application targets: #{@name}") - pkg.unlink() for key, pkg of @packages + pkg.unlink() for pkg in @packages build: -> log("Building application targets: #{@name}") - pkg.build() for key, pkg of @packages + pkg.build() for pkg in @packages watch: -> log("Watching application: #{@name}") - dirs = (pkg.watch() for key, pkg of @packages) + dirs = (pkg.watch() for pkg in @packages) # make sure dirs has valid values if dirs.length log.info("- Watching directories: #{dirs}") @@ -132,7 +134,7 @@ class Package constructor: (app, config) -> @app = app @name = config.name - @src = @app.applyRootDir(config.src or "") + @src = @app.applyRootDir(config.src or "") @target = @app.applyRootDir(config.target or "")[0] # determine target filename @@ -155,11 +157,11 @@ class Package @route = @app.applyBaseRoute(config.route) # use the static urls to determine the package @route else - for route, value of @app.static when not @route - if utils.startsWith(@target, value) - regexp = new RegExp("^#{value.replace(/\\/g,"\\\\")}(\\\\|\/)?") + for route in @app.static when not @route + if utils.startsWith(@target, route.path) + regexp = new RegExp("^#{route.path.replace(/\\/g,"\\\\")}(\\\\|\/)?") targetUrl = @target.replace(regexp,"") - @route = utils.cleanRoute(route, targetUrl) + @route = utils.cleanRoute(route.url, targetUrl) # make sure we have a route to use when using server command if argv.command is "server" and not @route diff --git a/src/server.coffee b/src/server.coffee index 664588f..c4efbe4 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -17,52 +17,61 @@ server.start = (hem) -> # start server options = hem.options.hem http.createServer(app).listen(options.port, options.host) + return app server.middleware = (hem) -> - statics = connect() + backend = connect() options = hem.options.hem + statics = [] + + # create array of static routes + for route, value of options?.static + statics.push + url : route + path : value # determine if there is any dynamic or static routes to add for app in hem.apps # if verbose then print our mappings and apply the baseAppRoute if present log.info "> Apply route mappings for application: #{app.name}" - for name, pkg of app.packages - pkg.route = utils.cleanRoute(options.baseAppRoute, pkg.route) if options.baseAppRoute + for pkg in app.packages + if options.baseAppRoute + pkg.route = utils.cleanRoute(options.baseAppRoute, pkg.route) log.info " - Mapping route #{pkg.route} to #{pkg.target}" - for route, value of app.static - route = utils.cleanRoute(options.baseAppRoute, route) if options.baseAppRoute - log.info " - Mapping static #{route} to #{value}" - # add static routes to main options.route collection - if app.static - options.routes = utils.extend(app.static, options.routes) + # loop over potential static routes and add them to main route array + for route in app.static + if options.baseAppRoute + route.url = utils.cleanRoute(options.baseAppRoute, route.url) + log.info " - Mapping static #{route.url} to #{route.path}" + statics.push(route) # setup separate connect app for static routes and proxy middleware - for route, value of options.routes - if fs.existsSync(value) - # test if file is directory or file.... - if fs.lstatSync(value).isDirectory() - statics.use(route, checkForRedirect()) - statics.use(route, connect.static(value) ) - else - statics.use route, do (value) -> - (req, res) -> - fs.readFile value, (err, data) -> - if err - res.writeHead(404) - res.end(JSON.stringify(err)) - return + for route in statics + # make sure path exists + unless fs.existsSync(route.path) + log.errorAndExit "The resource #{route.path} not found for static mapping #{route.url}" + # test if file is directory or file.... + if fs.lstatSync(route.path).isDirectory() + backend.use(route.url, checkForRedirect()) + backend.use(route.url, connect.static(route.path) ) + else + backend.use route.url, do (route) -> + (req, res) -> + fs.readFile route.path, (err, data) -> + if err + res.writeHead(404) + res.end(JSON.stringify(err)) + else res.writeHead(200) res.end(data) - else - log.errorAndExit "The folder #{value} does not exist for static mapping #{route}" # setup proxy route for route, value of options.proxy display = "#{value.host}:#{value.port or 80}#{value.path}" log.info "> Proxy requests #{route} to #{display}" - statics.use(route, createRoutingProxy(value)) + backend.use(route, createRoutingProxy(value)) # return the custom middleware for connect to use return (req, res, next) -> @@ -73,7 +82,6 @@ server.middleware = (hem) -> if url.match(/(\.js|\.css)$/) for app in hem.apps if pkg = app.isMatchingRoute(url) - # TODO: keep (and return) in memory build if there hasn't been any changes?? str = pkg.build(false) res.charset = 'utf-8' res.setHeader('Content-Type', mime.lookup(pkg.target)) @@ -82,7 +90,7 @@ server.middleware = (hem) -> return # pass request to static connect app to handle static/proxy requests - statics.handle(req, res, next) + backend.handle(req, res, next) # ------- Private Functions From db3facbb534b538d6e44cf5b8678f2d995b21d7f Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 7 Oct 2013 14:18:07 -0500 Subject: [PATCH 096/167] compiled js files --- lib/compilers.js | 18 ++-- lib/hem.js | 228 ++++++++++++++++++++--------------------------- lib/log.js | 44 +++++++++ lib/package.js | 152 ++++++++++++++++--------------- lib/server.js | 129 ++++++++++++++++----------- lib/utils.js | 30 +------ 6 files changed, 308 insertions(+), 293 deletions(-) create mode 100644 lib/log.js diff --git a/lib/compilers.js b/lib/compilers.js index afb40be..ff6f6a2 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,12 +1,14 @@ // Generated by CoffeeScript 1.6.3 (function() { - var compileCoffeescript, compilers, cs, fs, lmCache, path, projectPath, requireLocalModule, utils; + var argv, compileCoffeescript, compilers, cs, fs, lmCache, log, path, projectPath, requireLocalModule; fs = require('fs'); path = require('path'); - utils = require('./utils'); + log = require('./log'); + + argv = require('./utils').ARGV; compilers = {}; @@ -22,10 +24,10 @@ } catch (_error) { error = _error; relativePath = path.relative(projectPath, _path); - utils.error("Unable to load " + localModule + " module to compile " + relativePath + ""); - utils.error("Try to use 'npm install " + localModule + "' in your project directory."); - if (utils.VERBOSE) { - console.log(error); + log.error("Unable to load " + localModule + " module to compile " + relativePath + ""); + log.error("Try to use 'npm install " + localModule + "' in your project directory."); + if (log.VERBOSE) { + log.error(error, false); } return process.exit(); } @@ -109,7 +111,7 @@ err.path = "jeco Path: " + _path; throw err; } - return "module.exports = function(values, data){ \n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; + return "module.exports = function(values, data){\n var $ = jQuery, result = $();\n values = $.makeArray(values);\n data = data || {};\n for(var i=0; i < values.length; i++) {\n var value = $.extend({}, values[i], data, {index: i});\n var elem = $((" + content + ")(value));\n elem.data('item', value);\n $.merge(result, elem);\n }\n return result;\n};"; }; require.extensions['.jeco'] = require.extensions['.eco']; @@ -121,7 +123,7 @@ try { template = jade.compile(content, { filename: _path, - compileDebug: utils.COMMAND === "server", + compileDebug: argv.command === "server", client: true }); source = template.toString(); diff --git a/lib/hem.js b/lib/hem.js index 199e24a..644d593 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,45 +1,43 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Hem, application, argv, compilers, fs, help, optimist, path, server, testing, utils, + var Hem, application, argv, compilers, fs, help, log, optimist, path, server, testing, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; - path = require('path'); - optimist = require('optimist'); - fs = require('fs'); + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').argv; - utils = require('./utils'); + argv.command = argv._[0]; - compilers = require('./compilers'); + argv.targets = argv._.slice(1); - server = require('./server'); + if (!!argv.nocolors) { + require("sty").disable(); + } - application = require('./package'); + require("./log").VERBOSE = argv.v = !!argv.v; - testing = require('./test'); + require("./utils").ARGV = argv; - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all complications are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').describe('v', ':make hem more talkative(verbose)').argv; + path = require('path'); - argv.command = argv._[0]; + fs = require('fs'); - argv.targets = argv._.slice(1); + compilers = require('./compilers'); - if (!!argv.nocolors) { - require("sty").disable(); - } + server = require('./server'); - utils.ARGV = argv; + testing = require('./test'); - utils.COMPRESS = argv.compress = !!argv.compress; + application = require('./package'); - utils.VERBOSE = argv.v = !!argv.v; + log = require('./log'); - utils.COMMAND = argv.command; + utils = require('./utils'); help = function() { var _ref; - utils.log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); + log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); optimist.showHelp(); return process.exit(); }; @@ -49,73 +47,54 @@ return (new this(options)).exec(command); }; - Hem.include = function(props) { - var key, value, _results; - _results = []; - for (key in props) { - value = props[key]; - _results.push(this.prototype[key] = value); - } - return _results; - }; - - Hem.middleware = function(slugFile) { + Hem.middleware = function(slug) { var hem; - hem = new Hem(slugFile); - return server.middleware(hem.apps, hem.options.server); + hem = new Hem(slug); + return server.middleware(hem); }; - Hem.prototype.compilers = compilers; + Hem.compilers = compilers; - Hem.prototype.homeDir = ''; - - Hem.prototype.options = { + Hem.defaults = { hem: { - port: 9294 + port: 9294, + host: "localhost" } }; + Hem.prototype.options = {}; + Hem.prototype.apps = []; + Hem.prototype.home = process.cwd(); + function Hem(options) { - var config, name, slug, _base, _base1, _ref, _ref1, _ref2; - if (options == null) { - options = {}; - } - if (options === "string") { - slug = options; - } else { - slug = argv.slug || './slug.json'; - if (options) { - this.options = utils.extend(options, this.options); - } + var config, name, slug, _base, _base1, _base2, _ref; + switch (typeof options) { + case "string": + slug = options; + break; + case "object": + this.options = options; + break; + default: + slug || (slug = argv.slug || 'slug'); } - if (fs.existsSync(slug)) { - options = this.readSlug(slug); - if ((_ref = options.hem) != null) { - _ref.port || (_ref.port = this.options.hem.port); - } - if ((_ref1 = options.hem) != null) { - _ref1.host || (_ref1.host = this.options.hem.host); - } - this.options = options; - this.homeDir = path.dirname(path.resolve(process.cwd() + "/" + slug)); - process.chdir(this.homeDir); - } else { - utils.errorAndExit("Unable to find " + slug + " file in current directory"); + if (slug) { + this.options = this.readSlug(slug); } + (_base = this.options).hem || (_base.hem = {}); + (_base1 = this.options.hem).port || (_base1.port = Hem.defaults.hem.port); + (_base2 = this.options.hem).host || (_base2.host = Hem.defaults.hem.host); if (argv.port) { this.options.hem.port = argv.port; } - (_base = this.options.hem).host || (_base.host = ""); - (_base1 = this.options.hem).routes || (_base1.routes = {}); - _ref2 = this.options; - for (name in _ref2) { - config = _ref2[name]; + _ref = this.options; + for (name in _ref) { + config = _ref[name]; if (name === "hem") { continue; } - config.hem = this.options.hem; this.apps.push(application.createApplication(name, config)); } } @@ -123,46 +102,45 @@ Hem.prototype.server = function() { var value; value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; - utils.log("Starting Server at " + value + ""); - return server.start(this.apps, this.options.hem); + log("Starting Server at " + value + ""); + return server.start(this); }; Hem.prototype.clean = function() { - var app, cleanAll, targets, _i, _len, _ref, _ref1, _results; - targets = argv.targets; - cleanAll = targets.length === 0; + var app, _i, _len, _ref, _results; _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; - if ((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || cleanAll) { - _results.push(app.unlink()); - } + _results.push(app.unlink()); } return _results; }; Hem.prototype.build = function() { this.clean(); - return this.buildTargets(argv.targets); + return this.buildApps(); }; Hem.prototype.version = function() { - return this.versionTargets(argv.targets); + var app, _i, _len, _ref, _results; + _ref = this.apps; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + app = _ref[_i]; + _results.push(app.version()); + } + return _results; }; Hem.prototype.watch = function() { - var app, targets, watchAll, _i, _len, _ref, _ref1, _results; - targets = argv.targets; - this.buildTargets(targets); - watchAll = targets.length === 0; + var app, _i, _len, _ref, _results; + this.buildApps(); _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; - if ((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || watchAll) { - _results.push(app.watch()); - } + _results.push(app.watch()); } return _results; }; @@ -177,35 +155,30 @@ this.watch(); testOptions.singleRun = false; } else { - this.buildTargets(targets); + this.buildApps(); testOptions.singleRun = true; } return this.testTargets(targets, testOptions); }; Hem.prototype.check = function() { - var app, inspect, printOptions, targetAll, targets, _i, _len, _ref, _ref1, _results; + var app, inspect, printOptions, _i, _len, _ref, _results; printOptions = { showHidden: false, colors: !argv.nocolors, depth: null }; inspect = require('util').inspect; - utils.log("> Configuration for hem:"); + log("> Configuration for hem:"); console.log(inspect(this.options.hem, printOptions)); - utils.log(""); - targets = argv.targets; - targetAll = targets.length === 0; + log(""); _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; - if (!((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || targetAll)) { - continue; - } - utils.log("> Configuration values for " + app.name + ":"); + log("> Configuration values for " + app.name + ":"); console.log(inspect(app, printOptions)); - _results.push(utils.log("")); + _results.push(log("")); } return _results; }; @@ -217,20 +190,34 @@ if (!this[command]) { return help(); } + this.apps = this.getTargetApps(); return this[command](); }; Hem.prototype.readSlug = function(slug) { - if (!(slug && fs.existsSync(slug))) { - return {}; + var error, slugPath; + slugPath = path.resolve(slug); + try { + slugPath = require.resolve(slugPath); + } catch (_error) { + error = _error; + log.errorAndExit("Couldn't find slug file " + slugPath + ". " + error); + } + Hem.home = path.dirname(slugPath); + try { + delete require.cache[slugPath]; + slug = require(slugPath); + return (typeof slug === "function" ? slug(Hem) : void 0) || slug; + } catch (_error) { + error = _error; + return log.errorAndExit("Couldn't load slug file " + slugPath + ". " + error); } - return JSON.parse(fs.readFileSync(slug, 'utf-8')); }; Hem.prototype.getTargetApps = function(targets) { var app, targetAll, _i, _len, _ref, _ref1, _results; if (targets == null) { - targets = []; + targets = argv.targets; } targetAll = targets.length === 0; _ref = this.apps; @@ -244,6 +231,17 @@ return _results; }; + Hem.prototype.buildApps = function() { + var app, _i, _len, _ref, _results; + _ref = this.apps; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + app = _ref[_i]; + _results.push(app.build()); + } + return _results; + }; + Hem.prototype.testTargets = function(targets, options) { var app, testApps; if (targets == null) { @@ -254,7 +252,7 @@ } testApps = (function() { var _i, _len, _ref, _results; - _ref = this.getTargetApps(targets); + _ref = this.apps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; @@ -267,34 +265,6 @@ return testing.run(testApps, options); }; - Hem.prototype.buildTargets = function(targets) { - var app, _i, _len, _ref, _results; - if (targets == null) { - targets = []; - } - _ref = this.getTargetApps(targets); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.build()); - } - return _results; - }; - - Hem.prototype.versionTargets = function(targets) { - var app, _i, _len, _ref, _results; - if (targets == null) { - targets = []; - } - _ref = this.getTargetApps(targets); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.version()); - } - return _results; - }; - return Hem; })(); diff --git a/lib/log.js b/lib/log.js new file mode 100644 index 0000000..37e0b61 --- /dev/null +++ b/lib/log.js @@ -0,0 +1,44 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var log, sty; + + sty = require('sty'); + + log = function(message, parse) { + if (parse == null) { + parse = true; + } + return console.log(parse && sty.parse(message) || message); + }; + + log.info = function(message, parse) { + if (parse == null) { + parse = true; + } + if (this.VERBOSE) { + return console.log(parse && sty.parse(message) || message); + } + }; + + log.error = function(message, parse) { + if (parse == null) { + parse = true; + } + return console.log("" + (sty.red('ERROR:')) + " " + (parse && sty.parse(message) || message)); + }; + + log.errorAndExit = function(error, parse) { + if (parse == null) { + parse = true; + } + log.error(error, parse); + return process.exit(1); + }; + + log.parse = function(message) { + return sty.parse(message); + }; + + module.exports = log; + +}).call(this); diff --git a/lib/package.js b/lib/package.js index 99004b7..7a3d235 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, createApplication, fs, path, uglifycss, uglifyjs, utils, versioning, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, argv, createApplication, fs, log, path, uglifycss, uglifyjs, utils, versioning, __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -19,11 +19,15 @@ utils = require('./utils'); + argv = require('./utils').ARGV; + + log = require('./log'); + versioning = require('./versioning'); Application = (function() { function Application(name, config) { - var defaults, err, key, loadedDefaults, packager, pkg, route, value, verType, _ref, _ref1; + var defaults, err, key, loadedDefaults, packager, route, value, verType, _ref; if (config == null) { config = {}; } @@ -36,7 +40,7 @@ defaults = utils.extend({}, loadedDefaults); } catch (_error) { err = _error; - utils.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); + log.error("ERROR: Invalid 'defaults' value provided: " + config.defaults); process.exit(1); } config = utils.extend(defaults, config); @@ -44,19 +48,21 @@ if (!this.root) { if (utils.isDirectory(this.name)) { this.root = this.name; - this.route || (this.route = this.applyBaseRoute("/" + this.name)); + this.route || (this.route = "/" + this.name); } else { - this.root = ""; + this.root = "/"; + this.route || (this.route = "/"); } } - this.route || (this.route = this.applyBaseRoute("/")); - this.route = this.applyBaseRoute((_ref = config.hem) != null ? _ref.baseAppRoute : void 0, this.route); - this["static"] = {}; - this.packages = {}; - _ref1 = config["static"]; - for (route in _ref1) { - value = _ref1[route]; - this["static"][this.applyBaseRoute(this.route, route)] = this.applyRootDir(value)[0]; + this["static"] = []; + this.packages = []; + _ref = config["static"]; + for (route in _ref) { + value = _ref[route]; + this["static"].push({ + url: this.applyBaseRoute(route), + path: this.applyRootDir(value)[0] + }); } for (key in config) { value = config[key]; @@ -69,31 +75,30 @@ value.name = key; } if (packager) { - pkg = new packager(this, value); - this.packages[pkg.name] = pkg; + this.packages.push(new packager(this, value)); } } if (config.test) { config.test.name = "test"; - this.packages.test = new TestPackage(this, config.test); + this.packages.push(new TestPackage(this, config.test)); } if (config.version) { verType = versioning[config.version.type]; if (!verType) { - utils.errorAndExit("Incorrect type value for version configuration: (" + config.version.type + ")"); + log.errorAndExit("Incorrect type value for version configuration: (" + config.version.type + ")"); } this.versioning = new verType(this, config.version); } } Application.prototype.isMatchingRoute = function(route) { - var name, pkg, _ref; + var pkg, _i, _len, _ref; if (this.versioning) { route = this.versioning.trim(route); } _ref = this.packages; - for (name in _ref) { - pkg = _ref[name]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; if (route === pkg.route) { return pkg; } @@ -101,55 +106,55 @@ }; Application.prototype.unlink = function() { - var key, pkg, _ref, _results; - utils.log("Removing application targets: " + this.name + ""); + var pkg, _i, _len, _ref, _results; + log("Removing application targets: " + this.name + ""); _ref = this.packages; _results = []; - for (key in _ref) { - pkg = _ref[key]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; _results.push(pkg.unlink()); } return _results; }; Application.prototype.build = function() { - var key, pkg, _ref, _results; - utils.log("Building application targets: " + this.name + ""); + var pkg, _i, _len, _ref, _results; + log("Building application targets: " + this.name + ""); _ref = this.packages; _results = []; - for (key in _ref) { - pkg = _ref[key]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; _results.push(pkg.build()); } return _results; }; Application.prototype.watch = function() { - var dirs, key, pkg; - utils.log("Watching application: " + this.name + ""); + var dirs, pkg; + log("Watching application: " + this.name + ""); dirs = (function() { - var _ref, _results; + var _i, _len, _ref, _results; _ref = this.packages; _results = []; - for (key in _ref) { - pkg = _ref[key]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; _results.push(pkg.watch()); } return _results; }).call(this); if (dirs.length) { - return utils.info("- Watching directories: " + dirs + ""); + return log.info("- Watching directories: " + dirs + ""); } else { - return utils.info("- No directories to watch..."); + return log.info("- No directories to watch..."); } }; Application.prototype.version = function() { - utils.log("Versioning application: " + this.name + ""); + log("Versioning application: " + this.name + ""); if (this.versioning) { return this.versioning.update(); } else { - return utils.errorAndExit("ERROR: Versioning not enabled in slug.json"); + return log.errorAndExit("ERROR: Versioning not enabled in slug.json"); } }; @@ -170,6 +175,9 @@ Application.prototype.applyBaseRoute = function() { var values; values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + if (this.route) { + values.unshift(this.route); + } return utils.cleanRoute.apply(utils, values); }; @@ -178,15 +186,15 @@ })(); Package = (function() { - function Package(parent, config) { - var regexp, route, targetFile, targetUrl, value, _ref; - this.parent = parent; + function Package(app, config) { + var regexp, route, targetFile, targetUrl, _i, _len, _ref; + this.app = app; this.name = config.name; - this.paths = this.parent.applyRootDir(config.paths || ""); - this.target = this.parent.applyRootDir(config.target || "")[0]; + this.src = this.app.applyRootDir(config.src || ""); + this.target = this.app.applyRootDir(config.target || "")[0]; if (utils.isDirectory(this.target)) { if (this.name === this.ext) { - targetFile = parent.name; + targetFile = this.app.name; } else { targetFile = this.name; } @@ -199,34 +207,32 @@ if (utils.startsWith(this.target, "/")) { this.route = config.route; } else { - this.route = this.parent.applyBaseRoute(parent.route, config.route); + this.route = this.app.applyBaseRoute(config.route); } } else { - _ref = this.parent["static"]; - for (route in _ref) { - value = _ref[route]; + _ref = this.app["static"]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + route = _ref[_i]; if (!this.route) { - if (utils.startsWith(this.target, value)) { - regexp = new RegExp("^" + (value.replace(/\\/g, "\\\\")) + "(\\\\|\/)?"); + if (utils.startsWith(this.target, route.path)) { + regexp = new RegExp("^" + (route.path.replace(/\\/g, "\\\\")) + "(\\\\|\/)?"); targetUrl = this.target.replace(regexp, ""); - this.route = this.parent.applyBaseRoute(route, targetUrl); + this.route = utils.cleanRoute(route.url, targetUrl); } } } } - if (utils.COMMAND === "server") { - if (!this.route) { - utils.errorAndExit("Unable to determine route for " + this.target + ""); - } + if (argv.command === "server" && !this.route) { + log.errorAndExit("Unable to determine route for " + this.target + ""); } } Package.prototype.handleCompileError = function(ex) { - utils.error(ex.message); + log.error(ex.message); if (ex.path) { - utils.error(ex.path); + log.error(ex.path); } - switch (utils.COMMAND) { + switch (argv.command) { case "server" || "watch": return "console.log(\"HEM compile ERROR: " + ex + "\n" + ex.path + "\");"; default: @@ -236,7 +242,7 @@ Package.prototype.unlink = function() { if (fs.existsSync(this.target)) { - utils.info("- removing " + this.target + ""); + log.info("- removing " + this.target + ""); return fs.unlinkSync(this.target); } }; @@ -246,8 +252,8 @@ if (write == null) { write = true; } - extra = (utils.COMPRESS && " --using compression") || ""; - utils.log("- Building target: " + this.target + "" + extra); + extra = (argv.compress && " --using compression") || ""; + log("- Building target: " + this.target + "" + extra); source = this.compile(); if (source && write) { dirname = path.dirname(this.target); @@ -293,7 +299,7 @@ }; Package.prototype.getWatchedDirs = function() { - return this.paths; + return this.src; }; Package.prototype.ext = ""; @@ -305,10 +311,10 @@ JsPackage = (function(_super) { __extends(JsPackage, _super); - function JsPackage(parent, config) { - JsPackage.__super__.constructor.call(this, parent, config); + function JsPackage(app, config) { + JsPackage.__super__.constructor.call(this, app, config); this.identifier = config.identifier || 'require'; - this.libs = this.parent.applyRootDir(config.libs || []); + this.libs = this.app.applyRootDir(config.libs || []); this.after = utils.arrayToString(config.after || ""); this.modules = utils.toArray(config.modules || []); } @@ -317,7 +323,7 @@ var ex, result; try { result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); - if (utils.COMPRESS) { + if (argv.compress) { result = uglifyjs.minify(result, { fromString: true }).code; @@ -332,7 +338,7 @@ JsPackage.prototype.compileModules = function() { var _modules, _stitch; this.depend || (this.depend = new Dependency(this.modules)); - _stitch = new Stitch(this.paths); + _stitch = new Stitch(this.src); _modules = this.depend.resolve().concat(_stitch.resolve()); if (_modules) { return _stitch.template(this.identifier, _modules); @@ -372,7 +378,7 @@ }; JsPackage.prototype.getWatchedDirs = function() { - return this.paths.concat(this.libs); + return this.src.concat(this.libs); }; JsPackage.prototype.ext = "js"; @@ -384,8 +390,8 @@ TestPackage = (function(_super) { __extends(TestPackage, _super); - function TestPackage(parent, config) { - TestPackage.__super__.constructor.call(this, parent, config); + function TestPackage(app, config) { + TestPackage.__super__.constructor.call(this, app, config); this.depends = utils.toArray(config.depends); this.runner = config.runner; } @@ -397,8 +403,8 @@ CssPackage = (function(_super) { __extends(CssPackage, _super); - function CssPackage(parent, config) { - CssPackage.__super__.constructor.call(this, parent, config); + function CssPackage(app, config) { + CssPackage.__super__.constructor.call(this, app, config); } CssPackage.prototype.compile = function() { @@ -410,7 +416,7 @@ delete require.cache[filepath]; return require(filepath); }; - _ref = this.paths; + _ref = this.src; for (_i = 0, _len = _ref.length; _i < _len; _i++) { fileOrDir = _ref[_i]; if (utils.isDirectory(fileOrDir)) { @@ -428,7 +434,7 @@ } } result = output.join("\n"); - if (utils.COMPRESS) { + if (argv.compress) { result = uglifycss.processString(result); } return result; diff --git a/lib/server.js b/lib/server.js index 51c7036..5f1b426 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,85 +1,106 @@ // Generated by CoffeeScript 1.6.3 (function() { - var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, mime, patchServerResponseForRedirects, server, utils; + var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, log, mime, patchServerResponseForRedirects, server, utils; connect = require('connect'); mime = require('connect')["static"].mime; + httpProxy = require('http-proxy'); + http = require('http'); fs = require('fs'); utils = require('./utils'); - httpProxy = require('http-proxy'); + log = require('./log'); server = {}; - server.start = function(applications, options) { - var app; + server.start = function(hem) { + var app, options; app = connect(); - app.use(server.middleware(applications, options)); - return http.createServer(app).listen(options.port, options.host); + app.use(server.middleware(hem)); + options = hem.options.hem; + http.createServer(app).listen(options.port, options.host); + return app; }; - server.middleware = function(applications, options) { - var display, hemapp, pkg, route, statics, value, _i, _j, _len, _len1, _ref, _ref1, _ref2; - for (_i = 0, _len = applications.length; _i < _len; _i++) { - hemapp = applications[_i]; - utils.info("> Apply route mappings for application: " + hemapp.name + ""); - _ref = hemapp.packages; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - pkg = _ref[_j]; - utils.info("- Mapping route " + pkg.route + " to " + pkg.target + ""); + server.middleware = function(hem) { + var app, backend, display, options, pkg, route, statics, value, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4; + backend = connect(); + options = hem.options.hem; + statics = []; + _ref = options != null ? options["static"] : void 0; + for (route in _ref) { + value = _ref[route]; + statics.push({ + url: route, + path: value + }); + } + _ref1 = hem.apps; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + app = _ref1[_i]; + log.info("> Apply route mappings for application: " + app.name + ""); + _ref2 = app.packages; + for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { + pkg = _ref2[_j]; + if (options.baseAppRoute) { + pkg.route = utils.cleanRoute(options.baseAppRoute, pkg.route); + } + log.info(" - Mapping route " + pkg.route + " to " + pkg.target + ""); } - if (hemapp["static"]) { - options.routes = utils.extend(hemapp["static"], options.routes); + _ref3 = app["static"]; + for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { + route = _ref3[_k]; + if (options.baseAppRoute) { + route.url = utils.cleanRoute(options.baseAppRoute, route.url); + } + log.info(" - Mapping static " + route.url + " to " + route.path + ""); + statics.push(route); } } - statics = connect(); - _ref1 = options.routes; - for (route in _ref1) { - value = _ref1[route]; - if (fs.existsSync(value)) { - if (fs.lstatSync(value).isDirectory()) { - utils.info("- Mapping static " + route + " to dir " + value + ""); - statics.use(route, checkForRedirect()); - statics.use(route, connect["static"](value)); - } else { - utils.info("- Mapping static " + route + " to resource " + value + ""); - statics.use(route, (function(value) { - return function(req, res) { - return fs.readFile(value, function(err, data) { - if (err) { - res.writeHead(404); - res.end(JSON.stringify(err)); - return; - } + for (_l = 0, _len3 = statics.length; _l < _len3; _l++) { + route = statics[_l]; + if (!fs.existsSync(route.path)) { + log.errorAndExit("The resource " + route.path + " not found for static mapping " + route.url + ""); + } + if (fs.lstatSync(route.path).isDirectory()) { + backend.use(route.url, checkForRedirect()); + backend.use(route.url, connect["static"](route.path)); + } else { + backend.use(route.url, (function(route) { + return function(req, res) { + return fs.readFile(route.path, function(err, data) { + if (err) { + res.writeHead(404); + return res.end(JSON.stringify(err)); + } else { res.writeHead(200); return res.end(data); - }); - }; - })(value)); - } - } else { - utils.errorAndExit("The folder " + value + " does not exist for static mapping " + route + ""); + } + }); + }; + })(route)); } } - _ref2 = options.proxy; - for (route in _ref2) { - value = _ref2[route]; + _ref4 = options.proxy; + for (route in _ref4) { + value = _ref4[route]; display = "" + value.host + ":" + (value.port || 80) + value.path; - utils.info("- Proxy requests " + route + " to " + display + ""); - statics.use(route, createRoutingProxy(value)); + log.info("> Proxy requests " + route + " to " + display + ""); + backend.use(route, createRoutingProxy(value)); } return function(req, res, next) { - var str, url, _k, _len2, _ref3; - url = ((_ref3 = require("url").parse(req.url)) != null ? _ref3.pathname.toLowerCase() : void 0) || ""; + var str, url, _len4, _m, _ref5, _ref6; + url = ((_ref5 = require("url").parse(req.url)) != null ? _ref5.pathname.toLowerCase() : void 0) || ""; if (url.match(/(\.js|\.css)$/)) { - for (_k = 0, _len2 = applications.length; _k < _len2; _k++) { - hemapp = applications[_k]; - if (pkg = hemapp.isMatchingRoute(url)) { + _ref6 = hem.apps; + for (_m = 0, _len4 = _ref6.length; _m < _len4; _m++) { + app = _ref6[_m]; + if (pkg = app.isMatchingRoute(url)) { str = pkg.build(false); res.charset = 'utf-8'; res.setHeader('Content-Type', mime.lookup(pkg.target)); @@ -89,7 +110,7 @@ } } } - return statics.handle(req, res, next); + return backend.handle(req, res, next); }; }; @@ -112,7 +133,7 @@ var proxy; proxy = new httpProxy.RoutingProxy(); options.path || (options.path = ""); - options.port || (options.port = url.port || 80); + options.port || (options.port = 80); options.patchRedirect || (options.patchRedirect = true); if (options.patchRedirect) { proxy.once("start", function(req, res) { diff --git a/lib/utils.js b/lib/utils.js index b51f2f7..32ec753 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,11 +1,9 @@ // Generated by CoffeeScript 1.6.3 (function() { - var clean, extend, flatten, fs, isWin, path, sty, utils, + var clean, extend, flatten, fs, isWin, path, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; - sty = require('sty'); - path = require('path'); fs = require('fs'); @@ -146,32 +144,6 @@ return clean(routes, "/"); }; - utils.log = function(message) { - return console.log(sty.parse(message)); - }; - - utils.info = function(message) { - if (this.VERBOSE) { - return console.log(sty.parse(message)); - } - }; - - utils.error = function(message, parse) { - if (parse == null) { - parse = true; - } - return console.log("" + (sty.red('ERROR:')) + " " + (parse && sty.parse(message) || message)); - }; - - utils.errorAndExit = function(error) { - utils.error(error); - return process.exit(1); - }; - - utils.parse = function(message) { - return sty.parse(message); - }; - module.exports = utils; }).call(this); From 9b9ff7b0abec6ca38d2dc36b8f3e423b6d5c72fd Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 7 Oct 2013 14:19:12 -0500 Subject: [PATCH 097/167] =?UTF-8?q?updated=20version,=20everything=20shoul?= =?UTF-8?q?d=20be=20working=20at=20this=20point.=20On=20to=20more=20featur?= =?UTF-8?q?es=E2=80=A6.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f7704a..be8507b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.14", + "version": "0.4.15", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 0669be4d15b12c44a89eba12f64abff4f830d131 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 7 Oct 2013 14:59:02 -0500 Subject: [PATCH 098/167] beginnings of event system, will expand at a later date. --- lib/hem.js | 23 +++++++++++++---------- lib/utils.js | 6 +++++- src/hem.coffee | 20 ++++++++++++-------- src/utils.coffee | 11 ++++++++--- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 644d593..cbeb5d7 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -3,6 +3,10 @@ var Hem, application, argv, compilers, fs, help, log, optimist, path, server, testing, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + fs = require('fs'); + + path = require('path'); + optimist = require('optimist'); argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').argv; @@ -15,13 +19,13 @@ require("sty").disable(); } - require("./log").VERBOSE = argv.v = !!argv.v; + log = require('./log'); - require("./utils").ARGV = argv; + log.VERBOSE = argv.v = !!argv.v; - path = require('path'); + utils = require('./utils'); - fs = require('fs'); + utils.ARGV = argv; compilers = require('./compilers'); @@ -31,10 +35,6 @@ application = require('./package'); - log = require('./log'); - - utils = require('./utils'); - help = function() { var _ref; log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); @@ -55,6 +55,8 @@ Hem.compilers = compilers; + Hem.events = utils.events; + Hem.defaults = { hem: { port: 9294, @@ -100,10 +102,11 @@ } Hem.prototype.server = function() { - var value; + var app, value; value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; log("Starting Server at " + value + ""); - return server.start(this); + app = server.start(this); + return Hem.events.emit("server-start", app); }; Hem.prototype.clean = function() { diff --git a/lib/utils.js b/lib/utils.js index 32ec753..1a0b10c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var clean, extend, flatten, fs, isWin, path, utils, + var clean, events, extend, flatten, fs, isWin, path, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; @@ -8,6 +8,8 @@ fs = require('fs'); + events = require('events'); + utils = {}; isWin = !!require('os').platform().match(/^win/); @@ -104,6 +106,8 @@ } }; + utils.events = new events.EventEmitter(); + clean = function(values, sep, trimStart) { var regexp, result, value, _i, _len; if (trimStart == null) { diff --git a/src/hem.coffee b/src/hem.coffee index 08c2b15..736c467 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -1,3 +1,5 @@ +fs = require('fs') +path = require('path') optimist = require('optimist') # ------- Commandline arguments @@ -28,21 +30,19 @@ argv.targets = argv._[1..] require("sty").disable() if !!argv.nocolors # turn on/off verbose logging -require("./log").VERBOSE = argv.v = !!argv.v +log = require('./log') +log.VERBOSE = argv.v = !!argv.v # save argv to utils class to allow access by other modules -require("./utils").ARGV = argv +utils = require('./utils') +utils.ARGV = argv # ------- perform requires -path = require('path') -fs = require('fs') compilers = require('./compilers') server = require('./server') testing = require('./test') application = require('./package') -log = require('./log') -utils = require('./utils') # ------- Global Functions @@ -62,7 +62,10 @@ class Hem hem = new Hem(slug) server.middleware(hem) - @compilers: compilers + # exposing globals for customization + + @compilers : compilers + @events : utils.events # TODO: eventuall get an event system going... # default values for server @defaults: @@ -110,7 +113,8 @@ class Hem server: -> value = "http://#{@options.hem.host or "*"}:#{@options.hem.port}" log "Starting Server at #{value}" - server.start(@) + app = server.start(@) + Hem.events.emit("server-start", app) clean: -> app.unlink() for app in @apps diff --git a/src/utils.coffee b/src/utils.coffee index 789bf0c..dd9059d 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,6 +1,7 @@ -path = require('path') -fs = require('fs') -utils = {} +path = require('path') +fs = require('fs') +events = require('events') +utils = {} # check for windows :o(... isWin = !!require('os').platform().match(/^win/) @@ -57,6 +58,10 @@ utils.isDirectory = (dir) -> catch e false +# ------ Setup shareable events emitter + +utils.events = new events.EventEmitter() + # ------ Formatting urls and folder paths clean = (values, sep, trimStart = false) -> From de7b85d25c2763d4e13bb2c594c14349f3c4bcfa Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 7 Oct 2013 15:18:30 -0500 Subject: [PATCH 099/167] using commonjs as config value instead of identifier --- lib/package.js | 4 ++-- src/package.coffee | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/package.js b/lib/package.js index 7a3d235..ca8c4a6 100644 --- a/lib/package.js +++ b/lib/package.js @@ -313,7 +313,7 @@ function JsPackage(app, config) { JsPackage.__super__.constructor.call(this, app, config); - this.identifier = config.identifier || 'require'; + this.commonjs = config.commonjs || 'require'; this.libs = this.app.applyRootDir(config.libs || []); this.after = utils.arrayToString(config.after || ""); this.modules = utils.toArray(config.modules || []); @@ -341,7 +341,7 @@ _stitch = new Stitch(this.src); _modules = this.depend.resolve().concat(_stitch.resolve()); if (_modules) { - return _stitch.template(this.identifier, _modules); + return _stitch.template(this.commonjs, _modules); } else { return ""; } diff --git a/src/package.coffee b/src/package.coffee index 06905fb..f253657 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -228,7 +228,7 @@ class JsPackage extends Package super(app, config) # javascript only configurations - @identifier = config.identifier or 'require' + @commonjs = config.commonjs or 'require' @libs = @app.applyRootDir(config.libs or []) @after = utils.arrayToString(config.after or "") @modules = utils.toArray(config.modules or []) @@ -253,7 +253,7 @@ class JsPackage extends Package _stitch = new Stitch(@src) _modules = @depend.resolve().concat(_stitch.resolve()) if _modules - _stitch.template(@identifier, _modules) + _stitch.template(@commonjs, _modules) else "" From e552c0989e26c011959c6a8c3cb014b09cd399c4 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 7 Oct 2013 15:19:07 -0500 Subject: [PATCH 100/167] using commonjs instead of identifier part 2 --- assets/defaults/spine.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 195ce39..f4d1e07 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -15,7 +15,7 @@ }, "js": { - "identifier": "require", + "commonjs": "require", "libs": [ "lib" ], @@ -41,7 +41,7 @@ }, "test": { - "identifier": "specs", + "commonjs": "specs", "libs": [], "src": [ "test/specs" From 8b3a6b36ac6e40b34b957b9fc14b177937dc9cee Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 17:58:35 -0500 Subject: [PATCH 101/167] adding micro template compiler so we can store html files in assets --- src/compilers.coffee | 5 ++++- src/utils.coffee | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/compilers.coffee b/src/compilers.coffee index 5dbde5b..5c458aa 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -40,7 +40,7 @@ require.extensions['.css'] = (module, filename) -> module._compile "module.exports = #{source}", filename ## -## HTML files +## HTML and Tmpl files ## compilers.html = (_path) -> @@ -50,6 +50,9 @@ compilers.html = (_path) -> require.extensions['.html'] = (module, filename) -> module._compile compilers.html(filename), filename +require.extensions['.tmpl'] = (module, filename) -> + module._compile compilers.html(filename), filename + ## ## Compile Coffeescript ## diff --git a/src/utils.coffee b/src/utils.coffee index dd9059d..7e0de74 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -58,6 +58,45 @@ utils.isDirectory = (dir) -> catch e false +# ------ Simple templating function + +# Simple JavaScript Templating +# John Resig - http://ejohn.org/ - MIT Licensed +tmplCache = {}; + +utils.tmpl = (str, data) -> + # Figure out if we're getting a template, or if we need to + # load the template - and be sure to cache the result. + if not /[\t\r\n% ]/.test(str) + if tmplCache[str] + fn = tmplCache[str] + else + # load file + template = utils.loadAsset("#{str}.tmpl") + fn = utils.tmpl(template) + else + # Convert the template into pure JavaScript + str = str + .split("'").join("\\'") + .split("\n").join("\\n") + .replace(/{{([\s\S]*?)}}/mg, (m, t) -> '{{' + t.split("\\'").join("'").split("\\n").join("\n") + '}}') + .replace(/{{=(.+?)}}/g, "',$1,'") + .split("{{").join("');") + .split("}}").join("p.push('") + # Generate a reusable function that will serve as a template + fn = new Function("obj", + """ + var p=[] + var print = function(){ p.push.apply(p,arguments); }; + with(obj){ + p.push('#{str}'); + } + return p.join(''); + """ + ) + # Provide some basic currying to the user + return data and fn( data ) or fn; + # ------ Setup shareable events emitter utils.events = new events.EventEmitter() From 739aab55c8677de1d031cd402d577506d3124ec2 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 17:59:26 -0500 Subject: [PATCH 102/167] adding jasmine files as assets that can be used to run tests --- assets/testing/index.tmpl | 122 ++ assets/testing/jasmine/jasmine-html.js | 681 +++++++ assets/testing/jasmine/jasmine.css | 82 + assets/testing/jasmine/jasmine.js | 2600 ++++++++++++++++++++++++ 4 files changed, 3485 insertions(+) create mode 100644 assets/testing/index.tmpl create mode 100644 assets/testing/jasmine/jasmine-html.js create mode 100644 assets/testing/jasmine/jasmine.css create mode 100644 assets/testing/jasmine/jasmine.js diff --git a/assets/testing/index.tmpl b/assets/testing/index.tmpl new file mode 100644 index 0000000..c144d25 --- /dev/null +++ b/assets/testing/index.tmpl @@ -0,0 +1,122 @@ + + + + + Hem Test Runner + + + + +{{= hmm }} + + diff --git a/assets/testing/jasmine/jasmine-html.js b/assets/testing/jasmine/jasmine-html.js new file mode 100644 index 0000000..157f7e8 --- /dev/null +++ b/assets/testing/jasmine/jasmine-html.js @@ -0,0 +1,681 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + setExceptionHandling(); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView && reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = jasmine.HtmlReporter.parameters(doc); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}, + self.createDom('span', { className: 'exceptions' }, + self.createDom('label', { className: 'label', for: 'no_try_catch' }, 'No try/catch'), + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } + + function noTryCatch() { + return window.location.search.match(/catch=false/); + } + + function searchWithCatch() { + var params = jasmine.HtmlReporter.parameters(window.document); + var removed = false; + var i = 0; + + while (!removed && i < params.length) { + if (params[i].match(/catch=/)) { + params.splice(i, 1); + removed = true; + } + i++; + } + if (jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + + return params.join("&"); + } + + function setExceptionHandling() { + var chxCatch = document.getElementById('no_try_catch'); + + if (noTryCatch()) { + chxCatch.setAttribute('checked', true); + jasmine.CATCH_EXCEPTIONS = false; + } + chxCatch.onclick = function() { + window.location.search = searchWithCatch(); + }; + } +}; +jasmine.HtmlReporter.parameters = function(doc) { + var paramStr = doc.location.search.substring(1); + var params = []; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + } + return params; +} +jasmine.HtmlReporter.sectionLink = function(sectionName) { + var link = '?'; + var params = []; + + if (sectionName) { + params.push('spec=' + encodeURIComponent(sectionName)); + } + if (!jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + if (params.length > 0) { + link += params.join("&"); + } + + return link; +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/assets/testing/jasmine/jasmine.css b/assets/testing/jasmine/jasmine.css new file mode 100644 index 0000000..8c008dc --- /dev/null +++ b/assets/testing/jasmine/jasmine.css @@ -0,0 +1,82 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/assets/testing/jasmine/jasmine.js b/assets/testing/jasmine/jasmine.js new file mode 100644 index 0000000..5964112 --- /dev/null +++ b/assets/testing/jasmine/jasmine.js @@ -0,0 +1,2600 @@ +var isCommonJS = typeof window == "undefined" && typeof exports == "object"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Maximum levels of nesting that will be included when an object is pretty-printed + */ +jasmine.MAX_PRETTY_PRINT_DEPTH = 40; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +/** + * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. + * Set to false to let the exception bubble up in the browser. + * + */ +jasmine.CATCH_EXCEPTIONS = true; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the + * attributes on the object. + * + * @example + * // don't care about any other attributes than foo. + * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); + * + * @param sample {Object} sample + * @returns matchable object for the sample + */ +jasmine.objectContaining = function (sample) { + return new jasmine.Matchers.ObjectContaining(sample); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + * @return {jasmine.Matchers} + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.source != b.source) + mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); + + if (a.ignoreCase != b.ignoreCase) + mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.global != b.global) + mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.multiline != b.multiline) + mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.sticky != b.sticky) + mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); + + return (mismatchValues.length === 0); +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a.jasmineMatches) { + return a.jasmineMatches(b); + } + + if (b.jasmineMatches) { + return b.jasmineMatches(a); + } + + if (a instanceof jasmine.Matchers.ObjectContaining) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.ObjectContaining) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (a instanceof RegExp && b instanceof RegExp) { + return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + if (!jasmine.CATCH_EXCEPTIONS) { + this.func.apply(this.spec); + } + else { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that compares the actual to NaN. + */ +jasmine.Matchers.prototype.toBeNaN = function() { + this.message = function() { + return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; + }; + + return (this.actual !== this.actual); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; + var positiveMessage = ""; + if (this.actual.callCount === 0) { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; + } else { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') + } + return [positiveMessage, invertedMessage]; + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision, as number of decimal places + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} [expected] + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineToString = function() { + return ''; +}; + +jasmine.Matchers.ObjectContaining = function (sample) { + this.sample = sample; +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var env = jasmine.getEnv(); + + var hasKey = function(obj, keyName) { + return obj != null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in this.sample) { + if (!hasKey(other, property) && hasKey(this.sample, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { + return ""; +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!obj.hasOwnProperty(property)) continue; + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Array"); + return; + } + + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Object"); + return; + } + + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + + // parallel to blocks. each true value in this array means the block will + // get executed even if we abort + this.ensured = []; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.unshift(block); + this.ensured.unshift(ensure); +}; + +jasmine.Queue.prototype.add = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.push(block); + this.ensured.push(ensure); +}; + +jasmine.Queue.prototype.insertNext = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.ensured.splice((this.index + this.offset + 1), 0, ensure); + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this), true); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 3, + "build": 0, + "revision": 1354052693 +}; From 4b9b094ae3bde8b71d0e4e7bc674ee27ea58ded7 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 18:22:01 -0500 Subject: [PATCH 103/167] using fs-extra for directory/file functions --- package.json | 6 +++--- src/package.coffee | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index be8507b..cc9c5bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.15", + "version": "0.4.16", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -38,7 +38,6 @@ "dependencies": { "fast-detective": "~0.0.2", "uglifycss": "0.0.5", - "mkdirp": "~0.3.5", "coffee-script": "~1.6.3", "connect": "~2.9.0", "http-proxy": "~0.10.3", @@ -46,6 +45,7 @@ "karma": "~0.10.2", "sty": "~0.6.1", "optimist": "~0.6.0", - "watch": "~0.8.0" + "watch": "~0.8.0", + "fs-extra": "~0.7.0" } } diff --git a/src/package.coffee b/src/package.coffee index f253657..68a4b29 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -1,4 +1,4 @@ -fs = require('fs') +fs = require('fs-extra') path = require('path') uglifyjs = require('uglify-js') uglifycss = require('uglifycss') @@ -191,8 +191,7 @@ class Package source = @compile() if source and write dirname = path.dirname(@target) - unless fs.existsSync(dirname) - require('mkdirp').sync(dirname) + fs.mkdirsSync(dirname) unless fs.existsSync(dirname) fs.writeFileSync(@target, source) source From 55474f88e37dd091bcf5dbbe14b11b50db9b6b38 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 19:17:48 -0500 Subject: [PATCH 104/167] moving stitch commonjs file to assets as a template to load --- assets/stitch.tmpl | 62 ++++++++++++++++++++++++++++++++++++++++++++++ src/stitch.coffee | 58 +------------------------------------------ src/utils.coffee | 10 +++++--- 3 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 assets/stitch.tmpl diff --git a/assets/stitch.tmpl b/assets/stitch.tmpl new file mode 100644 index 0000000..cd99443 --- /dev/null +++ b/assets/stitch.tmpl @@ -0,0 +1,62 @@ +(function(/*! Stitch !*/) { + if (!this.<%=identifier%>) { + var modules = {}, cache = {}, require = function(name, root) { + var path = expand(root, name), indexPath = expand(path, './index'), module, fn; + module = cache[path] || cache[indexPath] + if (module) { + return module.exports; + } else if (fn = modules[path] || modules[path = indexPath]) { + module = {id: path, exports: {}}; + try { + cache[path] = module; + fn(module.exports, function(name) { + return require(name, dirname(path)); + }, module); + return module.exports; + } catch (err) { + delete cache[path]; + throw err; + } + } else { + throw 'module ' + name + ' not found'; + } + }, expand = function(root, name) { + var results = [], parts, part; + if (/^\.\.?(\\/|$)/.test(name)) { + parts = [root, name].join('/').split('/'); + } else { + parts = name.split('/'); + } + for (var i = 0, length = parts.length; i < length; i++) { + part = parts[i]; + if (part == '..') { + results.pop(); + } else if (part != '.' && part != '') { + results.push(part); + } + } + return results.join('/'); + }, dirname = function(path) { + return path.split('/').slice(0, -1).join('/'); + }; + this.<%=identifier%> = function(name) { + return require(name, ''); + } + this.<%=identifier%>.define = function(bundle) { + for (var key in bundle) + modules[key] = bundle[key]; + }; + this.<%=identifier%>.modules = modules; + this.<%=identifier%>.cache = cache; + } + return this.<%=identifier%>.define; +}).call(this)({ + <% + var output = []; + for (var i = 0; i < modules.length; i++) { + output.push(JSON.stringify(modules[i].id) + ": function(exports, require, module) { " + modules[i].compile() + " }" ); + } + %> + <%= output.join(',') %> +}); + diff --git a/src/stitch.coffee b/src/stitch.coffee index 68f591d..1d6e966 100644 --- a/src/stitch.coffee +++ b/src/stitch.coffee @@ -26,63 +26,7 @@ class Stitch result template: (identifier, modules) -> - """ - (function(/*! Stitch !*/) { - if (!this.#{identifier}) { - var modules = {}, cache = {}, require = function(name, root) { - var path = expand(root, name), indexPath = expand(path, './index'), module, fn; - module = cache[path] || cache[indexPath] - if (module) { - return module.exports; - } else if (fn = modules[path] || modules[path = indexPath]) { - module = {id: path, exports: {}}; - try { - cache[path] = module; - fn(module.exports, function(name) { - return require(name, dirname(path)); - }, module); - return module.exports; - } catch (err) { - delete cache[path]; - throw err; - } - } else { - throw 'module ' + name + ' not found'; - } - }, expand = function(root, name) { - var results = [], parts, part; - if (/^\.\.?(\\/|$)/.test(name)) { - parts = [root, name].join('/').split('/'); - } else { - parts = name.split('/'); - } - for (var i = 0, length = parts.length; i < length; i++) { - part = parts[i]; - if (part == '..') { - results.pop(); - } else if (part != '.' && part != '') { - results.push(part); - } - } - return results.join('/'); - }, dirname = function(path) { - return path.split('/').slice(0, -1).join('/'); - }; - this.#{identifier} = function(name) { - return require(name, ''); - } - this.#{identifier}.define = function(bundle) { - for (var key in bundle) - modules[key] = bundle[key]; - }; - this.#{identifier}.modules = modules; - this.#{identifier}.cache = cache; - } - return this.#{identifier}.define; - }).call(this)({ - #{(JSON.stringify(module.id) + ": function(exports, require, module) {#{module.compile()}}" for module in modules).join(', ')} - }); - """ + require('./utils').tmpl("stitch", { identifier: identifier, modules: modules } ) class Module constructor: (@filename, @parent) -> diff --git a/src/utils.coffee b/src/utils.coffee index 7e0de74..78c5241 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -51,6 +51,8 @@ utils.extend = extend = (a, b) -> utils.loadAsset = (asset) -> require("../assets/" + asset) +utils.copyAsset = (from, to) -> + utils.isDirectory = (dir) -> try stats = fs.lstatSync(dir) @@ -79,10 +81,10 @@ utils.tmpl = (str, data) -> str = str .split("'").join("\\'") .split("\n").join("\\n") - .replace(/{{([\s\S]*?)}}/mg, (m, t) -> '{{' + t.split("\\'").join("'").split("\\n").join("\n") + '}}') - .replace(/{{=(.+?)}}/g, "',$1,'") - .split("{{").join("');") - .split("}}").join("p.push('") + .replace(/<%([\s\S]*?)%>/mg, (m, t) -> '<%' + t.split("\\'").join("'").split("\\n").join("\n") + '%>') + .replace(/<%=(.+?)%>/g, "',$1,'") + .split("<%").join("');") + .split("%>").join("p.push('") # Generate a reusable function that will serve as a template fn = new Function("obj", """ From 4658afb0e40771d7470ca5f6dd0847ec8feb6d37 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 22:33:04 -0500 Subject: [PATCH 105/167] helper method to copy files --- src/utils.coffee | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/utils.coffee b/src/utils.coffee index 78c5241..07026fd 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,5 +1,5 @@ path = require('path') -fs = require('fs') +fs = require('fs-extra') events = require('events') utils = {} @@ -51,7 +51,19 @@ utils.extend = extend = (a, b) -> utils.loadAsset = (asset) -> require("../assets/" + asset) -utils.copyAsset = (from, to) -> +utils.copyFile = (from, to) -> + BUF_LENGTH = 64 * 1024 + _buff = new Buffer(BUF_LENGTH) + fdr = fs.openSync(from, 'r') + fdw = fs.openSync(to, 'w') + bytesRead = 1 + pos = 0 + while bytesRead > 0 + bytesRead = fs.readSync(fdr, _buff, 0, BUF_LENGTH, pos) + fs.writeSync(fdw, _buff, 0, bytesRead) + pos += bytesRead + fs.closeSync(fdr) + fs.closeSync(fdw) utils.isDirectory = (dir) -> try From 8a843e2a2c066df64a66dae3138ffbe03085c194 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 22:33:35 -0500 Subject: [PATCH 106/167] updates to generate the test runner index.html file with correct paths --- src/hem.coffee | 16 +++---- src/package.coffee | 102 +++++++++++++++++++++++++++++++++++---------- src/test.coffee | 24 +++++------ 3 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index 736c467..d65d2a8 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -106,7 +106,7 @@ class Hem # setup applications from options/slug for name, config of @options continue if name is "hem" - @apps.push application.createApplication(name, config) + @apps.push application.createApplication(name, config, @) # ------- Command Functions @@ -131,10 +131,9 @@ class Hem app.watch() for app in @apps test: -> - targets = argv.targets # set test options testOptions = - basePath: @homeDir + basePath: @home # check for watch mode if argv.watch @watch() @@ -143,7 +142,7 @@ class Hem @buildApps() testOptions.singleRun = true # run tests - @testTargets(targets, testOptions) + testing.run(@apps, options) check: -> printOptions = showHidden: false, colors: !argv.nocolors, depth: null @@ -159,9 +158,12 @@ class Hem log "" exec: (command = argv.command) -> + # handle empty arguments return help() unless @[command] + # reset the apps list based on command line args @apps = @getTargetApps() + # hope this works :o) @[command]() @@ -193,11 +195,5 @@ class Hem buildApps: () -> app.build() for app in @apps - testTargets: (targets = [], options = {}) -> - testApps = (app for app in @apps when app.test) - testing.run(testApps, options) - - - module.exports = Hem diff --git a/src/package.coffee b/src/package.coffee index 68a4b29..37830ec 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -13,7 +13,8 @@ versioning = require('./versioning') # ------- Application Class class Application - constructor: (name, config = {}) -> + constructor: (name, config = {}, hem) -> + @hem = hem @name = name @route = config.route @root = config.root @@ -99,7 +100,7 @@ class Application log("Watching application: #{@name}") dirs = (pkg.watch() for pkg in @packages) # make sure dirs has valid values - if dirs.length + if dirs.length log.info("- Watching directories: #{dirs}") else log.info("- No directories to watch...") @@ -109,7 +110,7 @@ class Application log("Versioning application: #{@name}") if @versioning @versioning.update() - else + else log.errorAndExit "ERROR: Versioning not enabled in slug.json" applyRootDir: (value) -> @@ -128,7 +129,7 @@ class Application utils.cleanRoute.apply(utils, values) # ------- Package Classes - + class Package constructor: (app, config) -> @@ -183,7 +184,7 @@ class Package unlink: -> if fs.existsSync(@target) log.info "- removing #{@target}" - fs.unlinkSync(@target) + fs.unlinkSync(@target) build: (write = true) -> extra = (argv.compress and " --using compression") or "" @@ -192,7 +193,7 @@ class Package if source and write dirname = path.dirname(@target) fs.mkdirsSync(dirname) unless fs.existsSync(dirname) - fs.writeFileSync(@target, source) + fs.writeFileSync(@target, source) source watch: -> @@ -225,7 +226,7 @@ class JsPackage extends Package constructor: (app, config) -> # call parent super(app, config) - + # javascript only configurations @commonjs = config.commonjs or 'require' @libs = @app.applyRootDir(config.libs or []) @@ -244,8 +245,8 @@ class JsPackage extends Package # TODO use detective to load only those modules that are required ("required")? # or use the modules [] to specifiy which modules if any to load? or - # set to false to never load any node_modules even if they are required in - # javascript files. Would have to determine files needed from the stitched + # set to false to never load any node_modules even if they are required in + # javascript files. Would have to determine files needed from the stitched # files first... @depend or= new Dependency(@modules) @@ -261,7 +262,7 @@ class JsPackage extends Package # TODO: need to perform similar operation as stitch in that only # compilable code is used... - # check if folder or file + # check if folder or file results = [] for file in files # treat as normal javascript @@ -287,12 +288,71 @@ class JsPackage extends Package class TestPackage extends JsPackage - constructor: (app, config) -> - super(app, config) - # TODO: use after in default spine json to setup specs... - # TODO: testLibs = ['jasmine'] or ['test/public/lib'] - @depends = utils.toArray(config.depends) - @runner = config.runner + constructor: (app, config) -> + super(app, config) + # test configurations + @depends = utils.toArray(config.depends) + @runner = config.runner + @framework = config.framework + + # get test home directory based on target file location + @testHome = path.dirname(@target) + + build: -> + @createTestFiles() + super() + + getAllTestTargets: -> + targets = [] + homeRoute = path.dirname(@route) + + # first get dependencies + for dep in @depends + for depapp in @app.hem.apps when depapp.name is dep + for pkg in depapp.packages + if pkg.constructor.name is "JsPackage" + url = path.relative(homeRoute, pkg.route) + pth = path.relative(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + + # get app targets + for pkg in @app.packages + if pkg.constructor.name is "JsPackage" + url = path.relative(homeRoute, pkg.route) + pth = path.relative(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + + # finally test file + url = path.relative(homeRoute, pkg.route) + pth = path.relative(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + targets + + getFrameworkFiles: -> + targets = [] + frameworkPath = path.resolve(__dirname, "../assets/testing/#{@framework}") + for file in fs.readdirSync(frameworkPath) + if path.extname(file) in [".js",".css"] + url = "#{@framework}/#{file}" + targets.push({ url: url, path: url }) + targets + + + createTestFiles: -> + # create index file + indexFile = path.resolve(@testHome,'index.html') + files = [] + files.push.apply(files, @getFrameworkFiles()) + files.push.apply(files, @getAllTestTargets()) + template = utils.tmpl("testing/index", { commonjs: @commonjs, files: files } ) + fs.outputFileSync(indexFile, template) + + # copy the framework files if they aren't present + frameworkPath = path.resolve(__dirname, "../assets/testing/#{@framework}") + for file in fs.readdirSync(frameworkPath) + if path.extname(file) in [".js",".css"] + filepath = path.resolve(@testHome, "#{@framework}/#{file}") + utils.copyFile(path.resolve(frameworkPath, file), filepath) class CssPackage extends Package @@ -300,7 +360,7 @@ class CssPackage extends Package super(app, config) compile: () -> - try + try output = [] # helper function to perform compiles @@ -319,7 +379,7 @@ class CssPackage extends Package else output.push requireCss(fileOrDir) - # join and minify + # join and minify result = output.join("\n") result = uglifycss.processString(result) if argv.compress result @@ -330,9 +390,9 @@ class CssPackage extends Package # ------- Public Functions -createApplication = (name, config) -> - return new Application(name, config) +createApplication = (name, config, hem) -> + return new Application(name, config, hem) module.exports.createApplication = createApplication - + diff --git a/src/test.coffee b/src/test.coffee index 5dcdf92..fd88d5a 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -3,22 +3,21 @@ utils = require('./utils') # ------- Public Functions - # TODO: only ONE app at a time!!! - run = (apps, options = {}) -> - # TODO: is karam avaliable, use that, + # TODO: is karam avaliable, use that, copy code from compilers # - fall back to phantomjs # - otherwise just open file in browser?? - runKarma(apps, options) + # probably need to loop over apps and run karma for each?? + runKarma(app, options) for app in apps # ------- Test Functions -runPhantomjs = (apps, options = {}) -> +runPhantomjs = (app, options = {}) -> # look at https://github.com/sgentle/phantomjs-node # could spin up phantomjs and evaulate rendered page? -runKarma = (apps, options = {}) -> +runKarma = (app, options = {}) -> # use custom testacular config file provided by user testConfig = fs.existsSync(options.config) and fs.realpathSync(options.config) @@ -28,18 +27,15 @@ runKarma = (apps, options = {}) -> basePath : options.basePath reporters : ['progress'] logLevel : 'info' - frameworks : ['jasmine'] + frameworks : [options.framework] browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] - files : createKarmaFileList(apps) - + files : createKarmaFileList(app) + # start testacular server require('karma').server.start(testConfig) -createKarmaFileList = (apps) -> - for app in apps - return [app.test.target, app.js.target] - # TODO: need to add special js to load the specs in... should be a part of the jasmine type during builds, either that - # or don't use stitch on jasmine files, just concat.. +createKarmaFileList = (app) -> + # get the test package and return the appropiate file list # ------- Exports From 279d53ba2502bdfa86737cd31723e1c96cae0014 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 22:34:07 -0500 Subject: [PATCH 107/167] updates assets for jasmine, eventually will have a file that lists the order the jasmine files should be loaded in. --- assets/defaults/spine.json | 44 ++++++++--------- assets/testing/index.tmpl | 47 ++++++------------- .../jasmine/{jasmine.js => 1.jasmine.js} | 0 .../{jasmine-html.js => 2.jasmine-html.js} | 0 4 files changed, 34 insertions(+), 57 deletions(-) rename assets/testing/jasmine/{jasmine.js => 1.jasmine.js} (100%) rename assets/testing/jasmine/{jasmine-html.js => 2.jasmine-html.js} (100%) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index f4d1e07..bef9c49 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -1,8 +1,8 @@ { - "root": "", + "root": "", "route": "/", - "version": { + "version": { "type": "package", "files": [ "public/index.html" ] }, @@ -10,53 +10,49 @@ "css": { "src": [ "css" - ], + ], "target": "public/application.css" }, "js": { - "commonjs": "require", + "commonjs": "require", "libs": [ "lib" - ], + ], "modules": [ - "jqueryify", - "spine", - "spine/lib/local", - "spine/lib/list", - "spine/lib/ajax", - "spine/lib/route", - "spine/lib/manager", + "jqueryify", + "spine", + "spine/lib/local", + "spine/lib/list", + "spine/lib/ajax", + "spine/lib/route", + "spine/lib/manager", "spine/lib/relation" - ], + ], "src": [ "app" - ], + ], "target": "public/application.js" }, "static": { - "/": "public", + "/": "public", "/test": "test/public" }, "test": { "commonjs": "specs", "libs": [], + "depends": [], "src": [ "test/specs" - ], + ], "target": "test/public/specs.js", "after": [ - "require('lib/setup');", - "for (var key in specs.modules) specs(key);" - ], - "depends": [ - "#jasmine", - "common.js", - "js" + "require('lib/setup');" ], - "runner": "karma" + "runner": "karma", + "framework": "jasmine" } } diff --git a/assets/testing/index.tmpl b/assets/testing/index.tmpl index c144d25..cb3ed2f 100644 --- a/assets/testing/index.tmpl +++ b/assets/testing/index.tmpl @@ -11,7 +11,7 @@ function startTests() { // load in specs from specs.js file - for (var key in specs.modules) { specs(key); } + for (var key in <%=commonjs%>.modules) { <%=commonjs%>(key); } // create jasmine environment var jasmineEnv = jasmine.getEnv(); @@ -36,28 +36,23 @@ } window.onload = function() { + var http = (location.protocol === "http:") + var urlsToLoad = [] var filesToLoad = [] - // dynamically load the information we need - // 1) framework -> jasmine or mocha - // 2) pull jasmine from hem assets, along with phantomjs - // 3) but move phantom runner to hem? add runner parameter to argsv - // 4) need api to return file list - // 5) write index.html file and jasmine files once if not present - - // then framework files - filesToLoad.push("/segway/feedlink/test/lib/jasmine.css") - filesToLoad.push("lib/jasmine.js") - filesToLoad.push("lib/jasmine-html.js") - // then project files - filesToLoad.push("../../common/application.js") - filesToLoad.push("../feedlink.js") - // finally test files - filesToLoad.push("specs.js") + // files to load + <% for (var i = 0; i < files.length; i++) { %> + urlsToLoad.push("<%= files[i].url %>") + filesToLoad.push("<%= files[i].path %>") + <% } %> + // determine if using http to load files + var files = http ? urlsToLoad : filesToLoad + + // function to create elements to load css/js var loadCssOrScript = function(position) { // start tests if everything loaded - if (position >= filesToLoad.length) { + if (position >= files.length) { startTests(); return; } @@ -65,7 +60,7 @@ // create document elements var head = document.getElementsByTagName("HEAD").item(0); var child; - var source = filesToLoad[position]; + var source = files[position]; // handle js if (source.indexOf(".js") > -1) { @@ -101,22 +96,8 @@ loadCssOrScript(0) }; - function ajaxJSONGet(url, callback){ - var http_request = new XMLHttpRequest(); - http_request.open("GET", url, true); - http_request.onreadystatechange = function () { - var done = 4; - var ok = 200; - if (http_request.readyState === done && http_request.status === ok){ - callback(JSON.parse(http_request.responseText)); - } - }; - http_request.send(); - } - })(); -{{= hmm }} diff --git a/assets/testing/jasmine/jasmine.js b/assets/testing/jasmine/1.jasmine.js similarity index 100% rename from assets/testing/jasmine/jasmine.js rename to assets/testing/jasmine/1.jasmine.js diff --git a/assets/testing/jasmine/jasmine-html.js b/assets/testing/jasmine/2.jasmine-html.js similarity index 100% rename from assets/testing/jasmine/jasmine-html.js rename to assets/testing/jasmine/2.jasmine-html.js From c7109babce9ce9dc3a8e624986a660c59a2b372b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 8 Oct 2013 22:34:18 -0500 Subject: [PATCH 108/167] new compiled js files --- lib/compilers.js | 4 ++ lib/hem.js | 32 ++------------ lib/package.js | 109 ++++++++++++++++++++++++++++++++++++++++++++--- lib/stitch.js | 14 ++---- lib/test.js | 24 +++++------ lib/utils.js | 41 +++++++++++++++++- 6 files changed, 167 insertions(+), 57 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index ff6f6a2..e9ceb12 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -53,6 +53,10 @@ return module._compile(compilers.html(filename), filename); }; + require.extensions['.tmpl'] = function(module, filename) { + return module._compile(compilers.html(filename), filename); + }; + cs = require('coffee-script'); compilers.coffee = function(_path) { diff --git a/lib/hem.js b/lib/hem.js index cbeb5d7..cdb2a87 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -97,7 +97,7 @@ if (name === "hem") { continue; } - this.apps.push(application.createApplication(name, config)); + this.apps.push(application.createApplication(name, config, this)); } } @@ -149,10 +149,9 @@ }; Hem.prototype.test = function() { - var targets, testOptions; - targets = argv.targets; + var testOptions; testOptions = { - basePath: this.homeDir + basePath: this.home }; if (argv.watch) { this.watch(); @@ -161,7 +160,7 @@ this.buildApps(); testOptions.singleRun = true; } - return this.testTargets(targets, testOptions); + return testing.run(this.apps, options); }; Hem.prototype.check = function() { @@ -245,29 +244,6 @@ return _results; }; - Hem.prototype.testTargets = function(targets, options) { - var app, testApps; - if (targets == null) { - targets = []; - } - if (options == null) { - options = {}; - } - testApps = (function() { - var _i, _len, _ref, _results; - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - if (app.test) { - _results.push(app); - } - } - return _results; - }).call(this); - return testing.run(testApps, options); - }; - return Hem; })(); diff --git a/lib/package.js b/lib/package.js index ca8c4a6..aaa534d 100644 --- a/lib/package.js +++ b/lib/package.js @@ -5,7 +5,7 @@ __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - fs = require('fs'); + fs = require('fs-extra'); path = require('path'); @@ -26,11 +26,12 @@ versioning = require('./versioning'); Application = (function() { - function Application(name, config) { + function Application(name, config, hem) { var defaults, err, key, loadedDefaults, packager, route, value, verType, _ref; if (config == null) { config = {}; } + this.hem = hem; this.name = name; this.route = config.route; this.root = config.root; @@ -258,7 +259,7 @@ if (source && write) { dirname = path.dirname(this.target); if (!fs.existsSync(dirname)) { - require('mkdirp').sync(dirname); + fs.mkdirsSync(dirname); } fs.writeFileSync(this.target, source); } @@ -394,8 +395,106 @@ TestPackage.__super__.constructor.call(this, app, config); this.depends = utils.toArray(config.depends); this.runner = config.runner; + this.framework = config.framework; + this.testHome = path.dirname(this.target); } + TestPackage.prototype.build = function() { + this.createTestFiles(); + return TestPackage.__super__.build.call(this); + }; + + TestPackage.prototype.getAllTestTargets = function() { + var dep, depapp, homeRoute, pkg, pth, targets, url, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3; + targets = []; + homeRoute = path.dirname(this.route); + _ref = this.depends; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + dep = _ref[_i]; + _ref1 = this.app.hem.apps; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + depapp = _ref1[_j]; + if (depapp.name === dep) { + _ref2 = depapp.packages; + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + pkg = _ref2[_k]; + if (pkg.constructor.name === "JsPackage") { + url = path.relative(homeRoute, pkg.route); + pth = path.relative(this.testHome, pkg.target); + targets.push({ + url: url, + path: pth + }); + } + } + } + } + } + _ref3 = this.app.packages; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + pkg = _ref3[_l]; + if (pkg.constructor.name === "JsPackage") { + url = path.relative(homeRoute, pkg.route); + pth = path.relative(this.testHome, pkg.target); + targets.push({ + url: url, + path: pth + }); + } + } + url = path.relative(homeRoute, pkg.route); + pth = path.relative(this.testHome, pkg.target); + targets.push({ + url: url, + path: pth + }); + return targets; + }; + + TestPackage.prototype.getFrameworkFiles = function() { + var file, frameworkPath, targets, url, _i, _len, _ref, _ref1; + targets = []; + frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); + _ref = fs.readdirSync(frameworkPath); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if ((_ref1 = path.extname(file)) === ".js" || _ref1 === ".css") { + url = "" + this.framework + "/" + file; + targets.push({ + url: url, + path: url + }); + } + } + return targets; + }; + + TestPackage.prototype.createTestFiles = function() { + var file, filepath, files, frameworkPath, indexFile, template, _i, _len, _ref, _ref1, _results; + indexFile = path.resolve(this.testHome, 'index.html'); + files = []; + files.push.apply(files, this.getFrameworkFiles()); + files.push.apply(files, this.getAllTestTargets()); + template = utils.tmpl("testing/index", { + commonjs: this.commonjs, + files: files + }); + fs.outputFileSync(indexFile, template); + frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); + _ref = fs.readdirSync(frameworkPath); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if ((_ref1 = path.extname(file)) === ".js" || _ref1 === ".css") { + filepath = path.resolve(this.testHome, "" + this.framework + "/" + file); + _results.push(utils.copyFile(path.resolve(frameworkPath, file), filepath)); + } else { + _results.push(void 0); + } + } + return _results; + }; + return TestPackage; })(JsPackage); @@ -450,8 +549,8 @@ })(Package); - createApplication = function(name, config) { - return new Application(name, config); + createApplication = function(name, config, hem) { + return new Application(name, config, hem); }; module.exports.createApplication = createApplication; diff --git a/lib/stitch.js b/lib/stitch.js index 0c29109..ac19a53 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -71,16 +71,10 @@ }; Stitch.prototype.template = function(identifier, modules) { - var module; - return "(function(/*! Stitch !*/) {\n if (!this." + identifier + ") {\n var modules = {}, cache = {}, require = function(name, root) {\n var path = expand(root, name), indexPath = expand(path, './index'), module, fn;\n module = cache[path] || cache[indexPath]\n if (module) {\n return module.exports;\n } else if (fn = modules[path] || modules[path = indexPath]) {\n module = {id: path, exports: {}};\n try {\n cache[path] = module;\n fn(module.exports, function(name) {\n return require(name, dirname(path));\n }, module);\n return module.exports;\n } catch (err) {\n delete cache[path];\n throw err;\n }\n } else {\n throw 'module ' + name + ' not found';\n }\n }, expand = function(root, name) {\n var results = [], parts, part;\n if (/^\.\.?(\\/|$)/.test(name)) {\n parts = [root, name].join('/').split('/');\n } else {\n parts = name.split('/');\n }\n for (var i = 0, length = parts.length; i < length; i++) {\n part = parts[i];\n if (part == '..') {\n results.pop();\n } else if (part != '.' && part != '') {\n results.push(part);\n }\n }\n return results.join('/');\n }, dirname = function(path) {\n return path.split('/').slice(0, -1).join('/');\n };\n this." + identifier + " = function(name) {\n return require(name, '');\n }\n this." + identifier + ".define = function(bundle) {\n for (var key in bundle)\n modules[key] = bundle[key];\n };\n this." + identifier + ".modules = modules;\n this." + identifier + ".cache = cache;\n }\n return this." + identifier + ".define;\n}).call(this)({\n " + (((function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = modules.length; _i < _len; _i++) { - module = modules[_i]; - _results.push(JSON.stringify(module.id) + (": function(exports, require, module) {" + (module.compile()) + "}")); - } - return _results; - })()).join(', ')) + "\n});"; + return require('./utils').tmpl("stitch", { + identifier: identifier, + modules: modules + }); }; return Stitch; diff --git a/lib/test.js b/lib/test.js index 695f7b3..4501cd8 100644 --- a/lib/test.js +++ b/lib/test.js @@ -7,19 +7,25 @@ utils = require('./utils'); run = function(apps, options) { + var app, _i, _len, _results; if (options == null) { options = {}; } - return runKarma(apps, options); + _results = []; + for (_i = 0, _len = apps.length; _i < _len; _i++) { + app = apps[_i]; + _results.push(runKarma(app, options)); + } + return _results; }; - runPhantomjs = function(apps, options) { + runPhantomjs = function(app, options) { if (options == null) { options = {}; } }; - runKarma = function(apps, options) { + runKarma = function(app, options) { var testConfig; if (options == null) { options = {}; @@ -30,20 +36,14 @@ basePath: options.basePath, reporters: ['progress'], logLevel: 'info', - frameworks: ['jasmine'], + frameworks: [options.framework], browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], - files: createKarmaFileList(apps) + files: createKarmaFileList(app) }); return require('karma').server.start(testConfig); }; - createKarmaFileList = function(apps) { - var app, _i, _len; - for (_i = 0, _len = apps.length; _i < _len; _i++) { - app = apps[_i]; - return [app.test.target, app.js.target]; - } - }; + createKarmaFileList = function(app) {}; module.exports.run = run; diff --git a/lib/utils.js b/lib/utils.js index 1a0b10c..6709d6e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,12 +1,12 @@ // Generated by CoffeeScript 1.6.3 (function() { - var clean, events, extend, flatten, fs, isWin, path, utils, + var clean, events, extend, flatten, fs, isWin, path, tmplCache, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; path = require('path'); - fs = require('fs'); + fs = require('fs-extra'); events = require('events'); @@ -95,6 +95,23 @@ return require("../assets/" + asset); }; + utils.copyFile = function(from, to) { + var BUF_LENGTH, bytesRead, fdr, fdw, pos, _buff; + BUF_LENGTH = 64 * 1024; + _buff = new Buffer(BUF_LENGTH); + fdr = fs.openSync(from, 'r'); + fdw = fs.openSync(to, 'w'); + bytesRead = 1; + pos = 0; + while (bytesRead > 0) { + bytesRead = fs.readSync(fdr, _buff, 0, BUF_LENGTH, pos); + fs.writeSync(fdw, _buff, 0, bytesRead); + pos += bytesRead; + } + fs.closeSync(fdr); + return fs.closeSync(fdw); + }; + utils.isDirectory = function(dir) { var e, stats; try { @@ -106,6 +123,26 @@ } }; + tmplCache = {}; + + utils.tmpl = function(str, data) { + var fn, template; + if (!/[\t\r\n% ]/.test(str)) { + if (tmplCache[str]) { + fn = tmplCache[str]; + } else { + template = utils.loadAsset("" + str + ".tmpl"); + fn = utils.tmpl(template); + } + } else { + str = str.split("'").join("\\'").split("\n").join("\\n").replace(/<%([\s\S]*?)%>/mg, function(m, t) { + return '<%' + t.split("\\'").join("'").split("\\n").join("\n") + '%>'; + }).replace(/<%=(.+?)%>/g, "',$1,'").split("<%").join("');").split("%>").join("p.push('"); + fn = new Function("obj", "var p=[]\nvar print = function(){ p.push.apply(p,arguments); };\nwith(obj){\n p.push('" + str + "');\n}\nreturn p.join('');"); + } + return data && fn(data) || fn; + }; + utils.events = new events.EventEmitter(); clean = function(values, sep, trimStart) { From d8c39b2c0cc947bb7e483f01fe9ef4126d8a38e0 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 9 Oct 2013 01:54:37 -0500 Subject: [PATCH 109/167] incorporate phantom runner into hem project --- lib/hem.js | 2 +- lib/package.js | 11 +++ lib/phantom.js | 179 +++++++++++++++++++++++++++++++++++++++++++++ lib/test.js | 11 ++- lib/utils.js | 1 + package.json | 3 +- src/hem.coffee | 4 +- src/package.coffee | 4 + src/phantom.coffee | 146 ++++++++++++++++++++++++++++++++++++ src/test.coffee | 9 ++- src/utils.coffee | 4 + 11 files changed, 362 insertions(+), 12 deletions(-) create mode 100644 lib/phantom.js create mode 100644 src/phantom.coffee diff --git a/lib/hem.js b/lib/hem.js index cdb2a87..15962fd 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -160,7 +160,7 @@ this.buildApps(); testOptions.singleRun = true; } - return testing.run(this.apps, options); + return testing.run(this.apps, this.options); }; Hem.prototype.check = function() { diff --git a/lib/package.js b/lib/package.js index aaa534d..d7eadc2 100644 --- a/lib/package.js +++ b/lib/package.js @@ -92,6 +92,17 @@ } } + Application.prototype.getTestPackage = function() { + var pkg, _i, _len, _ref; + _ref = this.packages; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pkg = _ref[_i]; + if (pkg.constructor.name === "TestPackage") { + return pkg; + } + } + }; + Application.prototype.isMatchingRoute = function(route) { var pkg, _i, _len, _ref; if (this.versioning) { diff --git a/lib/phantom.js b/lib/phantom.js new file mode 100644 index 0000000..3ff016e --- /dev/null +++ b/lib/phantom.js @@ -0,0 +1,179 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var phantom; + + phantom = require('phantom'); + + module.exports = function(filepath) { + return phantom.create(function(ph) { + var waitFor; + waitFor = (function() { + var getTime; + getTime = function() { + return (new Date).getTime(); + }; + return function(test, doIt, duration) { + var finish, int, looop, start; + duration || (duration = 10000); + start = getTime(); + finish = start + duration; + int = void 0; + looop = function() { + var testCallback, time, timeout; + time = getTime(); + timeout = time >= finish; + testCallback = function(condition) { + if (condition) { + clearInterval(int); + doIt(time - start); + } + if (timeout && !condition) { + console.log("ERROR - Timeout for page condition."); + clearInterval(int); + return ph.exit(); + } + }; + return test(testCallback); + }; + return int = setInterval(looop, 1000); + }; + })(); + return ph.createPage(function(page) { + var _page; + _page = page; + _page.set('onConsoleMessage', function(msg) { + return console.log(msg); + }); + _page.set('onCallback', function(msg) { + if (msg) { + return console.log(msg); + } + }); + return _page.open(filepath, function(status) { + var check, evalTests; + if (status !== "success") { + console.log("Cannot open URL"); + ph.exit(); + } + check = function(callback) { + return _page.evaluate(function() { + var _ref; + return (_ref = document.querySelector(".duration")) != null ? _ref.innerText : void 0; + }, callback); + }; + evalTests = function(time) { + return _page.evaluate(function(formatter) { + var errorsOnly, ex, fails, format, formatColors, passed, printSpecs, printSuites; + formatColors = (function() { + var desc, indent, tick; + indent = function(level) { + var i, ret, _i; + ret = ''; + for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { + ret = ret + ' '; + } + return ret; + }; + tick = function(el) { + if ($(el).is('.passed')) { + return '\x1B[32m✓\x1B[0m'; + } else { + return '\x1B[31m✖'; + } + }; + desc = function(el, strong) { + var ret; + strong || (strong = false); + ret = $(el).find('> a.description'); + return ret = strong && '\x1B[1m' + ret[0].text || ret[0].text; + }; + return function(el, level, strong) { + var results; + if (typeof el === 'number') { + results = "-------------------------------------\n"; + results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el; + if (level > 0) { + results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level; + } + return results; + } else { + return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong); + } + }; + })(); + errorsOnly = (function() { + var desc, indent, tick; + indent = function(level) { + var i, ret, _i; + ret = ''; + for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { + ret = ret + ' '; + } + return ret; + }; + desc = function(el) { + return $(el).find('> a.description')[0].text; + }; + tick = function(el) { + var _ref; + return (_ref = $(el).is('.passed')) != null ? _ref : { + '✓ ': '✖ ' + }; + }; + return function(el, level, strong) { + if (typeof el === 'number') { + return "Passed: " + el + ", Failed: " + level; + } else { + if (!$(el).is(".passed")) { + return indent(level) + tick(el) + desc(el); + } else { + return null; + } + } + }; + })(); + try { + format = eval(formatter || "formatColors"); + } catch (_error) { + ex = _error; + format = formatColors; + } + printSuites = function(root, level) { + level || (level = 0); + return $(root).find('div.suite').each(function(i, el) { + var output; + output = format(el, level, true); + if ($(el).parents('div.suite').length === level) { + if (output) { + window.callPhantom(output); + } + printSpecs(el, level + 1); + } + return printSuites(el, level + 1); + }); + }; + printSpecs = function(root, level) { + level || (level = 0); + return $(root).find('> .specSummary').each(function(i, el) { + var output; + output = format(el, level); + if (output) { + return window.callPhantom(output); + } + }); + }; + printSuites($('div.jasmine_reporter')); + fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed'); + passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed'); + return window.callPhantom(format(passed.length, fails.length)); + }, function() { + return ph.exit(); + }); + }; + return waitFor(check, evalTests); + }); + }); + }); + }; + +}).call(this); diff --git a/lib/test.js b/lib/test.js index 4501cd8..dd39bd4 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,11 +1,13 @@ // Generated by CoffeeScript 1.6.3 (function() { - var createKarmaFileList, fs, run, runKarma, runPhantomjs, utils; + var createKarmaFileList, fs, path, run, runKarma, runPhantom, utils; fs = require('fs'); utils = require('./utils'); + path = require('path'); + run = function(apps, options) { var app, _i, _len, _results; if (options == null) { @@ -14,15 +16,18 @@ _results = []; for (_i = 0, _len = apps.length; _i < _len; _i++) { app = apps[_i]; - _results.push(runKarma(app, options)); + if (app.name !== 'common') { + _results.push(runPhantom(app, options)); + } } return _results; }; - runPhantomjs = function(app, options) { + runPhantom = function(app, options) { if (options == null) { options = {}; } + return require('./phantom')(path.resolve(app.getTestPackage().testHome, 'index.html')); }; runKarma = function(app, options) { diff --git a/lib/utils.js b/lib/utils.js index 6709d6e..9b34312 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -97,6 +97,7 @@ utils.copyFile = function(from, to) { var BUF_LENGTH, bytesRead, fdr, fdw, pos, _buff; + fs.createFileSync(to); BUF_LENGTH = 64 * 1024; _buff = new Buffer(BUF_LENGTH); fdr = fs.openSync(from, 'r'); diff --git a/package.json b/package.json index cc9c5bc..b92ae52 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "sty": "~0.6.1", "optimist": "~0.6.0", "watch": "~0.8.0", - "fs-extra": "~0.7.0" + "fs-extra": "~0.7.0", + "phantom": "~0.5.2" } } diff --git a/src/hem.coffee b/src/hem.coffee index d65d2a8..96813b5 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -142,7 +142,7 @@ class Hem @buildApps() testOptions.singleRun = true # run tests - testing.run(@apps, options) + testing.run(@apps, @options) check: -> printOptions = showHidden: false, colors: !argv.nocolors, depth: null @@ -160,10 +160,8 @@ class Hem exec: (command = argv.command) -> # handle empty arguments return help() unless @[command] - # reset the apps list based on command line args @apps = @getTargetApps() - # hope this works :o) @[command]() diff --git a/src/package.coffee b/src/package.coffee index 37830ec..760da0e 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -78,6 +78,10 @@ class Application log.errorAndExit "Incorrect type value for version configuration: (#{config.version.type})" @versioning = new verType(@, config.version) + getTestPackage: -> + for pkg in @packages + return pkg if pkg.constructor.name is "TestPackage" + isMatchingRoute: (route) -> # strip out any versioning applied to file if @versioning diff --git a/src/phantom.coffee b/src/phantom.coffee new file mode 100644 index 0000000..0ff2805 --- /dev/null +++ b/src/phantom.coffee @@ -0,0 +1,146 @@ +phantom = require 'phantom' + + +module.exports = (filepath) -> + phantom.create (ph) -> + + # wait for method used in page evaluation + waitFor = (-> + getTime = -> (new Date).getTime() + + return (test, doIt, duration) -> + duration or= 10000 + start = getTime() + finish = start + duration + int = undefined + # looop function to call using setInterval + looop = -> + time = getTime() + timeout = (time >= finish) + # method to process evaluation results + testCallback = (condition) -> + # No more time or condition fulfilled + if condition + clearInterval(int) + doIt(time - start) + # THEN, no moretime but condition unfulfilled + if timeout and not condition + console.log("ERROR - Timeout for page condition.") + clearInterval(int) + ph.exit() + # perform the test evaluation + test(testCallback) + # the intervale number + int = setInterval(looop, 1000 ) + )() + + ph.createPage (page) -> + + _page = page + + # print console.log output from the webpage + _page.set('onConsoleMessage', (msg) -> console.log(msg)) + + # page callback, kind of a hackish way to only allow our phantom + # script to make use of console.log so we only see test results. + _page.set('onCallback', (msg) -> console.log(msg) if msg) + + # open the filepath and beging tests + _page.open filepath, (status) -> + if status isnt "success" + console.log("Cannot open URL") + ph.exit() + + check = (callback) -> + _page.evaluate( + -> document.querySelector(".duration")?.innerText + , + callback + ) + + evalTests = (time) -> + _page.evaluate( (formatter) -> + + formatColors = (-> + indent = (level) -> + ret = '' + for i in [0..level] + ret = ret + ' ' + return ret + + tick = (el) -> + return if $(el).is('.passed') then '\x1B[32m✓\x1B[0m' else '\x1B[31m✖' + + desc = (el, strong) -> + strong or= false + ret = $(el).find('> a.description') + ret = strong and '\x1B[1m' + ret[0].text or ret[0].text + + return (el, level, strong) -> + if typeof el is 'number' + results= "-------------------------------------\n" + results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el + if level > 0 + results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level + return results + else + return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong) + )() + + errorsOnly = (-> + indent = (level) -> + ret = '' + for i in [0..level] + ret = ret + ' ' + return ret + + desc = (el) -> + $(el).find('> a.description')[0].text + + tick = (el) -> $(el).is('.passed') ? '✓ ' : '✖ ' + + return (el, level, strong) -> + if typeof el is 'number' + return "Passed: " + el + ", Failed: " + level + else + if (!$(el).is(".passed")) + return indent(level) + tick(el) + desc(el) + else + return null + )() + + # ability to request different type of outputs, default to formatColors + try + format = eval(formatter or "formatColors") + catch ex + format = formatColors + + printSuites = (root, level) -> + level or= 0 + $(root).find('div.suite').each( (i, el) -> + output = format(el, level, true) + if $(el).parents('div.suite').length is level + window.callPhantom(output) if output + printSpecs(el, level + 1) + printSuites(el, level + 1) + ) + + printSpecs = (root, level) -> + level or= 0 + $(root).find('> .specSummary').each( (i, el) -> + output = format(el, level) + window.callPhantom(output) if output + ) + + printSuites($('div.jasmine_reporter')) + + # handle fails + fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed') + passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed') + window.callPhantom(format(passed.length, fails.length)) + + , -> ph.exit() ) + + waitFor(check, evalTests) + + diff --git a/src/test.coffee b/src/test.coffee index fd88d5a..a66a357 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -1,5 +1,6 @@ fs = require('fs') utils = require('./utils') +path = require('path') # ------- Public Functions @@ -9,13 +10,13 @@ run = (apps, options = {}) -> # - otherwise just open file in browser?? # probably need to loop over apps and run karma for each?? - runKarma(app, options) for app in apps + runPhantom(app, options) for app in apps when app.name isnt 'common' # ------- Test Functions -runPhantomjs = (app, options = {}) -> - # look at https://github.com/sgentle/phantomjs-node - # could spin up phantomjs and evaulate rendered page? +runPhantom = (app, options = {}) -> + # TODO need to do this in steps if we have multiple apps + require('./phantom')(path.resolve(app.getTestPackage().testHome,'index.html')) runKarma = (app, options = {}) -> # use custom testacular config file provided by user diff --git a/src/utils.coffee b/src/utils.coffee index 07026fd..298db4f 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -52,8 +52,12 @@ utils.loadAsset = (asset) -> require("../assets/" + asset) utils.copyFile = (from, to) -> + # make sure target files exists + fs.createFileSync(to) + # constants BUF_LENGTH = 64 * 1024 _buff = new Buffer(BUF_LENGTH) + # perform copy fdr = fs.openSync(from, 'r') fdw = fs.openSync(to, 'w') bytesRead = 1 From 62e5a8cadcf09b4372270bc6133beb1a62de2273 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 9 Oct 2013 10:22:38 -0500 Subject: [PATCH 110/167] use all apps when finding test dependencies as sometimes there may only be one target --- lib/hem.js | 6 ++++-- lib/package.js | 2 +- src/hem.coffee | 5 +++-- src/package.coffee | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 15962fd..74ad72a 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -68,6 +68,8 @@ Hem.prototype.apps = []; + Hem.prototype.allApps = []; + Hem.prototype.home = process.cwd(); function Hem(options) { @@ -97,7 +99,7 @@ if (name === "hem") { continue; } - this.apps.push(application.createApplication(name, config, this)); + this.allApps.push(application.createApplication(name, config, this)); } } @@ -222,7 +224,7 @@ targets = argv.targets; } targetAll = targets.length === 0; - _ref = this.apps; + _ref = this.allApps; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { app = _ref[_i]; diff --git a/lib/package.js b/lib/package.js index d7eadc2..4d85d5d 100644 --- a/lib/package.js +++ b/lib/package.js @@ -422,7 +422,7 @@ _ref = this.depends; for (_i = 0, _len = _ref.length; _i < _len; _i++) { dep = _ref[_i]; - _ref1 = this.app.hem.apps; + _ref1 = this.app.hem.allApps; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { depapp = _ref1[_j]; if (depapp.name === dep) { diff --git a/src/hem.coffee b/src/hem.coffee index 96813b5..ba0d2db 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -78,6 +78,7 @@ class Hem # emtpy options map and applications list options : {} apps : [] + allApps : [] home : process.cwd() # ------- Constructor @@ -106,7 +107,7 @@ class Hem # setup applications from options/slug for name, config of @options continue if name is "hem" - @apps.push application.createApplication(name, config, @) + @allApps.push application.createApplication(name, config, @) # ------- Command Functions @@ -188,7 +189,7 @@ class Hem getTargetApps: (targets = argv.targets) -> targetAll = targets.length is 0 - (app for app in @apps when app.name in targets or targetAll) + (app for app in @allApps when app.name in targets or targetAll) buildApps: () -> app.build() for app in @apps diff --git a/src/package.coffee b/src/package.coffee index 760da0e..3f1d7e6 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -312,7 +312,7 @@ class TestPackage extends JsPackage # first get dependencies for dep in @depends - for depapp in @app.hem.apps when depapp.name is dep + for depapp in @app.hem.allApps when depapp.name is dep for pkg in depapp.packages if pkg.constructor.name is "JsPackage" url = path.relative(homeRoute, pkg.route) From 045e222944cc90d92834dfeb94a138920fcc51ac Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 9 Oct 2013 16:11:17 -0500 Subject: [PATCH 111/167] tweaks to phantom test runner to allow different reporting styles --- lib/package.js | 6 +- lib/phantom.js | 298 +++++++++++++++++++++++---------------------- lib/test.js | 10 +- src/package.coffee | 52 ++++---- src/phantom.coffee | 256 +++++++++++++++++++------------------- src/test.coffee | 11 +- 6 files changed, 328 insertions(+), 305 deletions(-) diff --git a/lib/package.js b/lib/package.js index 4d85d5d..c8be4b8 100644 --- a/lib/package.js +++ b/lib/package.js @@ -480,9 +480,13 @@ return targets; }; + TestPackage.prototype.getTestIndexFile = function() { + return path.resolve(this.testHome, 'index.html'); + }; + TestPackage.prototype.createTestFiles = function() { var file, filepath, files, frameworkPath, indexFile, template, _i, _len, _ref, _ref1, _results; - indexFile = path.resolve(this.testHome, 'index.html'); + indexFile = this.getTestIndexFile(); files = []; files.push.apply(files, this.getFrameworkFiles()); files.push.apply(files, this.getAllTestTargets()); diff --git a/lib/phantom.js b/lib/phantom.js index 3ff016e..02eaa7b 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -1,174 +1,178 @@ // Generated by CoffeeScript 1.6.3 (function() { - var phantom; + var phantom, reporters, waitFor; phantom = require('phantom'); - module.exports = function(filepath) { - return phantom.create(function(ph) { - var waitFor; - waitFor = (function() { - var getTime; - getTime = function() { - return (new Date).getTime(); - }; - return function(test, doIt, duration) { - var finish, int, looop, start; - duration || (duration = 10000); - start = getTime(); - finish = start + duration; - int = void 0; - looop = function() { - var testCallback, time, timeout; - time = getTime(); - timeout = time >= finish; - testCallback = function(condition) { - if (condition) { - clearInterval(int); - doIt(time - start); - } - if (timeout && !condition) { - console.log("ERROR - Timeout for page condition."); - clearInterval(int); - return ph.exit(); - } - }; - return test(testCallback); - }; - return int = setInterval(looop, 1000); + reporters = { + errorsOnly: function(el, level, strong) { + var desc, indent, tick; + indent = function(level) { + var i, ret, _i; + ret = ''; + for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { + ret = ret + ' '; + } + return ret; + }; + desc = function(el) { + return $(el).find('> a.description')[0].text; + }; + tick = function(el) { + if ($(el).is('.passed')) { + return '✓ '; + } else { + return '✖ '; + } + }; + if (typeof el === 'number') { + return "Passed: " + el + ", Failed: " + level; + } else { + if (!$(el).is(".passed")) { + return indent(level) + tick(el) + desc(el); + } else { + return null; + } + } + }, + passOrFail: function(el, level, strong) { + if (typeof el === 'number') { + return "Passed: " + el + ", Failed: " + level; + } + }, + formatColors: function(el, level, strong) { + var desc, indent, results, tick; + indent = function(level) { + var i, ret, _i; + ret = ''; + for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { + ret = ret + ' '; + } + return ret; + }; + tick = function(el) { + if ($(el).is('.passed')) { + return '\x1B[32m✓\x1B[0m'; + } else { + return '\x1B[31m✖'; + } + }; + desc = function(el, strong) { + var ret; + strong || (strong = false); + ret = $(el).find('> a.description'); + return ret = strong && '\x1B[1m' + ret[0].text || ret[0].text; + }; + if (typeof el === 'number') { + results = "-------------------------------------\n"; + results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el; + if (level > 0) { + results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level; + } + return results; + } else { + return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong); + } + } + }; + + waitFor = (function() { + var getTime; + getTime = function() { + return (new Date).getTime(); + }; + return function(test, doIt, duration) { + var finish, int, looop, start; + duration || (duration = 10000); + start = getTime(); + finish = start + duration; + int = void 0; + looop = function() { + var testCallback, time, timeout; + time = getTime(); + timeout = time >= finish; + testCallback = function(condition) { + if (condition) { + clearInterval(int); + doIt(time - start); + } + if (timeout && !condition) { + console.log("ERROR - Timeout for page condition."); + clearInterval(int); + return ph.exit(); + } }; - })(); + return test(testCallback); + }; + return int = setInterval(looop, 1000); + }; + })(); + + module.exports = function(filepath, reportStyle) { + if (reportStyle == null) { + reportStyle = "formatColors"; + } + return phantom.create(function(ph) { return ph.createPage(function(page) { - var _page; - _page = page; - _page.set('onConsoleMessage', function(msg) { + page.set('onConsoleMessage', function(msg) { return console.log(msg); }); - _page.set('onCallback', function(msg) { + page.set('onCallback', function(msg) { if (msg) { return console.log(msg); } }); - return _page.open(filepath, function(status) { - var check, evalTests; + return page.open(filepath, function(status) { + var check, evalTests, parseTestResults, reporter; if (status !== "success") { console.log("Cannot open URL"); ph.exit(); } check = function(callback) { - return _page.evaluate(function() { + var isTestComplete; + isTestComplete = function() { var _ref; return (_ref = document.querySelector(".duration")) != null ? _ref.innerText : void 0; - }, callback); + }; + return page.evaluate(isTestComplete, callback); }; - evalTests = function(time) { - return _page.evaluate(function(formatter) { - var errorsOnly, ex, fails, format, formatColors, passed, printSpecs, printSuites; - formatColors = (function() { - var desc, indent, tick; - indent = function(level) { - var i, ret, _i; - ret = ''; - for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { - ret = ret + ' '; - } - return ret; - }; - tick = function(el) { - if ($(el).is('.passed')) { - return '\x1B[32m✓\x1B[0m'; - } else { - return '\x1B[31m✖'; - } - }; - desc = function(el, strong) { - var ret; - strong || (strong = false); - ret = $(el).find('> a.description'); - return ret = strong && '\x1B[1m' + ret[0].text || ret[0].text; - }; - return function(el, level, strong) { - var results; - if (typeof el === 'number') { - results = "-------------------------------------\n"; - results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el; - if (level > 0) { - results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level; - } - return results; - } else { - return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong); - } - }; - })(); - errorsOnly = (function() { - var desc, indent, tick; - indent = function(level) { - var i, ret, _i; - ret = ''; - for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { - ret = ret + ' '; - } - return ret; - }; - desc = function(el) { - return $(el).find('> a.description')[0].text; - }; - tick = function(el) { - var _ref; - return (_ref = $(el).is('.passed')) != null ? _ref : { - '✓ ': '✖ ' - }; - }; - return function(el, level, strong) { - if (typeof el === 'number') { - return "Passed: " + el + ", Failed: " + level; - } else { - if (!$(el).is(".passed")) { - return indent(level) + tick(el) + desc(el); - } else { - return null; - } - } - }; - })(); - try { - format = eval(formatter || "formatColors"); - } catch (_error) { - ex = _error; - format = formatColors; - } - printSuites = function(root, level) { - level || (level = 0); - return $(root).find('div.suite').each(function(i, el) { - var output; - output = format(el, level, true); - if ($(el).parents('div.suite').length === level) { - if (output) { - window.callPhantom(output); - } - printSpecs(el, level + 1); - } - return printSuites(el, level + 1); - }); - }; - printSpecs = function(root, level) { - level || (level = 0); - return $(root).find('> .specSummary').each(function(i, el) { - var output; - output = format(el, level); + parseTestResults = function(report) { + var fails, passed, printSpecs, printSuites; + eval("report = " + report); + printSuites = function(root, level) { + level || (level = 0); + return $(root).find('div.suite').each(function(i, el) { + var output; + output = report(el, level, true); + if ($(el).parents('div.suite').length === level) { if (output) { - return window.callPhantom(output); + window.callPhantom(output); } - }); - }; - printSuites($('div.jasmine_reporter')); - fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed'); - passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed'); - return window.callPhantom(format(passed.length, fails.length)); - }, function() { + printSpecs(el, level + 1); + } + return printSuites(el, level + 1); + }); + }; + printSpecs = function(root, level) { + level || (level = 0); + return $(root).find('> .specSummary').each(function(i, el) { + var output; + output = report(el, level); + if (output) { + return window.callPhantom(output); + } + }); + }; + printSuites($('div.jasmine_reporter')); + fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed'); + passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed'); + return window.callPhantom(report(passed.length, fails.length)); + }; + reporter = reporters[reportStyle || "formatColors"]; + evalTests = function(time) { + return page.evaluate(parseTestResults, (function() { return ph.exit(); - }); + }), new String(reporter)); }; return waitFor(check, evalTests); }); diff --git a/lib/test.js b/lib/test.js index dd39bd4..8ce7068 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,12 +1,14 @@ // Generated by CoffeeScript 1.6.3 (function() { - var createKarmaFileList, fs, path, run, runKarma, runPhantom, utils; + var createKarmaFileList, fs, path, phantom, run, runKarma, runPhantom, utils; fs = require('fs'); + path = require('path'); + utils = require('./utils'); - path = require('path'); + phantom = require('./phantom'); run = function(apps, options) { var app, _i, _len, _results; @@ -24,10 +26,12 @@ }; runPhantom = function(app, options) { + var testFile; if (options == null) { options = {}; } - return require('./phantom')(path.resolve(app.getTestPackage().testHome, 'index.html')); + testFile = app.getTestPackage().getTestIndexFile(); + return phantom(testFile, "passOrFail"); }; runKarma = function(app, options) { diff --git a/src/package.coffee b/src/package.coffee index 3f1d7e6..532b5df 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -307,30 +307,30 @@ class TestPackage extends JsPackage super() getAllTestTargets: -> - targets = [] - homeRoute = path.dirname(@route) - - # first get dependencies - for dep in @depends - for depapp in @app.hem.allApps when depapp.name is dep - for pkg in depapp.packages - if pkg.constructor.name is "JsPackage" - url = path.relative(homeRoute, pkg.route) - pth = path.relative(@testHome, pkg.target) - targets.push({ url: url, path: pth }) - - # get app targets - for pkg in @app.packages - if pkg.constructor.name is "JsPackage" - url = path.relative(homeRoute, pkg.route) - pth = path.relative(@testHome, pkg.target) - targets.push({ url: url, path: pth }) - - # finally test file - url = path.relative(homeRoute, pkg.route) - pth = path.relative(@testHome, pkg.target) - targets.push({ url: url, path: pth }) - targets + targets = [] + homeRoute = path.dirname(@route) + + # first get dependencies + for dep in @depends + for depapp in @app.hem.allApps when depapp.name is dep + for pkg in depapp.packages + if pkg.constructor.name is "JsPackage" + url = path.relative(homeRoute, pkg.route) + pth = path.relative(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + + # get app targets + for pkg in @app.packages + if pkg.constructor.name is "JsPackage" + url = path.relative(homeRoute, pkg.route) + pth = path.relative(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + + # finally test file + url = path.relative(homeRoute, pkg.route) + pth = path.relative(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + targets getFrameworkFiles: -> targets = [] @@ -341,10 +341,12 @@ class TestPackage extends JsPackage targets.push({ url: url, path: url }) targets + getTestIndexFile: -> + path.resolve(@testHome,'index.html') createTestFiles: -> # create index file - indexFile = path.resolve(@testHome,'index.html') + indexFile = @getTestIndexFile() files = [] files.push.apply(files, @getFrameworkFiles()) files.push.apply(files, @getAllTestTargets()) diff --git a/src/phantom.coffee b/src/phantom.coffee index 0ff2805..8062a18 100644 --- a/src/phantom.coffee +++ b/src/phantom.coffee @@ -1,146 +1,154 @@ phantom = require 'phantom' +# formatters to display test results + +reporters = + + errorsOnly: (el, level, strong) -> + indent = (level) -> + ret = '' + ret = ret + ' ' for i in [0..level] + ret + + desc = (el) -> $(el).find('> a.description')[0].text + + tick = (el) -> if $(el).is('.passed') then '✓ ' else '✖ ' + + if typeof el is 'number' + return "Passed: " + el + ", Failed: " + level + else + if (!$(el).is(".passed")) + return indent(level) + tick(el) + desc(el) + else + return null + + passOrFail: (el, level, strong) -> + if typeof el is 'number' + return "Passed: " + el + ", Failed: " + level + + formatColors: (el, level, strong) -> + indent = (level) -> + ret = '' + for i in [0..level] + ret = ret + ' ' + return ret + + tick = (el) -> + if $(el).is('.passed') then '\x1B[32m✓\x1B[0m' else '\x1B[31m✖' + + desc = (el, strong) -> + strong or= false + ret = $(el).find('> a.description') + ret = strong and '\x1B[1m' + ret[0].text or ret[0].text + + if typeof el is 'number' + results= "-------------------------------------\n" + results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el + if level > 0 + results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level + return results + else + return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong) + +# wait for method used in page evaluation +waitFor = (-> + + getTime = -> (new Date).getTime() + + return (test, doIt, duration) -> + duration or= 10000 + start = getTime() + finish = start + duration + int = undefined + + # looop function to call using setInterval + looop = -> + time = getTime() + timeout = (time >= finish) + + # callback for page evaluate that receives results + testCallback = (condition) -> + # No more time or condition fulfilled + if condition + clearInterval(int) + doIt(time - start) + # THEN, no moretime but condition unfulfilled + if timeout and not condition + console.log("ERROR - Timeout for page condition.") + clearInterval(int) + ph.exit() -module.exports = (filepath) -> - phantom.create (ph) -> + # perform the test evaluation + test(testCallback) - # wait for method used in page evaluation - waitFor = (-> - getTime = -> (new Date).getTime() - - return (test, doIt, duration) -> - duration or= 10000 - start = getTime() - finish = start + duration - int = undefined - # looop function to call using setInterval - looop = -> - time = getTime() - timeout = (time >= finish) - # method to process evaluation results - testCallback = (condition) -> - # No more time or condition fulfilled - if condition - clearInterval(int) - doIt(time - start) - # THEN, no moretime but condition unfulfilled - if timeout and not condition - console.log("ERROR - Timeout for page condition.") - clearInterval(int) - ph.exit() - # perform the test evaluation - test(testCallback) - # the intervale number - int = setInterval(looop, 1000 ) - )() + # the intervale number + int = setInterval(looop, 1000 ) +)() - ph.createPage (page) -> - _page = page +module.exports = (filepath, reportStyle = "formatColors") -> + phantom.create (ph) -> + ph.createPage (page) -> # print console.log output from the webpage - _page.set('onConsoleMessage', (msg) -> console.log(msg)) + page.set('onConsoleMessage', (msg) -> console.log(msg)) # page callback, kind of a hackish way to only allow our phantom # script to make use of console.log so we only see test results. - _page.set('onCallback', (msg) -> console.log(msg) if msg) + page.set('onCallback', (msg) -> console.log(msg) if msg) # open the filepath and beging tests - _page.open filepath, (status) -> + page.open filepath, (status) -> if status isnt "success" console.log("Cannot open URL") ph.exit() check = (callback) -> - _page.evaluate( - -> document.querySelector(".duration")?.innerText - , - callback - ) - - evalTests = (time) -> - _page.evaluate( (formatter) -> - - formatColors = (-> - indent = (level) -> - ret = '' - for i in [0..level] - ret = ret + ' ' - return ret - - tick = (el) -> - return if $(el).is('.passed') then '\x1B[32m✓\x1B[0m' else '\x1B[31m✖' - - desc = (el, strong) -> - strong or= false - ret = $(el).find('> a.description') - ret = strong and '\x1B[1m' + ret[0].text or ret[0].text - - return (el, level, strong) -> - if typeof el is 'number' - results= "-------------------------------------\n" - results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el - if level > 0 - results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level - return results - else - return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong) - )() - - errorsOnly = (-> - indent = (level) -> - ret = '' - for i in [0..level] - ret = ret + ' ' - return ret - - desc = (el) -> - $(el).find('> a.description')[0].text - - tick = (el) -> $(el).is('.passed') ? '✓ ' : '✖ ' - - return (el, level, strong) -> - if typeof el is 'number' - return "Passed: " + el + ", Failed: " + level - else - if (!$(el).is(".passed")) - return indent(level) + tick(el) + desc(el) - else - return null - )() - - # ability to request different type of outputs, default to formatColors - try - format = eval(formatter or "formatColors") - catch ex - format = formatColors - - printSuites = (root, level) -> - level or= 0 - $(root).find('div.suite').each( (i, el) -> - output = format(el, level, true) - if $(el).parents('div.suite').length is level - window.callPhantom(output) if output - printSpecs(el, level + 1) - printSuites(el, level + 1) - ) - - printSpecs = (root, level) -> - level or= 0 - $(root).find('> .specSummary').each( (i, el) -> - output = format(el, level) + isTestComplete = -> document.querySelector(".duration")?.innerText + page.evaluate(isTestComplete, callback) + + parseTestResults = (report) -> + # parameters need to be passed in as a simple string so we + # need to turn report back into a real javascript function + eval("report = " + report) + + # handle looping over suites + printSuites = (root, level) -> + level or= 0 + $(root).find('div.suite').each( (i, el) -> + output = report(el, level, true) + if $(el).parents('div.suite').length is level window.callPhantom(output) if output - ) - - printSuites($('div.jasmine_reporter')) - - # handle fails - fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed') - passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed') - window.callPhantom(format(passed.length, fails.length)) - - , -> ph.exit() ) + printSpecs(el, level + 1) + printSuites(el, level + 1) + ) + + # handle looping over specs + printSpecs = (root, level) -> + level or= 0 + $(root).find('> .specSummary').each( (i, el) -> + output = report(el, level) + window.callPhantom(output) if output + ) + + # our starting point + printSuites($('div.jasmine_reporter')) + + # handle fails + fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed') + passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed') + window.callPhantom(report(passed.length, fails.length)) + + # ability to request different type of outputs, default to formatColors + reporter = reporters[reportStyle or "formatColors"] + + # function to call the parsing function, along with callback once + # everything is complete and the reporter instance that is passed + # to the parseTestResults function + evalTests = (time) -> + page.evaluate( parseTestResults, (-> ph.exit()), new String(reporter)) + # wait for indication tests are done and then + # eval/print the test results, all passing, yay! waitFor(check, evalTests) - diff --git a/src/test.coffee b/src/test.coffee index a66a357..b4b330b 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -1,6 +1,7 @@ -fs = require('fs') -utils = require('./utils') -path = require('path') +fs = require('fs') +path = require('path') +utils = require('./utils') +phantom = require('./phantom') # ------- Public Functions @@ -15,8 +16,8 @@ run = (apps, options = {}) -> # ------- Test Functions runPhantom = (app, options = {}) -> - # TODO need to do this in steps if we have multiple apps - require('./phantom')(path.resolve(app.getTestPackage().testHome,'index.html')) + testFile = app.getTestPackage().getTestIndexFile() + phantom(testFile, "passOrFail" ) runKarma = (app, options = {}) -> # use custom testacular config file provided by user From 14d86ed91619b3039bcdba7a0442e03a9f85bf8c Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 9 Oct 2013 23:39:41 -0500 Subject: [PATCH 112/167] more tweaks to the phantom test runner, still need to get multiple tests running at once... --- assets/defaults/spine.json | 1 - lib/hem.js | 7 +- lib/package.js | 3 +- lib/phantom.js | 130 ++++++++++++++++++++---------------- lib/test.js | 32 +++++---- package.json | 2 +- src/hem.coffee | 8 ++- src/package.coffee | 3 +- src/phantom.coffee | 133 ++++++++++++++++++++++--------------- src/test.coffee | 41 +++++++++--- 10 files changed, 213 insertions(+), 147 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index bef9c49..31f922e 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -51,7 +51,6 @@ "after": [ "require('lib/setup');" ], - "runner": "karma", "framework": "jasmine" } } diff --git a/lib/hem.js b/lib/hem.js index 74ad72a..dd13235 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -152,9 +152,8 @@ Hem.prototype.test = function() { var testOptions; - testOptions = { - basePath: this.home - }; + testOptions = this.options.hem.tests || {}; + testOptions.basePath || (testOptions.basePath = this.home); if (argv.watch) { this.watch(); testOptions.singleRun = false; @@ -162,7 +161,7 @@ this.buildApps(); testOptions.singleRun = true; } - return testing.run(this.apps, this.options); + return testing.run(this.apps, testOptions); }; Hem.prototype.check = function() { diff --git a/lib/package.js b/lib/package.js index c8be4b8..41b4f9a 100644 --- a/lib/package.js +++ b/lib/package.js @@ -265,7 +265,7 @@ write = true; } extra = (argv.compress && " --using compression") || ""; - log("- Building target: " + this.target + "" + extra); + log.info("- Building target: " + this.target + "" + extra); source = this.compile(); if (source && write) { dirname = path.dirname(this.target); @@ -405,7 +405,6 @@ function TestPackage(app, config) { TestPackage.__super__.constructor.call(this, app, config); this.depends = utils.toArray(config.depends); - this.runner = config.runner; this.framework = config.framework; this.testHome = path.dirname(this.target); } diff --git a/lib/phantom.js b/lib/phantom.js index 02eaa7b..71d7d59 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var phantom, reporters, waitFor; + var jasmine_checkTestResults, jasmine_parseTestResults, phantom, reporters, run, waitFor; phantom = require('phantom'); @@ -27,14 +27,13 @@ }; if (typeof el === 'number') { return "Passed: " + el + ", Failed: " + level; - } else { - if (!$(el).is(".passed")) { - return indent(level) + tick(el) + desc(el); - } else { - return null; - } + } else if (!$(el).is(".passed")) { + return indent(level) + tick(el) + desc(el); } }, + silent: function() { + return ""; + }, passOrFail: function(el, level, strong) { if (typeof el === 'number') { return "Passed: " + el + ", Failed: " + level; @@ -59,7 +58,9 @@ }; desc = function(el, strong) { var ret; - strong || (strong = false); + if (strong == null) { + strong = false; + } ret = $(el).find('> a.description'); return ret = strong && '\x1B[1m' + ret[0].text || ret[0].text; }; @@ -108,10 +109,55 @@ }; })(); - module.exports = function(filepath, reportStyle) { - if (reportStyle == null) { - reportStyle = "formatColors"; - } + jasmine_parseTestResults = function(report) { + var fails, passed, printSpecs, printSuites; + eval("report = " + report); + printSuites = function(root, level) { + level || (level = 0); + return $(root).find('div.suite').each(function(i, el) { + var output; + output = report(el, level, true); + if ($(el).parents('div.suite').length === level) { + if (output) { + window.callPhantom(output); + } + printSpecs(el, level + 1); + } + return printSuites(el, level + 1); + }); + }; + printSpecs = function(root, level) { + level || (level = 0); + return $(root).find('> .specSummary').each(function(i, el) { + var output; + output = report(el, level); + if (output) { + return window.callPhantom(output); + } + }); + }; + printSuites($('div.jasmine_reporter')); + fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed').length; + passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed').length; + window.callPhantom(report(passed, fails)); + return { + passed: passed, + fails: fails + }; + }; + + jasmine_checkTestResults = function(page) { + return function(checkComplete) { + var isCheckComplete; + isCheckComplete = function() { + var _ref; + return (_ref = document.querySelector(".duration")) != null ? _ref.innerText : void 0; + }; + return page.evaluate(isCheckComplete, checkComplete); + }; + }; + + run = function(filepath, options, callback) { return phantom.create(function(ph) { return ph.createPage(function(page) { page.set('onConsoleMessage', function(msg) { @@ -123,61 +169,29 @@ } }); return page.open(filepath, function(status) { - var check, evalTests, parseTestResults, reporter; + var checkTestResults, complete, evalTestResults, parseTestResults, reporter; if (status !== "success") { console.log("Cannot open URL"); ph.exit(); } - check = function(callback) { - var isTestComplete; - isTestComplete = function() { - var _ref; - return (_ref = document.querySelector(".duration")) != null ? _ref.innerText : void 0; - }; - return page.evaluate(isTestComplete, callback); - }; - parseTestResults = function(report) { - var fails, passed, printSpecs, printSuites; - eval("report = " + report); - printSuites = function(root, level) { - level || (level = 0); - return $(root).find('div.suite').each(function(i, el) { - var output; - output = report(el, level, true); - if ($(el).parents('div.suite').length === level) { - if (output) { - window.callPhantom(output); - } - printSpecs(el, level + 1); - } - return printSuites(el, level + 1); - }); - }; - printSpecs = function(root, level) { - level || (level = 0); - return $(root).find('> .specSummary').each(function(i, el) { - var output; - output = report(el, level); - if (output) { - return window.callPhantom(output); - } - }); - }; - printSuites($('div.jasmine_reporter')); - fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed'); - passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed'); - return window.callPhantom(report(passed.length, fails.length)); + checkTestResults = jasmine_checkTestResults(page); + parseTestResults = jasmine_parseTestResults; + complete = function(results) { + ph.exit(); + return typeof callback === "function" ? callback(results) : void 0; }; - reporter = reporters[reportStyle || "formatColors"]; - evalTests = function(time) { - return page.evaluate(parseTestResults, (function() { - return ph.exit(); - }), new String(reporter)); + reporter = reporters[options.output]; + evalTestResults = function(time) { + return page.evaluate(parseTestResults, complete, new String(reporter)); }; - return waitFor(check, evalTests); + return waitFor(checkTestResults, evalTestResults); }); }); }); }; + module.exports.run = run; + + module.exports.reporters = reporters; + }).call(this); diff --git a/lib/test.js b/lib/test.js index 8ce7068..b056006 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,37 +1,45 @@ // Generated by CoffeeScript 1.6.3 (function() { - var createKarmaFileList, fs, path, phantom, run, runKarma, runPhantom, utils; + var createKarmaFileList, fs, log, path, phantom, run, runKarma, runPhantom, utils; fs = require('fs'); path = require('path'); + log = require('./log'); + utils = require('./utils'); phantom = require('./phantom'); run = function(apps, options) { - var app, _i, _len, _results; - if (options == null) { - options = {}; + var app, runTests, _i, _len, _results; + switch (options.runner) { + case "phantom": + runTests = runPhantom; + break; + case "karma": + runTests = runKarma; + break; + default: + throw new Error("Invalid or unset test runner value: " + options.runner); } _results = []; for (_i = 0, _len = apps.length; _i < _len; _i++) { app = apps[_i]; - if (app.name !== 'common') { - _results.push(runPhantom(app, options)); - } + _results.push(runTests(app, options)); } return _results; }; - runPhantom = function(app, options) { + runPhantom = function(app, options, done) { var testFile; - if (options == null) { - options = {}; - } + log("Testing application targets: " + app.name + ""); testFile = app.getTestPackage().getTestIndexFile(); - return phantom(testFile, "passOrFail"); + options.output || (options.output = "passOrFail"); + return phantom.run(testFile, options, function(results) { + return process.exit(results.fails); + }); }; runKarma = function(app, options) { diff --git a/package.json b/package.json index b92ae52..bf6ea4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.16", + "version": "0.4.17", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/hem.coffee b/src/hem.coffee index ba0d2db..94c2973 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -133,8 +133,9 @@ class Hem test: -> # set test options - testOptions = - basePath: @home + testOptions = @options.hem.tests or {} + testOptions.basePath or= @home + # check for watch mode if argv.watch @watch() @@ -142,8 +143,9 @@ class Hem else @buildApps() testOptions.singleRun = true + # run tests - testing.run(@apps, @options) + testing.run(@apps, testOptions) check: -> printOptions = showHidden: false, colors: !argv.nocolors, depth: null diff --git a/src/package.coffee b/src/package.coffee index 532b5df..0f80851 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -192,7 +192,7 @@ class Package build: (write = true) -> extra = (argv.compress and " --using compression") or "" - log("- Building target: #{@target}#{extra}") + log.info("- Building target: #{@target}#{extra}") source = @compile() if source and write dirname = path.dirname(@target) @@ -296,7 +296,6 @@ class TestPackage extends JsPackage super(app, config) # test configurations @depends = utils.toArray(config.depends) - @runner = config.runner @framework = config.framework # get test home directory based on target file location diff --git a/src/phantom.coffee b/src/phantom.coffee index 8062a18..435b313 100644 --- a/src/phantom.coffee +++ b/src/phantom.coffee @@ -1,6 +1,8 @@ phantom = require 'phantom' -# formatters to display test results +# ------- Test Result Formatters + +# TODO: need to make this work with mocha at some point reporters = @@ -11,16 +13,14 @@ reporters = ret desc = (el) -> $(el).find('> a.description')[0].text - tick = (el) -> if $(el).is('.passed') then '✓ ' else '✖ ' if typeof el is 'number' return "Passed: " + el + ", Failed: " + level - else - if (!$(el).is(".passed")) - return indent(level) + tick(el) + desc(el) - else - return null + else if (!$(el).is(".passed")) + return indent(level) + tick(el) + desc(el) + + silent: -> return "" passOrFail: (el, level, strong) -> if typeof el is 'number' @@ -29,28 +29,29 @@ reporters = formatColors: (el, level, strong) -> indent = (level) -> ret = '' - for i in [0..level] - ret = ret + ' ' - return ret + ret = ret + ' ' for i in [0..level] + ret tick = (el) -> if $(el).is('.passed') then '\x1B[32m✓\x1B[0m' else '\x1B[31m✖' - desc = (el, strong) -> - strong or= false + desc = (el, strong = false) -> ret = $(el).find('> a.description') ret = strong and '\x1B[1m' + ret[0].text or ret[0].text + # display final results if typeof el is 'number' results= "-------------------------------------\n" results += "\x1B[32m✓\x1B[0m\x1B[1m Passed: \x1B[0m" + el if level > 0 results += "\n\x1B[31m✖ \x1B[0m\x1B[1mFailed: \x1B[0m" + level return results + # format output else return '\x1B[1m' + indent(level) + tick(el) + ' ' + desc(el, strong) -# wait for method used in page evaluation +# ------- Wait for certain page elements to become visible + waitFor = (-> getTime = -> (new Date).getTime() @@ -85,8 +86,54 @@ waitFor = (-> int = setInterval(looop, 1000 ) )() - -module.exports = (filepath, reportStyle = "formatColors") -> +# ------- Jasmine Functions + +jasmine_parseTestResults = (report) -> + # parameters need to be passed in as a simple string so we + # need to turn report back into a real javascript function + eval("report = " + report) + + # TODO: at some point, do we need to inject jQuery? + + # handle looping over suites + printSuites = (root, level) -> + level or= 0 + $(root).find('div.suite').each( (i, el) -> + output = report(el, level, true) + if $(el).parents('div.suite').length is level + window.callPhantom(output) if output + printSpecs(el, level + 1) + printSuites(el, level + 1) + ) + + # handle looping over specs + printSpecs = (root, level) -> + level or= 0 + $(root).find('> .specSummary').each( (i, el) -> + output = report(el, level) + window.callPhantom(output) if output + ) + + # our starting point + printSuites($('div.jasmine_reporter')) + + # handle fails + fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed').length + passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed').length + window.callPhantom(report(passed, fails)) + + # return results, these will be eventually passed to the + # the callback function that was provided initially. + return passed: passed, fails: fails + +jasmine_checkTestResults = (page) -> + (checkComplete) -> + isCheckComplete = -> document.querySelector(".duration")?.innerText + page.evaluate(isCheckComplete, checkComplete) + +# ------- Public Functions + +run = (filepath, options, callback) -> phantom.create (ph) -> ph.createPage (page) -> @@ -103,52 +150,30 @@ module.exports = (filepath, reportStyle = "formatColors") -> console.log("Cannot open URL") ph.exit() - check = (callback) -> - isTestComplete = -> document.querySelector(".duration")?.innerText - page.evaluate(isTestComplete, callback) - - parseTestResults = (report) -> - # parameters need to be passed in as a simple string so we - # need to turn report back into a real javascript function - eval("report = " + report) - - # handle looping over suites - printSuites = (root, level) -> - level or= 0 - $(root).find('div.suite').each( (i, el) -> - output = report(el, level, true) - if $(el).parents('div.suite').length is level - window.callPhantom(output) if output - printSpecs(el, level + 1) - printSuites(el, level + 1) - ) - - # handle looping over specs - printSpecs = (root, level) -> - level or= 0 - $(root).find('> .specSummary').each( (i, el) -> - output = report(el, level) - window.callPhantom(output) if output - ) - - # our starting point - printSuites($('div.jasmine_reporter')) - - # handle fails - fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed') - passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed') - window.callPhantom(report(passed.length, fails.length)) + # assign the appropiate framework functions + checkTestResults = jasmine_checkTestResults(page) + parseTestResults = jasmine_parseTestResults + + # function to call upon completion of test parsing + complete = (results) -> + ph.exit() + callback?(results) # ability to request different type of outputs, default to formatColors - reporter = reporters[reportStyle or "formatColors"] + reporter = reporters[options.output] + # function to call the parsing function, along with callback once # everything is complete and the reporter instance that is passed # to the parseTestResults function - evalTests = (time) -> - page.evaluate( parseTestResults, (-> ph.exit()), new String(reporter)) + evalTestResults = (time) -> + page.evaluate( parseTestResults, complete, new String(reporter)) # wait for indication tests are done and then # eval/print the test results, all passing, yay! - waitFor(check, evalTests) + waitFor(checkTestResults, evalTestResults) + +# ------- Exports +module.exports.run = run +module.exports.reporters = reporters diff --git a/src/test.coffee b/src/test.coffee index b4b330b..e727e56 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -1,23 +1,44 @@ fs = require('fs') path = require('path') +log = require('./log') utils = require('./utils') phantom = require('./phantom') -# ------- Public Functions +# ------- Public Functions -run = (apps, options = {}) -> - # TODO: is karam avaliable, use that, copy code from compilers - # - fall back to phantomjs - # - otherwise just open file in browser?? +run = (apps, options) -> - # probably need to loop over apps and run karma for each?? - runPhantom(app, options) for app in apps when app.name isnt 'common' + # determine runner + switch options.runner + when "phantom" + runTests = runPhantom + when "karma" + runTests = runKarma + else + # TODO: open test file in browser as default?? + throw new Error("Invalid or unset test runner value: #{options.runner}") -# ------- Test Functions + # need to loop over apps and run tests for each target app + for app in apps + runTests(app, options) -runPhantom = (app, options = {}) -> +# ------- Test Functions + +runPhantom = (app, options, done) -> + log("Testing application targets: #{app.name}") testFile = app.getTestPackage().getTestIndexFile() - phantom(testFile, "passOrFail" ) + + # set some other defaults + options.output or= "passOrFail" + + # TODO: need a way for watch to work?, we can use our new event system :o) + # TODO: need a way to run tests in sequential steps... async library?? + + # run phantom + phantom.run(testFile, options, (results) -> + # exit with the number of failed tests + process.exit(results.fails) + ) runKarma = (app, options = {}) -> # use custom testacular config file provided by user From 6e211f7bcb51472d0d34fbbed4e384a2fca859f6 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 9 Oct 2013 23:48:26 -0500 Subject: [PATCH 113/167] handle timeout conditions with a little more grace --- lib/phantom.js | 10 +++++++--- src/phantom.coffee | 9 ++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/phantom.js b/lib/phantom.js index 71d7d59..0972f22 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -84,7 +84,7 @@ }; return function(test, doIt, duration) { var finish, int, looop, start; - duration || (duration = 10000); + duration || (duration = 6000); start = getTime(); finish = start + duration; int = void 0; @@ -100,7 +100,7 @@ if (timeout && !condition) { console.log("ERROR - Timeout for page condition."); clearInterval(int); - return ph.exit(); + return doIt(0); } }; return test(testCallback); @@ -182,7 +182,11 @@ }; reporter = reporters[options.output]; evalTestResults = function(time) { - return page.evaluate(parseTestResults, complete, new String(reporter)); + if (time > 0) { + return page.evaluate(parseTestResults, complete, new String(reporter)); + } else { + return ph.exit(); + } }; return waitFor(checkTestResults, evalTestResults); }); diff --git a/src/phantom.coffee b/src/phantom.coffee index 435b313..42c42d8 100644 --- a/src/phantom.coffee +++ b/src/phantom.coffee @@ -57,7 +57,7 @@ waitFor = (-> getTime = -> (new Date).getTime() return (test, doIt, duration) -> - duration or= 10000 + duration or= 6000 start = getTime() finish = start + duration int = undefined @@ -77,7 +77,7 @@ waitFor = (-> if timeout and not condition console.log("ERROR - Timeout for page condition.") clearInterval(int) - ph.exit() + doIt(0) # perform the test evaluation test(testCallback) @@ -167,7 +167,10 @@ run = (filepath, options, callback) -> # everything is complete and the reporter instance that is passed # to the parseTestResults function evalTestResults = (time) -> - page.evaluate( parseTestResults, complete, new String(reporter)) + if time > 0 + page.evaluate( parseTestResults, complete, new String(reporter)) + else + ph.exit() # wait for indication tests are done and then # eval/print the test results, all passing, yay! From 1da10bced0490bd8ffdc63e64402239b45b9c900 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 12:48:13 -0500 Subject: [PATCH 114/167] making phantom an optionalDependency, if not present then open test file in browser --- lib/phantom.js | 12 ++++++++++-- lib/test.js | 19 ++++++++++++++++--- package.json | 10 +++++++--- src/hem.coffee | 2 +- src/phantom.coffee | 10 +++++++++- src/test.coffee | 35 ++++++++++++++++++++--------------- 6 files changed, 63 insertions(+), 25 deletions(-) diff --git a/lib/phantom.js b/lib/phantom.js index 0972f22..b70b1ed 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -1,8 +1,16 @@ // Generated by CoffeeScript 1.6.3 (function() { - var jasmine_checkTestResults, jasmine_parseTestResults, phantom, reporters, run, waitFor; + var err, jasmine_checkTestResults, jasmine_parseTestResults, log, phantom, reporters, run, waitFor; - phantom = require('phantom'); + log = require("./log"); + + try { + phantom = require('phantom'); + } catch (_error) { + err = _error; + log.error("Unable to require('phantom') npm module..."); + return; + } reporters = { errorsOnly: function(el, level, strong) { diff --git a/lib/test.js b/lib/test.js index b056006..a0b2e39 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var createKarmaFileList, fs, log, path, phantom, run, runKarma, runPhantom, utils; + var createKarmaFileList, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils; fs = require('fs'); @@ -16,11 +16,14 @@ var app, runTests, _i, _len, _results; switch (options.runner) { case "phantom": - runTests = runPhantom; + runTests = phantom.run ? runPhantom : runBrowser; break; case "karma": runTests = runKarma; break; + case "browser": + runTests = runBrowser; + break; default: throw new Error("Invalid or unset test runner value: " + options.runner); } @@ -32,13 +35,21 @@ return _results; }; + runBrowser = function(app, options, done) { + var open; + open = require("open"); + return open(app.getTestPackage().getTestIndexFile()); + }; + runPhantom = function(app, options, done) { var testFile; log("Testing application targets: " + app.name + ""); testFile = app.getTestPackage().getTestIndexFile(); options.output || (options.output = "passOrFail"); return phantom.run(testFile, options, function(results) { - return process.exit(results.fails); + if (options.singleRun) { + return process.exit(results.fails); + } }); }; @@ -64,4 +75,6 @@ module.exports.run = run; + module.exports.phantom = phantom; + }).call(this); diff --git a/package.json b/package.json index bf6ea4d..35bfb90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.17", + "version": "0.4.18", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -42,11 +42,15 @@ "connect": "~2.9.0", "http-proxy": "~0.10.3", "uglify-js": "~2.4.0", - "karma": "~0.10.2", "sty": "~0.6.1", "optimist": "~0.6.0", "watch": "~0.8.0", "fs-extra": "~0.7.0", - "phantom": "~0.5.2" + "async": "~0.2.9", + "open": "0.0.4" + }, + "optionalDependencies": { + "phantom": "~0.5.2", + "karma": "~0.10.2" } } diff --git a/src/hem.coffee b/src/hem.coffee index 94c2973..c0d8d85 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -65,7 +65,7 @@ class Hem # exposing globals for customization @compilers : compilers - @events : utils.events # TODO: eventuall get an event system going... + @events : utils.events # TODO: eventually get an event system going... # default values for server @defaults: diff --git a/src/phantom.coffee b/src/phantom.coffee index 42c42d8..58936f8 100644 --- a/src/phantom.coffee +++ b/src/phantom.coffee @@ -1,4 +1,12 @@ -phantom = require 'phantom' +log = require("./log") + +# attempt to load the phantom module, which is an optional +# dependency. If not available return an empty object +try + phantom = require 'phantom' +catch err + log.error "Unable to require('phantom') npm module..." + return # ------- Test Result Formatters diff --git a/src/test.coffee b/src/test.coffee index e727e56..b0e550d 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -7,23 +7,27 @@ phantom = require('./phantom') # ------- Public Functions run = (apps, options) -> - - # determine runner - switch options.runner - when "phantom" - runTests = runPhantom - when "karma" - runTests = runKarma - else - # TODO: open test file in browser as default?? - throw new Error("Invalid or unset test runner value: #{options.runner}") - - # need to loop over apps and run tests for each target app - for app in apps - runTests(app, options) + # determine runner + switch options.runner + when "phantom" + runTests = if phantom.run then runPhantom else runBrowser + when "karma" + runTests = runKarma + when "browser" + runTests = runBrowser + else + throw new Error("Invalid or unset test runner value: #{options.runner}") + + # need to loop over apps and run tests for each target app + for app in apps + runTests(app, options) # ------- Test Functions +runBrowser = (app, options, done) -> + open = require("open") + open(app.getTestPackage().getTestIndexFile()) + runPhantom = (app, options, done) -> log("Testing application targets: #{app.name}") testFile = app.getTestPackage().getTestIndexFile() @@ -37,7 +41,7 @@ runPhantom = (app, options, done) -> # run phantom phantom.run(testFile, options, (results) -> # exit with the number of failed tests - process.exit(results.fails) + process.exit(results.fails) if options.singleRun ) runKarma = (app, options = {}) -> @@ -63,5 +67,6 @@ createKarmaFileList = (app) -> # ------- Exports module.exports.run = run +module.exports.phantom = phantom From 2d046512b3c3656fefc6e31e58f5016c2ee0db3e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 13:07:24 -0500 Subject: [PATCH 115/167] fix issue with log require in versioning module --- lib/versioning.js | 8 +++++--- package.json | 2 +- src/versioning.coffee | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/versioning.js b/lib/versioning.js index 8ff7309..bc7f085 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var NpmPackageVersion, fs, path, types, updateVersionInAppFiles, updateVersionInData, utils; + var NpmPackageVersion, fs, log, path, types, updateVersionInAppFiles, updateVersionInData, utils; fs = require('fs'); @@ -8,6 +8,8 @@ utils = require('./utils'); + log = require('./log'); + types = {}; updateVersionInAppFiles = function(files, packages, value) { @@ -15,7 +17,7 @@ _results = []; for (_i = 0, _len = files.length; _i < _len; _i++) { file = files[_i]; - utils.log("- updating file " + file + " with version: " + value + ""); + log("- updating file " + file + " with version: " + value + ""); data = fs.readFileSync(file, 'utf8'); for (key in packages) { pkg = packages[key]; @@ -33,7 +35,7 @@ match = new RegExp("=(\"|')(.*/?)" + name + "[^\"']?" + ext + "(\"|')"); replace = "=$1$2" + name + "." + value + ext + "$3"; if (data.match(match)) { - utils.log("> found target: " + pkg.target); + log("> found target: " + pkg.target); return data.replace(match, replace); } else { return data; diff --git a/package.json b/package.json index 35bfb90..8d79854 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.18", + "version": "0.4.19", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/versioning.coffee b/src/versioning.coffee index 7df727c..f7306c6 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -1,13 +1,14 @@ fs = require('fs') path = require('path') utils = require('./utils') +log = require('./log') types = {} # private functions updateVersionInAppFiles = (files, packages, value) -> for file in files - utils.log "- updating file #{file} with version: #{value}" + log "- updating file #{file} with version: #{value}" data = fs.readFileSync(file, 'utf8') # match all target in packages for key, pkg of packages @@ -21,7 +22,7 @@ updateVersionInData = (data, value, pkg) -> replace = "=$1$2#{name}.#{value}#{ext}$3" # perform replace if data.match(match) - utils.log "> found target: #{pkg.target}" + log "> found target: #{pkg.target}" data.replace(match, replace) else data From 32fa489a328a5ca27775cbac6f48f43e98e2c73a Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 16:02:12 -0500 Subject: [PATCH 116/167] setting up custom() method option for slug files that an instance of hem is passed into to allow customization --- lib/hem.js | 33 +++++++++++++++++++++++++-------- src/hem.coffee | 24 ++++++++++++++++-------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index dd13235..86f770d 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Hem, application, argv, compilers, fs, help, log, optimist, path, server, testing, utils, + var Hem, application, argv, compilers, fs, help, log, optimist, path, server, testing, utils, versioning, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; fs = require('fs'); @@ -35,6 +35,8 @@ application = require('./package'); + versioning = require('./versioning'); + help = function() { var _ref; log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); @@ -53,10 +55,6 @@ return server.middleware(hem); }; - Hem.compilers = compilers; - - Hem.events = utils.events; - Hem.defaults = { hem: { port: 9294, @@ -187,6 +185,7 @@ }; Hem.prototype.exec = function(command) { + var _base; if (command == null) { command = argv.command; } @@ -194,6 +193,9 @@ return help(); } this.apps = this.getTargetApps(); + if (typeof (_base = this.slug).custom === "function") { + _base.custom(this); + } return this[command](); }; @@ -209,12 +211,12 @@ Hem.home = path.dirname(slugPath); try { delete require.cache[slugPath]; - slug = require(slugPath); - return (typeof slug === "function" ? slug(Hem) : void 0) || slug; + this.slug = require(slugPath); } catch (_error) { error = _error; - return log.errorAndExit("Couldn't load slug file " + slugPath + ". " + error); + log.errorAndExit("Couldn't load slug file " + slugPath + ". " + error); } + return this.slug.config; }; Hem.prototype.getTargetApps = function(targets) { @@ -245,6 +247,21 @@ return _results; }; + Hem.prototype.module = function(name) { + switch (name) { + case "compilers": + return compilers; + case "events": + return utils.events; + case "reporters": + return testing.phantom.reporters; + case "versioning": + return versioning; + default: + throw new Error("Unknown module name " + name); + } + }; + return Hem; })(); diff --git a/src/hem.coffee b/src/hem.coffee index c0d8d85..e16be03 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -43,6 +43,7 @@ compilers = require('./compilers') server = require('./server') testing = require('./test') application = require('./package') +versioning = require('./versioning') # ------- Global Functions @@ -62,11 +63,6 @@ class Hem hem = new Hem(slug) server.middleware(hem) - # exposing globals for customization - - @compilers : compilers - @events : utils.events # TODO: eventually get an event system going... - # default values for server @defaults: hem: @@ -161,10 +157,11 @@ class Hem log "" exec: (command = argv.command) -> - # handle empty arguments return help() unless @[command] # reset the apps list based on command line args @apps = @getTargetApps() + # customize hem + @slug.custom?(@) # hope this works :o) @[command]() @@ -184,11 +181,13 @@ class Hem # next try to require try delete require.cache[slugPath] - slug = require(slugPath) - slug?(Hem) or slug + @slug = require(slugPath) catch error log.errorAndExit("Couldn't load slug file #{slugPath}. #{error}") + # return portion of slug file + @slug.config + getTargetApps: (targets = argv.targets) -> targetAll = targets.length is 0 (app for app in @allApps when app.name in targets or targetAll) @@ -196,5 +195,14 @@ class Hem buildApps: () -> app.build() for app in @apps + module: (name) -> + switch name + when "compilers" then compilers + when "events" then utils.events + when "reporters" then testing.phantom.reporters + when "versioning" then versioning + else + throw new Error("Unknown module name #{name}") + module.exports = Hem From a86e234ad9de6717ef4dfee42d3b7c779c495099 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 16:05:28 -0500 Subject: [PATCH 117/167] updated readme --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 89f8a38..619b528 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ Other goals and features... In addition to the above ideas, I want to try to get some of the features below integrated into hem... - [*] Easier to setup proxy -- [ ] livereload abilities for css and possibly js -- [ ] easier testing setup and execution (karma/phantomjs) -- [ ] integrate spine.app commands into hem -- [ ] update examples/documention - [*] ability to act as middleware for connect/express apps -- [ ] versioning abilities +- [*] versioning abilities +- [ ] easier testing setup and execution (karma/phantomjs) +- [ ] Simple event system +- [ ] livereload abilities for css and possibly js - [ ] manifest creation +- [ ] update examples/documention - [ ] really do need to write some tests for hem Will look into but not 100% sure @@ -47,11 +47,12 @@ These would be nice to have things, will have to research it more in the future. - [*] easier ways to add your own compilers/extensions - [ ] source mapping +- [ ] integrate spine.app commands into hem - [ ] possibly look at AMD vs commonjs??? - [ ] jshint/lint checks?? When will it be done?? --- -Thats a good question :) I think its possible to have the main features done, tested, and ready to go by the end of august or early september. +Thats a good question :) I think its possible to have the main features done, tested, and ready to go by the end of november :o). From 476cadd78f000f97e3b8929eb0e22227a89d5430 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 16:57:43 -0500 Subject: [PATCH 118/167] ok, try this one more time, should hopefully fix the issues. --- README.md | 2 +- lib/hem.js | 4 ++-- package.json | 2 +- src/hem.coffee | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 619b528..55bc2dd 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,8 @@ In addition to the above ideas, I want to try to get some of the features below - [*] versioning abilities - [ ] easier testing setup and execution (karma/phantomjs) - [ ] Simple event system -- [ ] livereload abilities for css and possibly js - [ ] manifest creation +- [ ] livereload abilities for css and possibly js - [ ] update examples/documention - [ ] really do need to write some tests for hem diff --git a/lib/hem.js b/lib/hem.js index 86f770d..015b8ce 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -106,7 +106,7 @@ value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; log("Starting Server at " + value + ""); app = server.start(this); - return Hem.events.emit("server-start", app); + return utils.events.emit("server-start", app); }; Hem.prototype.clean = function() { @@ -214,7 +214,7 @@ this.slug = require(slugPath); } catch (_error) { error = _error; - log.errorAndExit("Couldn't load slug file " + slugPath + ". " + error); + log.errorAndExit("Couldn't load slug file " + slugPath + "."); } return this.slug.config; }; diff --git a/package.json b/package.json index 8d79854..8e1ef71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.19", + "version": "0.4.21", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/hem.coffee b/src/hem.coffee index e16be03..0f4a8a6 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -111,7 +111,7 @@ class Hem value = "http://#{@options.hem.host or "*"}:#{@options.hem.port}" log "Starting Server at #{value}" app = server.start(@) - Hem.events.emit("server-start", app) + utils.events.emit("server-start", app) clean: -> app.unlink() for app in @apps @@ -183,7 +183,7 @@ class Hem delete require.cache[slugPath] @slug = require(slugPath) catch error - log.errorAndExit("Couldn't load slug file #{slugPath}. #{error}") + log.errorAndExit("Couldn't load slug file #{slugPath}.") # return portion of slug file @slug.config From fef7b2a5d5c622b9b8a6928133ea778be15d26d7 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 17:25:28 -0500 Subject: [PATCH 119/167] better error message --- lib/hem.js | 2 +- src/hem.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 015b8ce..788c656 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -206,7 +206,7 @@ slugPath = require.resolve(slugPath); } catch (_error) { error = _error; - log.errorAndExit("Couldn't find slug file " + slugPath + ". " + error); + log.errorAndExit("Couldn't find slug file " + (path.dirname(slugPath))); } Hem.home = path.dirname(slugPath); try { diff --git a/src/hem.coffee b/src/hem.coffee index 0f4a8a6..93ceb02 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -173,7 +173,7 @@ class Hem try slugPath = require.resolve(slugPath) catch error - log.errorAndExit("Couldn't find slug file #{slugPath}. #{error}") + log.errorAndExit("Couldn't find slug file #{path.dirname(slugPath)}") # set home directory to slug directory Hem.home = path.dirname(slugPath) From ab1e8044fcf465a99f036eb4860aa648cf9b59ed Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 17:26:16 -0500 Subject: [PATCH 120/167] setting incorrect home variable --- src/hem.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hem.coffee b/src/hem.coffee index 93ceb02..cc9a7c2 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -176,7 +176,7 @@ class Hem log.errorAndExit("Couldn't find slug file #{path.dirname(slugPath)}") # set home directory to slug directory - Hem.home = path.dirname(slugPath) + @home = path.dirname(slugPath) # next try to require try From a2577fe3b42dab17b5e593b6db87bccbfee34b77 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 19:46:00 -0500 Subject: [PATCH 121/167] making events its own module and adding watch event --- lib/events.js | 9 +++++++++ lib/hem.js | 12 ++++++++---- lib/package.js | 7 +++++-- lib/test.js | 2 +- lib/utils.js | 6 +----- src/events.coffee | 4 ++++ src/hem.coffee | 6 ++++-- src/package.coffee | 2 ++ src/test.coffee | 2 +- src/utils.coffee | 5 ----- 10 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 lib/events.js create mode 100644 src/events.coffee diff --git a/lib/events.js b/lib/events.js new file mode 100644 index 0000000..2c39903 --- /dev/null +++ b/lib/events.js @@ -0,0 +1,9 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var events; + + events = new require('events'); + + module.exports = new events.EventEmitter(); + +}).call(this); diff --git a/lib/hem.js b/lib/hem.js index 788c656..89868bb 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Hem, application, argv, compilers, fs, help, log, optimist, path, server, testing, utils, versioning, + var Hem, application, argv, compilers, events, fs, help, log, optimist, path, server, testing, utils, versioning, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; fs = require('fs'); @@ -37,6 +37,10 @@ versioning = require('./versioning'); + events = require('./events'); + + console.log(events); + help = function() { var _ref; log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); @@ -106,7 +110,7 @@ value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; log("Starting Server at " + value + ""); app = server.start(this); - return utils.events.emit("server-start", app); + return events.emit("server-start", app); }; Hem.prototype.clean = function() { @@ -208,7 +212,7 @@ error = _error; log.errorAndExit("Couldn't find slug file " + (path.dirname(slugPath))); } - Hem.home = path.dirname(slugPath); + this.home = path.dirname(slugPath); try { delete require.cache[slugPath]; this.slug = require(slugPath); @@ -252,7 +256,7 @@ case "compilers": return compilers; case "events": - return utils.events; + return events; case "reporters": return testing.phantom.reporters; case "versioning": diff --git a/lib/package.js b/lib/package.js index 41b4f9a..71216cb 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, argv, createApplication, fs, log, path, uglifycss, uglifyjs, utils, versioning, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, argv, createApplication, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -21,6 +21,8 @@ argv = require('./utils').ARGV; + events = require('./events'); + log = require('./log'); versioning = require('./versioning'); @@ -303,7 +305,8 @@ dir = dirs[_j]; require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { - return _this.build(); + _this.build(); + return events.emit("watch", _this, file); } }); } diff --git a/lib/test.js b/lib/test.js index a0b2e39..eb4be7c 100644 --- a/lib/test.js +++ b/lib/test.js @@ -62,7 +62,7 @@ testConfig || (testConfig = { singleRun: options.singleRun || true, basePath: options.basePath, - reporters: ['progress'], + reporters: [options.output || 'progress'], logLevel: 'info', frameworks: [options.framework], browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], diff --git a/lib/utils.js b/lib/utils.js index 9b34312..705b66e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var clean, events, extend, flatten, fs, isWin, path, tmplCache, utils, + var clean, extend, flatten, fs, isWin, path, tmplCache, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __slice = [].slice; @@ -8,8 +8,6 @@ fs = require('fs-extra'); - events = require('events'); - utils = {}; isWin = !!require('os').platform().match(/^win/); @@ -144,8 +142,6 @@ return data && fn(data) || fn; }; - utils.events = new events.EventEmitter(); - clean = function(values, sep, trimStart) { var regexp, result, value, _i, _len; if (trimStart == null) { diff --git a/src/events.coffee b/src/events.coffee new file mode 100644 index 0000000..7a7a936 --- /dev/null +++ b/src/events.coffee @@ -0,0 +1,4 @@ +# ------ Setup shareable events emitter + +events = new require('events') +module.exports = new events.EventEmitter() diff --git a/src/hem.coffee b/src/hem.coffee index cc9a7c2..3a7a322 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -44,6 +44,8 @@ server = require('./server') testing = require('./test') application = require('./package') versioning = require('./versioning') +events = require('./events') +console.log events # ------- Global Functions @@ -111,7 +113,7 @@ class Hem value = "http://#{@options.hem.host or "*"}:#{@options.hem.port}" log "Starting Server at #{value}" app = server.start(@) - utils.events.emit("server-start", app) + events.emit("server-start", app) clean: -> app.unlink() for app in @apps @@ -198,7 +200,7 @@ class Hem module: (name) -> switch name when "compilers" then compilers - when "events" then utils.events + when "events" then events when "reporters" then testing.phantom.reporters when "versioning" then versioning else diff --git a/src/package.coffee b/src/package.coffee index 0f80851..74ca650 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -6,6 +6,7 @@ Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') argv = require('./utils').ARGV +events = require('./events') log = require('./log') versioning = require('./versioning') @@ -216,6 +217,7 @@ class Package require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() + events.emit("watch", @, file) dirs getWatchedDirs: -> diff --git a/src/test.coffee b/src/test.coffee index b0e550d..6382c93 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -52,7 +52,7 @@ runKarma = (app, options = {}) -> testConfig or= singleRun : options.singleRun or true basePath : options.basePath - reporters : ['progress'] + reporters : [options.output or 'progress'] logLevel : 'info' frameworks : [options.framework] browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] diff --git a/src/utils.coffee b/src/utils.coffee index 298db4f..c01118a 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -1,6 +1,5 @@ path = require('path') fs = require('fs-extra') -events = require('events') utils = {} # check for windows :o(... @@ -115,10 +114,6 @@ utils.tmpl = (str, data) -> # Provide some basic currying to the user return data and fn( data ) or fn; -# ------ Setup shareable events emitter - -utils.events = new events.EventEmitter() - # ------ Formatting urls and folder paths clean = (values, sep, trimStart = false) -> From ec9dbe4366b2d09d181aabbd5634cfc2f730ee3b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Oct 2013 19:47:03 -0500 Subject: [PATCH 122/167] adding event that returns the connect app used by the server --- lib/hem.js | 4 +--- src/hem.coffee | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 89868bb..93bf32a 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -39,8 +39,6 @@ events = require('./events'); - console.log(events); - help = function() { var _ref; log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); @@ -110,7 +108,7 @@ value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; log("Starting Server at " + value + ""); app = server.start(this); - return events.emit("server-start", app); + return events.emit("server", app); }; Hem.prototype.clean = function() { diff --git a/src/hem.coffee b/src/hem.coffee index 3a7a322..5be053c 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -45,7 +45,6 @@ testing = require('./test') application = require('./package') versioning = require('./versioning') events = require('./events') -console.log events # ------- Global Functions @@ -113,7 +112,7 @@ class Hem value = "http://#{@options.hem.host or "*"}:#{@options.hem.port}" log "Starting Server at #{value}" app = server.start(@) - events.emit("server-start", app) + events.emit("server", app) clean: -> app.unlink() for app in @apps From 01381e291a5c495b0560de4b195ad15998e878bd Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 11 Oct 2013 11:17:42 -0500 Subject: [PATCH 123/167] have hem work with both json and modules.export.config --- src/hem.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index 5be053c..c17c0ba 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -103,7 +103,7 @@ class Hem # setup applications from options/slug for name, config of @options - continue if name is "hem" + continue if name is "hem" or typeof config is 'function' @allApps.push application.createApplication(name, config, @) # ------- Command Functions @@ -186,8 +186,8 @@ class Hem catch error log.errorAndExit("Couldn't load slug file #{slugPath}.") - # return portion of slug file - @slug.config + # return config portion of slug file + @slug.config or @slug getTargetApps: (targets = argv.targets) -> targetAll = targets.length is 0 From 613bf4da444a45a765151b84f2f776870e0cb830 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 11 Oct 2013 11:18:28 -0500 Subject: [PATCH 124/167] tweaks to testing configuration and some notes/comments on additional testing features. --- assets/defaults/spine.json | 5 ++--- assets/testing/index.tmpl | 3 +++ src/package.coffee | 15 ++++++++++++--- src/stitch.coffee | 4 ++++ src/test.coffee | 14 +++++++++++++- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 31f922e..00a0f1f 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -48,10 +48,9 @@ "test/specs" ], "target": "test/public/specs.js", - "after": [ + "before": [ "require('lib/setup');" - ], - "framework": "jasmine" + ] } } diff --git a/assets/testing/index.tmpl b/assets/testing/index.tmpl index cb3ed2f..dbf8c3e 100644 --- a/assets/testing/index.tmpl +++ b/assets/testing/index.tmpl @@ -10,6 +10,9 @@ (function() { function startTests() { + // javascript to run before tests + <%=before%> + // load in specs from specs.js file for (var key in <%=commonjs%>.modules) { <%=commonjs%>(key); } diff --git a/src/package.coffee b/src/package.coffee index 74ca650..325b21f 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -236,7 +236,6 @@ class JsPackage extends Package # javascript only configurations @commonjs = config.commonjs or 'require' @libs = @app.applyRootDir(config.libs or []) - @after = utils.arrayToString(config.after or "") @modules = utils.toArray(config.modules or []) compile: -> @@ -255,6 +254,12 @@ class JsPackage extends Package # javascript files. Would have to determine files needed from the stitched # files first... + # TODO: also the stitch module should keep cache of compiled code/or compile errors + # TODO: have watch pass in filepath that changed and only compile that module! have + # the stich use the cached version if possible. Should be as simple as clear cache + # of the module we want to recompile, if new file it won't have a cache... + # TODO: also for testing we can remove the specs that don't match and optional parameter + @depend or= new Dependency(@modules) _stitch = new Stitch(@src) _modules = @depend.resolve().concat(_stitch.resolve()) @@ -298,11 +303,15 @@ class TestPackage extends JsPackage super(app, config) # test configurations @depends = utils.toArray(config.depends) - @framework = config.framework + @framework = @app.hem.options.hem.tests?.framework # get test home directory based on target file location @testHome = path.dirname(@target) + # special spec to run before tests are executed + @before = utils.arrayToString(config.before or "") + + build: -> @createTestFiles() super() @@ -351,7 +360,7 @@ class TestPackage extends JsPackage files = [] files.push.apply(files, @getFrameworkFiles()) files.push.apply(files, @getAllTestTargets()) - template = utils.tmpl("testing/index", { commonjs: @commonjs, files: files } ) + template = utils.tmpl("testing/index", { commonjs: @commonjs, files: files, before: @before } ) fs.outputFileSync(indexFile, template) # copy the framework files if they aren't present diff --git a/src/stitch.coffee b/src/stitch.coffee index 1d6e966..7919c52 100644 --- a/src/stitch.coffee +++ b/src/stitch.coffee @@ -34,6 +34,10 @@ class Module @id = modulerize(@filename.replace(npath.join(@parent, npath.sep), '')) compile: -> + # TODO: need to cache results! + # TODO: need to have stich remember it's modules, so will + # need to make it a permanent variable for Stitch class + # and the application will have to hold onto it. compilers[@ext](@filename) valid: -> diff --git a/src/test.coffee b/src/test.coffee index 6382c93..c0004c8 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -22,6 +22,14 @@ run = (apps, options) -> for app in apps runTests(app, options) + # TODO: thoughts... + # 1) pass apps to the runTests method and have it loop over apps + # 2) use async to run in sequnce! + # 3) need some way to but pre/post test javascript into file for both phantom/karma + # 4) pass in argument to only require certain specs to run!! goes with #3 + # 5) use karma server once, and karma run after that, use our own watch to trigger run or + # run tests from multiple projects + # ------- Test Functions runBrowser = (app, options, done) -> @@ -58,8 +66,12 @@ runKarma = (app, options = {}) -> browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] files : createKarmaFileList(app) + # callback + callback = (exitCode) -> + process.exit(exitCode) if options.singleRun + # start testacular server - require('karma').server.start(testConfig) + require('karma').server.start(testConfig, callback) createKarmaFileList = (app) -> # get the test package and return the appropiate file list From 982a61457b526a60f9aa3b241c8b99b1d94e5eef Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 11 Oct 2013 11:18:57 -0500 Subject: [PATCH 125/167] updated js files and version bump --- lib/hem.js | 4 ++-- lib/package.js | 8 +++++--- lib/test.js | 9 +++++++-- package.json | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 93bf32a..9146629 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -96,7 +96,7 @@ _ref = this.options; for (name in _ref) { config = _ref[name]; - if (name === "hem") { + if (name === "hem" || typeof config === 'function') { continue; } this.allApps.push(application.createApplication(name, config, this)); @@ -218,7 +218,7 @@ error = _error; log.errorAndExit("Couldn't load slug file " + slugPath + "."); } - return this.slug.config; + return this.slug.config || this.slug; }; Hem.prototype.getTargetApps = function(targets) { diff --git a/lib/package.js b/lib/package.js index 71216cb..b67998a 100644 --- a/lib/package.js +++ b/lib/package.js @@ -330,7 +330,6 @@ JsPackage.__super__.constructor.call(this, app, config); this.commonjs = config.commonjs || 'require'; this.libs = this.app.applyRootDir(config.libs || []); - this.after = utils.arrayToString(config.after || ""); this.modules = utils.toArray(config.modules || []); } @@ -406,10 +405,12 @@ __extends(TestPackage, _super); function TestPackage(app, config) { + var _ref; TestPackage.__super__.constructor.call(this, app, config); this.depends = utils.toArray(config.depends); - this.framework = config.framework; + this.framework = (_ref = this.app.hem.options.hem.tests) != null ? _ref.framework : void 0; this.testHome = path.dirname(this.target); + this.before = utils.arrayToString(config.before || ""); } TestPackage.prototype.build = function() { @@ -494,7 +495,8 @@ files.push.apply(files, this.getAllTestTargets()); template = utils.tmpl("testing/index", { commonjs: this.commonjs, - files: files + files: files, + before: this.before }); fs.outputFileSync(indexFile, template); frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); diff --git a/lib/test.js b/lib/test.js index eb4be7c..7bb944d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -54,7 +54,7 @@ }; runKarma = function(app, options) { - var testConfig; + var callback, testConfig; if (options == null) { options = {}; } @@ -68,7 +68,12 @@ browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], files: createKarmaFileList(app) }); - return require('karma').server.start(testConfig); + callback = function(exitCode) { + if (options.singleRun) { + return process.exit(exitCode); + } + }; + return require('karma').server.start(testConfig, callback); }; createKarmaFileList = function(app) {}; diff --git a/package.json b/package.json index 8e1ef71..f556183 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.21", + "version": "0.4.22", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From a75dccc7fc75590f491e8aa28c5557b2006e0770 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 14 Oct 2013 16:40:32 -0500 Subject: [PATCH 126/167] making tests work in watch mode using events and async library --- lib/package.js | 5 ++- lib/phantom.js | 45 ++++++++++++++------- lib/test.js | 98 +++++++++++++++++++++++++++++++++++++--------- src/package.coffee | 6 +-- src/phantom.coffee | 50 +++++++++++++---------- src/test.coffee | 85 +++++++++++++++++++++++++++++----------- 6 files changed, 207 insertions(+), 82 deletions(-) diff --git a/lib/package.js b/lib/package.js index b67998a..70eaf18 100644 --- a/lib/package.js +++ b/lib/package.js @@ -145,7 +145,7 @@ Application.prototype.watch = function() { var dirs, pkg; - log("Watching application: " + this.name + ""); + log.info("Watching application: " + this.name + ""); dirs = (function() { var _i, _len, _ref, _results; _ref = this.packages; @@ -306,7 +306,7 @@ require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { _this.build(); - return events.emit("watch", _this, file); + return events.emit("watch", _this.app, _this, file); } }); } @@ -411,6 +411,7 @@ this.framework = (_ref = this.app.hem.options.hem.tests) != null ? _ref.framework : void 0; this.testHome = path.dirname(this.target); this.before = utils.arrayToString(config.before || ""); + this.after = ""; } TestPackage.prototype.build = function() { diff --git a/lib/phantom.js b/lib/phantom.js index b70b1ed..4d5cd22 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -8,8 +8,7 @@ phantom = require('phantom'); } catch (_error) { err = _error; - log.error("Unable to require('phantom') npm module..."); - return; + phantom = void 0; } reporters = { @@ -106,9 +105,8 @@ doIt(time - start); } if (timeout && !condition) { - console.log("ERROR - Timeout for page condition."); clearInterval(int); - return doIt(0); + return doIt(0, "Timeout for page condition."); } }; return test(testCallback); @@ -118,7 +116,7 @@ })(); jasmine_parseTestResults = function(report) { - var fails, passed, printSpecs, printSuites; + var failed, passed, printSpecs, printSuites; eval("report = " + report); printSuites = function(root, level) { level || (level = 0); @@ -145,12 +143,12 @@ }); }; printSuites($('div.jasmine_reporter')); - fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed').length; + failed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed').length; passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed').length; - window.callPhantom(report(passed, fails)); + window.callPhantom(report(passed, failed)); return { passed: passed, - fails: fails + failed: failed }; }; @@ -165,7 +163,11 @@ }; }; - run = function(filepath, options, callback) { + run = function(filepath, options, callback, port) { + if (port == null) { + port = 12300; + } + log.info("Testing file " + filepath + " on port " + port + ""); return phantom.create(function(ph) { return ph.createPage(function(page) { page.set('onConsoleMessage', function(msg) { @@ -179,8 +181,11 @@ return page.open(filepath, function(status) { var checkTestResults, complete, evalTestResults, parseTestResults, reporter; if (status !== "success") { - console.log("Cannot open URL"); ph.exit(); + callback({ + error: "Cannot open URL" + }); + return; } checkTestResults = jasmine_checkTestResults(page); parseTestResults = jasmine_parseTestResults; @@ -189,20 +194,30 @@ return typeof callback === "function" ? callback(results) : void 0; }; reporter = reporters[options.output]; - evalTestResults = function(time) { - if (time > 0) { - return page.evaluate(parseTestResults, complete, new String(reporter)); + evalTestResults = function(time, err) { + if (err) { + return complete({ + error: err + }); } else { - return ph.exit(); + return page.evaluate(parseTestResults, complete, new String(reporter)); } }; return waitFor(checkTestResults, evalTestResults); }); }); + }, { + port: port }); }; - module.exports.run = run; + if (phantom) { + module.exports.run = run; + } else { + module.exports.run = function() { + return log.error("Unable to require('phantom') npm module..."); + }; + } module.exports.reporters = reporters; diff --git a/lib/test.js b/lib/test.js index 7bb944d..937f95d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var createKarmaFileList, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils; + var async, createKarmaFileList, events, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils; fs = require('fs'); @@ -10,10 +10,14 @@ utils = require('./utils'); + events = require('./events'); + phantom = require('./phantom'); + async = require('async'); + run = function(apps, options) { - var app, runTests, _i, _len, _results; + var runTests; switch (options.runner) { case "phantom": runTests = phantom.run ? runPhantom : runBrowser; @@ -27,30 +31,86 @@ default: throw new Error("Invalid or unset test runner value: " + options.runner); } - _results = []; - for (_i = 0, _len = apps.length; _i < _len; _i++) { - app = apps[_i]; - _results.push(runTests(app, options)); - } - return _results; + return runTests(apps, options); }; - runBrowser = function(app, options, done) { - var open; + runBrowser = function(apps, options, done) { + var app, open, q, task, taskObject, tasks, testFile, testName, _i, _len, _results; open = require("open"); - return open(app.getTestPackage().getTestIndexFile()); + tasks = {}; + for (_i = 0, _len = apps.length; _i < _len; _i++) { + app = apps[_i]; + testName = app.name; + testFile = app.getTestPackage().getTestIndexFile(); + tasks[testName] = (function(testFile) { + return function(done) { + open(testFile); + return done(); + }; + })(testFile); + } + if (options.singleRun) { + return async.series(tasks); + } else { + q = async.queue((function(task, callback) { + return task(callback); + }), 1); + _results = []; + for (task in tasks) { + taskObject = tasks[task]; + _results.push(events.on("watch", function(app, pkg, file) { + return q.push(tasks[app.name]); + })); + } + return _results; + } }; - runPhantom = function(app, options, done) { - var testFile; - log("Testing application targets: " + app.name + ""); - testFile = app.getTestPackage().getTestIndexFile(); + runPhantom = function(apps, options, done) { + var app, q, task, taskObject, tasks, testFile, testName, testPort, _i, _len, _results; options.output || (options.output = "passOrFail"); - return phantom.run(testFile, options, function(results) { - if (options.singleRun) { - return process.exit(results.fails); + tasks = {}; + for (_i = 0, _len = apps.length; _i < _len; _i++) { + app = apps[_i]; + testName = app.name; + testFile = app.getTestPackage().getTestIndexFile(); + testPort = 12300 + Object.keys(tasks).length; + tasks[testName] = (function(testName, testFile, testPort) { + return function(done) { + log("Testing application targets: " + testName + ""); + return phantom.run(testFile, options, function(results) { + if (results.error) { + log.error(results.error); + } + return done(null, results); + }, testPort); + }; + })(testName, testFile, testPort); + } + if (options.singleRun) { + return async.series(tasks, function(err, results) { + var exitCode, name, result; + exitCode = 0; + for (name in results) { + result = results[name]; + exitCode += result.failed && result.failed || 0; + exitCode += result.error && 1 || 0; + } + return process.exit(exitCode); + }); + } else { + q = async.queue((function(task, callback) { + return task(callback); + }), 1); + _results = []; + for (task in tasks) { + taskObject = tasks[task]; + _results.push(events.on("watch", function(app, pkg, file) { + return q.push(tasks[app.name]); + })); } - }); + return _results; + } }; runKarma = function(app, options) { diff --git a/src/package.coffee b/src/package.coffee index 325b21f..8cd0d00 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -102,7 +102,7 @@ class Application pkg.build() for pkg in @packages watch: -> - log("Watching application: #{@name}") + log.info("Watching application: #{@name}") dirs = (pkg.watch() for pkg in @packages) # make sure dirs has valid values if dirs.length @@ -217,7 +217,7 @@ class Package require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) @build() - events.emit("watch", @, file) + events.emit("watch", @app, @, file) dirs getWatchedDirs: -> @@ -310,7 +310,7 @@ class TestPackage extends JsPackage # special spec to run before tests are executed @before = utils.arrayToString(config.before or "") - + @after = "" build: -> @createTestFiles() diff --git a/src/phantom.coffee b/src/phantom.coffee index 58936f8..3311825 100644 --- a/src/phantom.coffee +++ b/src/phantom.coffee @@ -1,12 +1,11 @@ log = require("./log") -# attempt to load the phantom module, which is an optional +# attempt to load the phantom module, which is an optional # dependency. If not available return an empty object try phantom = require 'phantom' catch err - log.error "Unable to require('phantom') npm module..." - return + phantom = undefined # ------- Test Result Formatters @@ -83,9 +82,8 @@ waitFor = (-> doIt(time - start) # THEN, no moretime but condition unfulfilled if timeout and not condition - console.log("ERROR - Timeout for page condition.") clearInterval(int) - doIt(0) + doIt(0, "Timeout for page condition.") # perform the test evaluation test(testCallback) @@ -125,14 +123,14 @@ jasmine_parseTestResults = (report) -> # our starting point printSuites($('div.jasmine_reporter')) - # handle fails - fails = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed').length + # handle results + failed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.failed').length passed = document.body.querySelectorAll('div.jasmine_reporter div.specSummary.passed').length - window.callPhantom(report(passed, fails)) + window.callPhantom(report(passed, failed)) - # return results, these will be eventually passed to the + # return results, these will be eventually passed to the # the callback function that was provided initially. - return passed: passed, fails: fails + return passed: passed, failed: failed jasmine_checkTestResults = (page) -> (checkComplete) -> @@ -141,9 +139,10 @@ jasmine_checkTestResults = (page) -> # ------- Public Functions -run = (filepath, options, callback) -> - phantom.create (ph) -> - ph.createPage (page) -> +run = (filepath, options, callback, port = 12300) -> + log.info "Testing file #{filepath} on port #{port}" + phantom.create( (ph) -> + ph.createPage( (page) -> # print console.log output from the webpage page.set('onConsoleMessage', (msg) -> console.log(msg)) @@ -155,8 +154,9 @@ run = (filepath, options, callback) -> # open the filepath and beging tests page.open filepath, (status) -> if status isnt "success" - console.log("Cannot open URL") ph.exit() + callback({ error: "Cannot open URL" }) + return # assign the appropiate framework functions checkTestResults = jasmine_checkTestResults(page) @@ -173,18 +173,28 @@ run = (filepath, options, callback) -> # function to call the parsing function, along with callback once # everything is complete and the reporter instance that is passed - # to the parseTestResults function - evalTestResults = (time) -> - if time > 0 + # to the parseTestResults function. If time parameter is 0 then + # assume that the tests never fully completed. + evalTestResults = (time, err) -> + if err + complete({error: err}) + else page.evaluate( parseTestResults, complete, new String(reporter)) - else - ph.exit() # wait for indication tests are done and then # eval/print the test results, all passing, yay! waitFor(checkTestResults, evalTestResults) + ) + , { port: port } ) # ------- Exports -module.exports.run = run +# check to make sure phantom is loaded correctly and if +# not then display an appropiate error message... +if phantom + module.exports.run = run +else + module.exports.run = -> log.error "Unable to require('phantom') npm module..." + +# export reporters object to allow custom code module.exports.reporters = reporters diff --git a/src/test.coffee b/src/test.coffee index c0004c8..02c92c6 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -2,7 +2,9 @@ fs = require('fs') path = require('path') log = require('./log') utils = require('./utils') +events = require('./events') phantom = require('./phantom') +async = require('async') # ------- Public Functions @@ -18,39 +20,76 @@ run = (apps, options) -> else throw new Error("Invalid or unset test runner value: #{options.runner}") - # need to loop over apps and run tests for each target app - for app in apps - runTests(app, options) + # loop over apps and run tests for each target app + runTests(apps, options) # TODO: thoughts... - # 1) pass apps to the runTests method and have it loop over apps - # 2) use async to run in sequnce! - # 3) need some way to but pre/post test javascript into file for both phantom/karma + # 3) need some way to set pre/post test javascript into specs file for both phantom/karma # 4) pass in argument to only require certain specs to run!! goes with #3 - # 5) use karma server once, and karma run after that, use our own watch to trigger run or + # 5) use karma server once, and karma run after that, use our own watch to trigger run or # run tests from multiple projects # ------- Test Functions -runBrowser = (app, options, done) -> - open = require("open") - open(app.getTestPackage().getTestIndexFile()) - -runPhantom = (app, options, done) -> - log("Testing application targets: #{app.name}") - testFile = app.getTestPackage().getTestIndexFile() +runBrowser = (apps, options, done) -> + open = require("open") + tasks = {} + # loop over target apps + for app in apps + testName = app.name + testFile = app.getTestPackage().getTestIndexFile() + tasks[testName] = do(testFile) -> + (done) -> + open(testFile) + done() + + # if single run then just add to async series + if options.singleRun + async.series(tasks) + else + q = async.queue( ((task, callback) -> task(callback)), 1) + for task, taskObject of tasks + events.on("watch", (app, pkg, file) -> q.push(tasks[app.name])) + +runPhantom = (apps, options, done) -> # set some other defaults options.output or= "passOrFail" + tasks = {} - # TODO: need a way for watch to work?, we can use our new event system :o) - # TODO: need a way to run tests in sequential steps... async library?? - - # run phantom - phantom.run(testFile, options, (results) -> - # exit with the number of failed tests - process.exit(results.fails) if options.singleRun - ) + # loop over apps to create test runner functions + for app in apps + testName = app.name + testFile = app.getTestPackage().getTestIndexFile() + testPort = 12300 + Object.keys(tasks).length + + # add phantom call to tasks array + tasks[testName] = do(testName, testFile, testPort) -> + (done) -> + log("Testing application targets: #{testName}") + phantom.run(testFile, options, (results) -> + log.error results.error if results.error + done(null, results) + , testPort) + + # if single run then just add to async series + if options.singleRun + async.series(tasks, (err, results) -> + exitCode = 0 + for name, result of results + exitCode += result.failed and result.failed or 0 + exitCode += result.error and 1 or 0 + process.exit(exitCode) + ) + + # else add to queue and setup watch + else + q = async.queue( ((task, callback) -> task(callback)), 1) + for task, taskObject of tasks + # setup watch and use q.push(taskObject) to assign to q + events.on("watch", (app, pkg, file) -> + q.push(tasks[app.name]) + ) runKarma = (app, options = {}) -> # use custom testacular config file provided by user @@ -67,7 +106,7 @@ runKarma = (app, options = {}) -> files : createKarmaFileList(app) # callback - callback = (exitCode) -> + callback = (exitCode) -> process.exit(exitCode) if options.singleRun # start testacular server From 4a65d5cd9d4a2b46a5fc417dab56f254f1934839 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 14 Oct 2013 17:29:57 -0500 Subject: [PATCH 127/167] print error instead of throwing an exception --- lib/test.js | 2 +- src/test.coffee | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/test.js b/lib/test.js index 937f95d..ba78649 100644 --- a/lib/test.js +++ b/lib/test.js @@ -29,7 +29,7 @@ runTests = runBrowser; break; default: - throw new Error("Invalid or unset test runner value: " + options.runner); + log.errorAndExit("Invalid or unset test runner value: " + options.runner + ""); } return runTests(apps, options); }; diff --git a/src/test.coffee b/src/test.coffee index 02c92c6..4344330 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -18,12 +18,13 @@ run = (apps, options) -> when "browser" runTests = runBrowser else - throw new Error("Invalid or unset test runner value: #{options.runner}") + log.errorAndExit("Invalid or unset test runner value: #{options.runner}") # loop over apps and run tests for each target app runTests(apps, options) # TODO: thoughts... + # 2) cache compile results!! # 3) need some way to set pre/post test javascript into specs file for both phantom/karma # 4) pass in argument to only require certain specs to run!! goes with #3 # 5) use karma server once, and karma run after that, use our own watch to trigger run or From 7d75a393ac6098a37066225d4f2518ed7559c077 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 21 Oct 2013 20:04:18 -0500 Subject: [PATCH 128/167] adding some compile caching to hem in server/watch mode --- lib/hem.js | 11 +++++- lib/package.js | 28 +++++++------- lib/server.js | 2 +- lib/stitch.js | 91 +++++++++++++++++++++++++++------------------- src/hem.coffee | 2 + src/package.coffee | 38 +++++++++++-------- src/server.coffee | 2 +- src/stitch.coffee | 69 ++++++++++++++++++++++------------- src/test.coffee | 2 +- 9 files changed, 147 insertions(+), 98 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 9146629..058cb4f 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -104,11 +104,18 @@ } Hem.prototype.server = function() { - var app, value; + var app, value, _i, _len, _ref, _results; value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; log("Starting Server at " + value + ""); app = server.start(this); - return events.emit("server", app); + events.emit("server", app); + _ref = this.apps; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + app = _ref[_i]; + _results.push(app.watch()); + } + return _results; }; Hem.prototype.clean = function() { diff --git a/lib/package.js b/lib/package.js index 70eaf18..f49138e 100644 --- a/lib/package.js +++ b/lib/package.js @@ -121,7 +121,7 @@ Application.prototype.unlink = function() { var pkg, _i, _len, _ref, _results; - log("Removing application targets: " + this.name + ""); + log("Removing application: " + this.name + ""); _ref = this.packages; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -133,7 +133,7 @@ Application.prototype.build = function() { var pkg, _i, _len, _ref, _results; - log("Building application targets: " + this.name + ""); + log("Building application: " + this.name + ""); _ref = this.packages; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -145,7 +145,7 @@ Application.prototype.watch = function() { var dirs, pkg; - log.info("Watching application: " + this.name + ""); + log("Watching application: " + this.name + ""); dirs = (function() { var _i, _len, _ref, _results; _ref = this.packages; @@ -247,7 +247,8 @@ log.error(ex.path); } switch (argv.command) { - case "server" || "watch": + case "server": + case "watch": return "console.log(\"HEM compile ERROR: " + ex + "\n" + ex.path + "\");"; default: return process.exit(1); @@ -261,14 +262,15 @@ } }; - Package.prototype.build = function(write) { - var dirname, extra, source; - if (write == null) { - write = true; + Package.prototype.build = function(moduleToRefresh) { + var dirname, extra, source, write; + if (moduleToRefresh) { + Stitch.clear(moduleToRefresh); } extra = (argv.compress && " --using compression") || ""; log.info("- Building target: " + this.target + "" + extra); source = this.compile(); + write = argv.command !== "server"; if (source && write) { dirname = path.dirname(this.target); if (!fs.existsSync(dirname)) { @@ -305,7 +307,7 @@ dir = dirs[_j]; require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { - _this.build(); + _this.build(file); return events.emit("watch", _this.app, _this, file); } }); @@ -350,12 +352,12 @@ }; JsPackage.prototype.compileModules = function() { - var _modules, _stitch; + var _modules; this.depend || (this.depend = new Dependency(this.modules)); - _stitch = new Stitch(this.src); - _modules = this.depend.resolve().concat(_stitch.resolve()); + this.stitch || (this.stitch = new Stitch(this.src)); + _modules = this.depend.resolve().concat(this.stitch.resolve()); if (_modules) { - return _stitch.template(this.commonjs, _modules); + return Stitch.template(this.commonjs, _modules); } else { return ""; } diff --git a/lib/server.js b/lib/server.js index 5f1b426..9e63235 100644 --- a/lib/server.js +++ b/lib/server.js @@ -101,7 +101,7 @@ for (_m = 0, _len4 = _ref6.length; _m < _len4; _m++) { app = _ref6[_m]; if (pkg = app.isMatchingRoute(url)) { - str = pkg.build(false); + str = pkg.build(); res.charset = 'utf-8'; res.setHeader('Content-Type', mime.lookup(pkg.target)); res.setHeader('Content-Length', Buffer.byteLength(str)); diff --git a/lib/stitch.js b/lib/stitch.js index ac19a53..1a95f62 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Module, Stitch, compilers, flatten, fs, modulerize, npath; + var Module, Stitch, compilers, flatten, fs, modulerize, npath, _createModule, _modules, _walk; npath = require('path'); @@ -12,7 +12,57 @@ flatten = require('./utils').flatten; + _modules = {}; + + _walk = function(path, parent, result) { + var child, module, stat, _i, _len, _ref; + if (parent == null) { + parent = path; + } + if (result == null) { + result = []; + } + if (!fs.existsSync(path)) { + return; + } + _ref = fs.readdirSync(path); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + child = npath.join(path, child); + stat = fs.statSync(child); + if (stat.isDirectory()) { + _walk(child, parent, result); + } else { + module = _createModule(child, parent); + if (module.valid()) { + result.push(module); + } + } + } + return result; + }; + + _createModule = function(child, parent) { + if (!_modules[child]) { + _modules[child] = new Module(child, parent); + } + return _modules[child]; + }; + Stitch = (function() { + Stitch.template = function(identifier, modules) { + var context; + context = { + identifier: identifier, + modules: modules + }; + return require('./utils').tmpl("stitch", context); + }; + + Stitch.clear = function(filename) { + return delete _modules[npath.resolve(filename)]; + }; + function Stitch(paths) { var path; this.paths = paths != null ? paths : []; @@ -36,47 +86,12 @@ _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { path = _ref[_i]; - _results.push(this.walk(path)); + _results.push(_walk(path)); } return _results; }).call(this)); }; - Stitch.prototype.walk = function(path, parent, result) { - var child, module, stat, _i, _len, _ref; - if (parent == null) { - parent = path; - } - if (result == null) { - result = []; - } - if (!fs.existsSync(path)) { - return; - } - _ref = fs.readdirSync(path); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - child = _ref[_i]; - child = npath.join(path, child); - stat = fs.statSync(child); - if (stat.isDirectory()) { - this.walk(child, parent, result); - } else { - module = new Module(child, parent); - if (module.valid()) { - result.push(module); - } - } - } - return result; - }; - - Stitch.prototype.template = function(identifier, modules) { - return require('./utils').tmpl("stitch", { - identifier: identifier, - modules: modules - }); - }; - return Stitch; })(); @@ -90,7 +105,7 @@ } Module.prototype.compile = function() { - return compilers[this.ext](this.filename); + return this._compiled || (this._compiled = compilers[this.ext](this.filename)); }; Module.prototype.valid = function() { diff --git a/src/hem.coffee b/src/hem.coffee index c17c0ba..aaa8849 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -113,6 +113,8 @@ class Hem log "Starting Server at #{value}" app = server.start(@) events.emit("server", app) + # make sure watch is going to recompile immediately + app.watch() for app in @apps clean: -> app.unlink() for app in @apps diff --git a/src/package.coffee b/src/package.coffee index 8cd0d00..6cbd2cd 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -94,15 +94,15 @@ class Application return unlink: -> - log("Removing application targets: #{@name}") + log("Removing application: #{@name}") pkg.unlink() for pkg in @packages build: -> - log("Building application targets: #{@name}") + log("Building application: #{@name}") pkg.build() for pkg in @packages watch: -> - log.info("Watching application: #{@name}") + log("Watching application: #{@name}") dirs = (pkg.watch() for pkg in @packages) # make sure dirs has valid values if dirs.length @@ -180,8 +180,8 @@ class Package log.error(ex.path) if ex.path # only return when in server/watch mode, otherwise exit switch argv.command - when "server" or "watch" - # TODO: only return this for javascript + when "server", "watch" + # TODO: only return this for javascript errors return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" else process.exit(1) @@ -191,10 +191,16 @@ class Package log.info "- removing #{@target}" fs.unlinkSync(@target) - build: (write = true) -> - extra = (argv.compress and " --using compression") or "" + build: (moduleToRefresh) -> + # remove the files module from Stitch so its recompiled + Stitch.clear(moduleToRefresh) if moduleToRefresh + # extrea logging + extra = (argv.compress and " --using compression") or "" log.info("- Building target: #{@target}#{extra}") + # compile source source = @compile() + # determine if we need to write to filesystem + write = argv.command isnt "server" if source and write dirname = path.dirname(@target) fs.mkdirsSync(dirname) unless fs.existsSync(dirname) @@ -216,8 +222,11 @@ class Package for dir in dirs require('watch').watchTree dir, watchOptions, (file, curr, prev) => if curr and (curr.nlink is 0 or +curr.mtime isnt +prev?.mtime) - @build() + # peform recompile + @build(file) + # emit watch event events.emit("watch", @app, @, file) + # return dirs that are watched dirs getWatchedDirs: -> @@ -254,24 +263,21 @@ class JsPackage extends Package # javascript files. Would have to determine files needed from the stitched # files first... - # TODO: also the stitch module should keep cache of compiled code/or compile errors - # TODO: have watch pass in filepath that changed and only compile that module! have - # the stich use the cached version if possible. Should be as simple as clear cache - # of the module we want to recompile, if new file it won't have a cache... # TODO: also for testing we can remove the specs that don't match and optional parameter @depend or= new Dependency(@modules) - _stitch = new Stitch(@src) - _modules = @depend.resolve().concat(_stitch.resolve()) + @stitch or= new Stitch(@src) + _modules = @depend.resolve().concat(@stitch.resolve()) if _modules - _stitch.template(@commonjs, _modules) + Stitch.template(@commonjs, _modules) else "" compileLibs: (files = @libs, parentDir = "") -> # TODO: need to perform similar operation as stitch in that only - # compilable code is used... + # compilable code is used... refactor Stitch class to handle this?? except + # we don't want the code actually stitched in a template, just plain old js # check if folder or file results = [] diff --git a/src/server.coffee b/src/server.coffee index c4efbe4..4d0304d 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -82,7 +82,7 @@ server.middleware = (hem) -> if url.match(/(\.js|\.css)$/) for app in hem.apps if pkg = app.isMatchingRoute(url) - str = pkg.build(false) + str = pkg.build() res.charset = 'utf-8' res.setHeader('Content-Type', mime.lookup(pkg.target)) res.setHeader('Content-Length', Buffer.byteLength(str)) diff --git a/src/stitch.coffee b/src/stitch.coffee index 7919c52..4694710 100644 --- a/src/stitch.coffee +++ b/src/stitch.coffee @@ -4,42 +4,59 @@ compilers = require('./compilers') {modulerize} = require('./resolve') {flatten} = require('./utils') + +## --- Private + +_modules = {} + +_walk = (path, parent = path, result = []) -> + return unless fs.existsSync(path) + for child in fs.readdirSync(path) + child = npath.join(path, child) + stat = fs.statSync(child) + if stat.isDirectory() + _walk(child, parent, result) + else + module = _createModule(child, parent) + result.push(module) if module.valid() + result + +_createModule = (child, parent) -> + if not _modules[child] + _modules[child] = new Module(child, parent) + _modules[child] + +## --- classes + class Stitch - constructor: (@paths = []) -> - @paths = (npath.resolve(path) for path in @paths) - - resolve: -> - flatten(@walk(path) for path in @paths) - # Private + ## --- class methods + + @template: (identifier, modules) -> + context = + identifier : identifier + modules : modules + require('./utils').tmpl("stitch", context ) - walk: (path, parent = path, result = []) -> - return unless fs.existsSync(path) - for child in fs.readdirSync(path) - child = npath.join(path, child) - stat = fs.statSync(child) - if stat.isDirectory() - @walk(child, parent, result) - else - module = new Module(child, parent) - result.push(module) if module.valid() - result + @clear: (filename) -> + delete _modules[npath.resolve(filename)] - template: (identifier, modules) -> - require('./utils').tmpl("stitch", { identifier: identifier, modules: modules } ) + ## --- instance methods + + constructor: (@paths = []) -> + @paths = (npath.resolve(path) for path in @paths) + + resolve: -> + flatten(_walk(path) for path in @paths) class Module constructor: (@filename, @parent) -> @ext = npath.extname(@filename).slice(1) @id = modulerize(@filename.replace(npath.join(@parent, npath.sep), '')) - + compile: -> - # TODO: need to cache results! - # TODO: need to have stich remember it's modules, so will - # need to make it a permanent variable for Stitch class - # and the application will have to hold onto it. - compilers[@ext](@filename) - + @_compiled or= compilers[@ext](@filename) + valid: -> !!compilers[@ext] diff --git a/src/test.coffee b/src/test.coffee index 4344330..241d34c 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -24,8 +24,8 @@ run = (apps, options) -> runTests(apps, options) # TODO: thoughts... - # 2) cache compile results!! # 3) need some way to set pre/post test javascript into specs file for both phantom/karma + # 2) cache compile results!! # 4) pass in argument to only require certain specs to run!! goes with #3 # 5) use karma server once, and karma run after that, use our own watch to trigger run or # run tests from multiple projects From b64e2c34cffb99030acd08bcf13b74c724188afd Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 24 Oct 2013 14:43:07 -0500 Subject: [PATCH 129/167] getting karma close to working and tweaking some of the hem default configurations. --- assets/defaults/spine.json | 5 +- assets/testing/index.tmpl | 5 -- lib/compilers.js | 8 +-- lib/hem.js | 32 +++++---- lib/package.js | 115 ++++++++++++++++++++------------ lib/test.js | 49 +++++++++----- lib/utils.js | 3 - package.json | 2 +- src/compilers.coffee | 7 +- src/hem.coffee | 32 +++++---- src/package.coffee | 131 ++++++++++++++++++++++++------------- src/test.coffee | 44 +++++++------ src/utils.coffee | 2 +- 13 files changed, 261 insertions(+), 174 deletions(-) diff --git a/assets/defaults/spine.json b/assets/defaults/spine.json index 00a0f1f..d505b60 100644 --- a/assets/defaults/spine.json +++ b/assets/defaults/spine.json @@ -47,10 +47,7 @@ "src": [ "test/specs" ], - "target": "test/public/specs.js", - "before": [ - "require('lib/setup');" - ] + "target": "test/public/specs.js" } } diff --git a/assets/testing/index.tmpl b/assets/testing/index.tmpl index dbf8c3e..e556847 100644 --- a/assets/testing/index.tmpl +++ b/assets/testing/index.tmpl @@ -10,11 +10,6 @@ (function() { function startTests() { - // javascript to run before tests - <%=before%> - - // load in specs from specs.js file - for (var key in <%=commonjs%>.modules) { <%=commonjs%>(key); } // create jasmine environment var jasmineEnv = jasmine.getEnv(); diff --git a/lib/compilers.js b/lib/compilers.js index e9ceb12..0097ebb 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var argv, compileCoffeescript, compilers, cs, fs, lmCache, log, path, projectPath, requireLocalModule; + var compileCoffeescript, compilers, cs, fs, lmCache, log, path, projectPath, requireLocalModule; fs = require('fs'); @@ -8,12 +8,12 @@ log = require('./log'); - argv = require('./utils').ARGV; - compilers = {}; lmCache = {}; + compilers.argv = {}; + projectPath = path.resolve(process.cwd()); requireLocalModule = function(localModule, _path) { @@ -127,7 +127,7 @@ try { template = jade.compile(content, { filename: _path, - compileDebug: argv.command === "server", + compileDebug: this.argv.command === "server", client: true }); source = template.toString(); diff --git a/lib/hem.js b/lib/hem.js index 058cb4f..7a833c3 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -9,7 +9,7 @@ optimist = require('optimist'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; argv.command = argv._[0]; @@ -25,8 +25,6 @@ utils = require('./utils'); - utils.ARGV = argv; - compilers = require('./compilers'); server = require('./server'); @@ -39,6 +37,10 @@ events = require('./events'); + compilers.argv = argv; + + application.argv = argv; + help = function() { var _ref; log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); @@ -57,13 +59,6 @@ return server.middleware(hem); }; - Hem.defaults = { - hem: { - port: 9294, - host: "localhost" - } - }; - Hem.prototype.options = {}; Hem.prototype.apps = []; @@ -73,7 +68,7 @@ Hem.prototype.home = process.cwd(); function Hem(options) { - var config, name, slug, _base, _base1, _base2, _ref; + var config, name, slug, _base, _base1, _base2, _base3, _base4, _base5, _base6, _ref; switch (typeof options) { case "string": slug = options; @@ -88,18 +83,25 @@ this.options = this.readSlug(slug); } (_base = this.options).hem || (_base.hem = {}); - (_base1 = this.options.hem).port || (_base1.port = Hem.defaults.hem.port); - (_base2 = this.options.hem).host || (_base2.host = Hem.defaults.hem.host); + (_base1 = this.options.hem).port || (_base1.port = 9294); + (_base2 = this.options.hem).host || (_base2.host = "localhost"); + (_base3 = this.options.hem).test || (_base3.test = {}); + (_base4 = this.options.hem.test).runner || (_base4.runner = "karma"); + (_base5 = this.options.hem.test).reporters || (_base5.reporters = "progress"); + (_base6 = this.options.hem.test).frameworks || (_base6.frameworks = "jasmine"); if (argv.port) { this.options.hem.port = argv.port; } + if (argv.runner) { + this.options.hem.test.runner = argv.runner; + } _ref = this.options; for (name in _ref) { config = _ref[name]; if (name === "hem" || typeof config === 'function') { continue; } - this.allApps.push(application.createApplication(name, config, this)); + this.allApps.push(application.create(name, config, this, argv)); } } @@ -266,6 +268,8 @@ return testing.phantom.reporters; case "versioning": return versioning; + case "log": + return log; default: throw new Error("Unknown module name " + name); } diff --git a/lib/package.js b/lib/package.js index f49138e..9a278a4 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.6.3 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, argv, createApplication, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, create, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, _argv, _hem, __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -19,21 +19,22 @@ utils = require('./utils'); - argv = require('./utils').ARGV; - events = require('./events'); log = require('./log'); versioning = require('./versioning'); + _hem = void 0; + + _argv = void 0; + Application = (function() { - function Application(name, config, hem) { + function Application(name, config) { var defaults, err, key, loadedDefaults, packager, route, value, verType, _ref; if (config == null) { config = {}; } - this.hem = hem; this.name = name; this.route = config.route; this.root = config.root; @@ -236,20 +237,30 @@ } } } - if (argv.command === "server" && !this.route) { + if (_argv.command === "server" && !this.route) { log.errorAndExit("Unable to determine route for " + this.target + ""); } } Package.prototype.handleCompileError = function(ex) { + if (_hem.handleCompileError) { + _hem.handleCompileError(ex); + return; + } log.error(ex.message); if (ex.path) { log.error(ex.path); } - switch (argv.command) { + switch (_argv.command) { case "server": + if (this.ext === "js") { + return "alert(\"HEM: " + ex + "\\n\\n" + ex.path + "\");"; + } else { + return ""; + } + break; case "watch": - return "console.log(\"HEM compile ERROR: " + ex + "\n" + ex.path + "\");"; + return ""; default: return process.exit(1); } @@ -262,15 +273,15 @@ } }; - Package.prototype.build = function(moduleToRefresh) { + Package.prototype.build = function(file) { var dirname, extra, source, write; - if (moduleToRefresh) { - Stitch.clear(moduleToRefresh); + if (file) { + Stitch.clear(file); } - extra = (argv.compress && " --using compression") || ""; + extra = (_argv.compress && " --using compression") || ""; log.info("- Building target: " + this.target + "" + extra); source = this.compile(); - write = argv.command !== "server"; + write = _argv.command !== "server"; if (source && write) { dirname = path.dirname(this.target); if (!fs.existsSync(dirname)) { @@ -333,13 +344,15 @@ this.commonjs = config.commonjs || 'require'; this.libs = this.app.applyRootDir(config.libs || []); this.modules = utils.toArray(config.modules || []); + this.before = utils.arrayToString(config.before || ""); + this.after = utils.arrayToString(config.after || ""); } JsPackage.prototype.compile = function() { var ex, result; try { - result = [this.compileLibs(), this.compileModules(), this.after].join("\n"); - if (argv.compress) { + result = [this.before, this.compileLibs(), this.compileModules(), this.after].join("\n"); + if (_argv.compress) { result = uglifyjs.minify(result, { fromString: true }).code; @@ -410,39 +423,52 @@ var _ref; TestPackage.__super__.constructor.call(this, app, config); this.depends = utils.toArray(config.depends); - this.framework = (_ref = this.app.hem.options.hem.tests) != null ? _ref.framework : void 0; this.testHome = path.dirname(this.target); - this.before = utils.arrayToString(config.before || ""); - this.after = ""; + this.framework = _hem.options.hem.test.frameworks; + if ((_ref = this.framework) !== 'jasmine' && _ref !== 'mocha') { + log.errorAndExit("Test frameworks value is not valid: " + this.framework); + } + this.after += "// HEM: load in specs from test js file\nvar onlyMatchingModules = \"" + (_argv.grep || "") + "\";\nfor (var key in " + this.commonjs + ".modules) {\n if (onlyMatchingModules && key.indexOf(onlyMatchingModules) == -1) {\n continue;\n }\n " + this.commonjs + "(key); \n}"; } - TestPackage.prototype.build = function() { + TestPackage.prototype.build = function(file) { this.createTestFiles(); - return TestPackage.__super__.build.call(this); + return TestPackage.__super__.build.call(this, file); }; - TestPackage.prototype.getAllTestTargets = function() { - var dep, depapp, homeRoute, pkg, pth, targets, url, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3; + TestPackage.prototype.getAllTestTargets = function(relative) { + var dep, depapp, homeRoute, pkg, pth, relativeFn, targets, url, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3; + if (relative == null) { + relative = true; + } targets = []; homeRoute = path.dirname(this.route); + relativeFn = function(home, target) { + if (relative) { + return path.relative(home, target); + } else { + return target; + } + }; _ref = this.depends; for (_i = 0, _len = _ref.length; _i < _len; _i++) { dep = _ref[_i]; - _ref1 = this.app.hem.allApps; + _ref1 = _hem.allApps; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { depapp = _ref1[_j]; if (depapp.name === dep) { _ref2 = depapp.packages; for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { pkg = _ref2[_k]; - if (pkg.constructor.name === "JsPackage") { - url = path.relative(homeRoute, pkg.route); - pth = path.relative(this.testHome, pkg.target); - targets.push({ - url: url, - path: pth - }); + if (pkg.constructor.name !== "JsPackage") { + continue; } + url = relativeFn(homeRoute, pkg.route); + pth = relativeFn(this.testHome, pkg.target); + targets.push({ + url: url, + path: pth + }); } } } @@ -450,17 +476,18 @@ _ref3 = this.app.packages; for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { pkg = _ref3[_l]; - if (pkg.constructor.name === "JsPackage") { - url = path.relative(homeRoute, pkg.route); - pth = path.relative(this.testHome, pkg.target); - targets.push({ - url: url, - path: pth - }); + if (pkg.constructor.name !== "JsPackage") { + continue; } + url = relativeFn(homeRoute, pkg.route); + pth = relativeFn(this.testHome, pkg.target); + targets.push({ + url: url, + path: pth + }); } - url = path.relative(homeRoute, pkg.route); - pth = path.relative(this.testHome, pkg.target); + url = relativeFn(homeRoute, pkg.route); + pth = relativeFn(this.testHome, pkg.target); targets.push({ url: url, path: pth @@ -555,7 +582,7 @@ } } result = output.join("\n"); - if (argv.compress) { + if (_argv.compress) { result = uglifycss.processString(result); } return result; @@ -571,10 +598,12 @@ })(Package); - createApplication = function(name, config, hem) { - return new Application(name, config, hem); + create = function(name, config, hem, argv) { + _hem || (_hem = hem); + _argv || (_argv = argv); + return new Application(name, config); }; - module.exports.createApplication = createApplication; + module.exports.create = create; }).call(this); diff --git a/lib/test.js b/lib/test.js index ba78649..b0e5dbb 100644 --- a/lib/test.js +++ b/lib/test.js @@ -113,30 +113,43 @@ } }; - runKarma = function(app, options) { - var callback, testConfig; + runKarma = function(apps, options) { + var app, callback, testConfig, _i, _len, _results; if (options == null) { options = {}; } - testConfig = fs.existsSync(options.config) && fs.realpathSync(options.config); - testConfig || (testConfig = { - singleRun: options.singleRun || true, - basePath: options.basePath, - reporters: [options.output || 'progress'], - logLevel: 'info', - frameworks: [options.framework], - browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], - files: createKarmaFileList(app) - }); - callback = function(exitCode) { - if (options.singleRun) { + _results = []; + for (_i = 0, _len = apps.length; _i < _len; _i++) { + app = apps[_i]; + testConfig = { + singleRun: options.singleRun, + basePath: options.basePath, + reporters: [options.reporters || 'progress'], + logLevel: options.logLevel || 'error', + frameworks: [options.frameworks || 'jasmine'], + browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], + files: createKarmaFileList(app), + autoWatch: true + }; + callback = function(exitCode) { + console.log("done!!"); return process.exit(exitCode); - } - }; - return require('karma').server.start(testConfig, callback); + }; + _results.push(require('karma').server.start(testConfig, callback)); + } + return _results; }; - createKarmaFileList = function(app) {}; + createKarmaFileList = function(app) { + var files, target, _i, _len, _ref; + files = []; + _ref = app.getTestPackage().getAllTestTargets(false); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + target = _ref[_i]; + files.push(target.path); + } + return files; + }; module.exports.run = run; diff --git a/lib/utils.js b/lib/utils.js index 705b66e..15ac94c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -30,9 +30,6 @@ utils.arrayToString = function(value) { var line, result, _i, _len; - if (value == null) { - value = ""; - } if (Array.isArray(value)) { result = ""; for (_i = 0, _len = value.length; _i < _len; _i++) { diff --git a/package.json b/package.json index f556183..0d8219c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.22", + "version": "0.4.23", "description": "stiches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/compilers.coffee b/src/compilers.coffee index 5c458aa..23abf2f 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -1,10 +1,13 @@ fs = require('fs') path = require('path') log = require('./log') -argv = require('./utils').ARGV compilers = {} lmCache = {} +# argv is set when hem first loads up + +compilers.argv = {} + # Load the modules from the project directory (instead of from the hem # node_modules). This allows a lot of the different javascript/css pre # compilers to be installed in the project vs having to be included with @@ -124,7 +127,7 @@ compilers.jade = (_path) -> try template = jade.compile content, filename: _path - compileDebug: argv.command is "server" + compileDebug: @argv.command is "server" client: true source = template.toString() "module.exports = #{source};" diff --git a/src/hem.coffee b/src/hem.coffee index aaa8849..714a85c 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -20,6 +20,8 @@ argv = optimist.usage([ .alias('s', 'slug').describe('s',':run hem using a specified slug file') .alias('n', 'nocolors').describe('n',':disable color in console output') .alias('v', 'verbose').describe('v',':make hem more talkative(verbose)') +.alias('g', 'grep').describe('g',':only run specific modules during test') +.alias('r', 'runner').describe('r',':override the default test runner') .argv # set command and targets properties @@ -33,12 +35,9 @@ require("sty").disable() if !!argv.nocolors log = require('./log') log.VERBOSE = argv.v = !!argv.v -# save argv to utils class to allow access by other modules -utils = require('./utils') -utils.ARGV = argv - # ------- perform requires +utils = require('./utils') compilers = require('./compilers') server = require('./server') testing = require('./test') @@ -46,6 +45,11 @@ application = require('./package') versioning = require('./versioning') events = require('./events') +# supply argv object to module + +compilers.argv = argv +application.argv = argv + # ------- Global Functions help = -> @@ -64,12 +68,6 @@ class Hem hem = new Hem(slug) server.middleware(hem) - # default values for server - @defaults: - hem: - port: 9294 - host: "localhost" - # ------- instance variables # emtpy options map and applications list @@ -95,16 +93,23 @@ class Hem # make sure some defaults are present @options.hem or= {} - @options.hem.port or= Hem.defaults.hem.port - @options.hem.host or= Hem.defaults.hem.host + @options.hem.port or= 9294 + @options.hem.host or= "localhost" + + # test defaults + @options.hem.test or= {} + @options.hem.test.runner or= "karma" + @options.hem.test.reporters or= "progress" + @options.hem.test.frameworks or= "jasmine" # allow overrides from command line @options.hem.port = argv.port if argv.port + @options.hem.test.runner = argv.runner if argv.runner # setup applications from options/slug for name, config of @options continue if name is "hem" or typeof config is 'function' - @allApps.push application.createApplication(name, config, @) + @allApps.push application.create(name, config, @, argv) # ------- Command Functions @@ -204,6 +209,7 @@ class Hem when "events" then events when "reporters" then testing.phantom.reporters when "versioning" then versioning + when "log" then log else throw new Error("Unknown module name #{name}") diff --git a/src/package.coffee b/src/package.coffee index 6cbd2cd..beb8d8d 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -5,17 +5,19 @@ uglifycss = require('uglifycss') Dependency = require('./dependency') Stitch = require('./stitch') utils = require('./utils') -argv = require('./utils').ARGV events = require('./events') log = require('./log') versioning = require('./versioning') +# ------- Variables set by hem during startup + +_hem = undefined +_argv = undefined # ------- Application Class class Application - constructor: (name, config = {}, hem) -> - @hem = hem + constructor: (name, config = {}) -> @name = name @route = config.route @root = config.root @@ -170,19 +172,28 @@ class Package @route = utils.cleanRoute(route.url, targetUrl) # make sure we have a route to use when using server command - if argv.command is "server" and not @route + if _argv.command is "server" and not @route log.errorAndExit("Unable to determine route for #{@target}") handleCompileError: (ex) -> + # check for method on _hem to allow override of behavior + if _hem.handleCompileError + _hem.handleCompileError(ex) + return + # TODO: construct better error message, one that works for all precompilers, - # having some problems with sty here, hmmm.... log.error(ex.message) log.error(ex.path) if ex.path + # only return when in server/watch mode, otherwise exit - switch argv.command - when "server", "watch" - # TODO: only return this for javascript errors - return "console.log(\"HEM compile ERROR: #{ex}\n#{ex.path}\");" + switch _argv.command + when "server" + if @ext is "js" + return "alert(\"HEM: #{ex}\\n\\n#{ex.path}\");" + else + return "" + when "watch" + return "" else process.exit(1) @@ -191,16 +202,16 @@ class Package log.info "- removing #{@target}" fs.unlinkSync(@target) - build: (moduleToRefresh) -> + build: (file) -> # remove the files module from Stitch so its recompiled - Stitch.clear(moduleToRefresh) if moduleToRefresh + Stitch.clear(file) if file # extrea logging - extra = (argv.compress and " --using compression") or "" + extra = (_argv.compress and " --using compression") or "" log.info("- Building target: #{@target}#{extra}") # compile source source = @compile() # determine if we need to write to filesystem - write = argv.command isnt "server" + write = _argv.command isnt "server" if source and write dirname = path.dirname(@target) fs.mkdirsSync(dirname) unless fs.existsSync(dirname) @@ -243,14 +254,18 @@ class JsPackage extends Package super(app, config) # javascript only configurations - @commonjs = config.commonjs or 'require' - @libs = @app.applyRootDir(config.libs or []) - @modules = utils.toArray(config.modules or []) + @commonjs = config.commonjs or 'require' + @libs = @app.applyRootDir(config.libs or []) + @modules = utils.toArray(config.modules or []) + + # javascript to add before/after the stitch file + @before = utils.arrayToString(config.before or "") + @after = utils.arrayToString(config.after or "") compile: -> try - result = [@compileLibs(), @compileModules(), @after].join("\n") - result = uglifyjs.minify(result, {fromString: true}).code if argv.compress + result = [@before, @compileLibs(), @compileModules(), @after].join("\n") + result = uglifyjs.minify(result, {fromString: true}).code if _argv.compress result catch ex @handleCompileError(ex) @@ -309,42 +324,62 @@ class TestPackage extends JsPackage super(app, config) # test configurations @depends = utils.toArray(config.depends) - @framework = @app.hem.options.hem.tests?.framework # get test home directory based on target file location - @testHome = path.dirname(@target) - - # special spec to run before tests are executed - @before = utils.arrayToString(config.before or "") - @after = "" - - build: -> + @testHome = path.dirname(@target) + @framework = _hem.options.hem.test.frameworks + + # test to make sure framework is set correctly + if @framework not in ['jasmine','mocha'] + log.errorAndExit("Test frameworks value is not valid: #{@framework}") + + # javascript to run at end of specs file + @after += + """ + // HEM: load in specs from test js file + var onlyMatchingModules = \"#{_argv.grep or ""}\"; + for (var key in #{@commonjs}.modules) { + if (onlyMatchingModules && key.indexOf(onlyMatchingModules) == -1) { + continue; + } + #{@commonjs}(key); + } + """ + + build: (file) -> @createTestFiles() - super() + super(file) - getAllTestTargets: -> - targets = [] + getAllTestTargets: (relative = true) -> + targets = [] homeRoute = path.dirname(@route) + # create function to determine route/path + relativeFn = (home, target) -> + if relative + path.relative(home, target) + else + target + # first get dependencies for dep in @depends - for depapp in @app.hem.allApps when depapp.name is dep + for depapp in _hem.allApps when depapp.name is dep for pkg in depapp.packages - if pkg.constructor.name is "JsPackage" - url = path.relative(homeRoute, pkg.route) - pth = path.relative(@testHome, pkg.target) - targets.push({ url: url, path: pth }) + continue unless pkg.constructor.name is "JsPackage" + url = relativeFn(homeRoute, pkg.route) + pth = relativeFn(@testHome, pkg.target) + targets.push({ url: url, path: pth }) # get app targets for pkg in @app.packages - if pkg.constructor.name is "JsPackage" - url = path.relative(homeRoute, pkg.route) - pth = path.relative(@testHome, pkg.target) - targets.push({ url: url, path: pth }) - - # finally test file - url = path.relative(homeRoute, pkg.route) - pth = path.relative(@testHome, pkg.target) + continue unless pkg.constructor.name is "JsPackage" + url = relativeFn(homeRoute, pkg.route) + pth = relativeFn(@testHome, pkg.target) + targets.push({ url: url, path: pth }) + + # finally add main test target file + url = relativeFn(homeRoute, pkg.route) + pth = relativeFn(@testHome, pkg.target) targets.push({ url: url, path: pth }) targets @@ -360,6 +395,8 @@ class TestPackage extends JsPackage getTestIndexFile: -> path.resolve(@testHome,'index.html') + # TODO: only do this for browser tests??? + createTestFiles: -> # create index file indexFile = @getTestIndexFile() @@ -403,7 +440,7 @@ class CssPackage extends Package # join and minify result = output.join("\n") - result = uglifycss.processString(result) if argv.compress + result = uglifycss.processString(result) if _argv.compress result catch ex @handleCompileError(ex) @@ -412,9 +449,11 @@ class CssPackage extends Package # ------- Public Functions -createApplication = (name, config, hem) -> - return new Application(name, config, hem) +create = (name, config, hem, argv) -> + _hem or= hem + _argv or= argv + return new Application(name, config) -module.exports.createApplication = createApplication +module.exports.create = create diff --git a/src/test.coffee b/src/test.coffee index 241d34c..6a050c7 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -25,7 +25,6 @@ run = (apps, options) -> # TODO: thoughts... # 3) need some way to set pre/post test javascript into specs file for both phantom/karma - # 2) cache compile results!! # 4) pass in argument to only require certain specs to run!! goes with #3 # 5) use karma server once, and karma run after that, use our own watch to trigger run or # run tests from multiple projects @@ -92,29 +91,34 @@ runPhantom = (apps, options, done) -> q.push(tasks[app.name]) ) -runKarma = (app, options = {}) -> - # use custom testacular config file provided by user - testConfig = fs.existsSync(options.config) and fs.realpathSync(options.config) - - # create config file to pass into server if user doesn't supply a file to use - testConfig or= - singleRun : options.singleRun or true - basePath : options.basePath - reporters : [options.output or 'progress'] - logLevel : 'info' - frameworks : [options.framework] - browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] - files : createKarmaFileList(app) +runKarma = (apps, options = {}) -> + + for app in apps - # callback - callback = (exitCode) -> - process.exit(exitCode) if options.singleRun + # create config file to pass into server if user doesn't supply a file to use + testConfig = + singleRun : options.singleRun + basePath : options.basePath + reporters : [options.reporters or 'progress'] + logLevel : options.logLevel or 'error' + frameworks : [options.frameworks or 'jasmine'] + browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] + files : createKarmaFileList(app) + autoWatch : true + + # callback + callback = (exitCode) -> + console.log "done!!" + process.exit(exitCode) - # start testacular server - require('karma').server.start(testConfig, callback) + # start testacular server + require('karma').server.start(testConfig, callback) createKarmaFileList = (app) -> - # get the test package and return the appropiate file list + files = [] + for target in app.getTestPackage().getAllTestTargets(false) + files.push(target.path) + files # ------- Exports diff --git a/src/utils.coffee b/src/utils.coffee index c01118a..3d41ec4 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -13,7 +13,7 @@ utils.flatten = flatten = (array, results = []) -> results.push(item) results -utils.arrayToString = (value = "") -> +utils.arrayToString = (value) -> if Array.isArray(value) result = "" for line in value From 16414a21b3f573322df084ed288fc43c35dd34dc Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 29 Oct 2013 13:01:23 -0500 Subject: [PATCH 130/167] making it possible to bind to all ips --- lib/hem.js | 5 ++++- lib/server.js | 6 +++++- package.json | 4 ++-- src/hem.coffee | 2 ++ src/server.coffee | 5 ++++- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index 7a833c3..c1e04d8 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -9,7 +9,7 @@ optimist = require('optimist'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('h', 'port').describe('p', ':hem server host').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; argv.command = argv._[0]; @@ -92,6 +92,9 @@ if (argv.port) { this.options.hem.port = argv.port; } + if (argv.host) { + this.options.hem.host = argv.host; + } if (argv.runner) { this.options.hem.test.runner = argv.runner; } diff --git a/lib/server.js b/lib/server.js index 9e63235..57674cb 100644 --- a/lib/server.js +++ b/lib/server.js @@ -23,7 +23,11 @@ app = connect(); app.use(server.middleware(hem)); options = hem.options.hem; - http.createServer(app).listen(options.port, options.host); + if (options.host === "*") { + http.createServer(app).listen(options.port); + } else { + http.createServer(app).listen(options.port, options.host); + } return app; }; diff --git a/package.json b/package.json index 0d8219c..8a26096 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "open": "0.0.4" }, "optionalDependencies": { - "phantom": "~0.5.2", - "karma": "~0.10.2" + "phantom": "0.5.x", + "karma": "0.10.x" } } diff --git a/src/hem.coffee b/src/hem.coffee index 714a85c..783b8c5 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -15,6 +15,7 @@ argv = optimist.usage([ ' check :check slug file values' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') +.alias('h', 'port').describe('p',':hem server host') .alias('c', 'compress').describe('c',':all compilations are compressed/minified') .alias('w', 'watch').describe('w',':watch files when running tests') .alias('s', 'slug').describe('s',':run hem using a specified slug file') @@ -104,6 +105,7 @@ class Hem # allow overrides from command line @options.hem.port = argv.port if argv.port + @options.hem.host = argv.host if argv.host @options.hem.test.runner = argv.runner if argv.runner # setup applications from options/slug diff --git a/src/server.coffee b/src/server.coffee index 4d0304d..1076b42 100644 --- a/src/server.coffee +++ b/src/server.coffee @@ -16,7 +16,10 @@ server.start = (hem) -> app.use(server.middleware(hem)) # start server options = hem.options.hem - http.createServer(app).listen(options.port, options.host) + if options.host is "*" + http.createServer(app).listen(options.port) + else + http.createServer(app).listen(options.port, options.host) return app server.middleware = (hem) -> From df4cfe6d4f127d1b23fa9399a53332e6837f1863 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Tue, 5 Nov 2013 10:53:58 -0600 Subject: [PATCH 131/167] bid spolling --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a26096..09c3658 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hem", "version": "0.4.23", - "description": "stiches CommonJS, and ties up other lose ends of web-app development.", + "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", "commonsJS", From a8b953c72028732add41fd0686f7c6ad55e12c7d Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 13 Nov 2013 11:22:21 -0600 Subject: [PATCH 132/167] fixing host command line option --- lib/hem.js | 2 +- src/hem.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index c1e04d8..fc430b4 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -9,7 +9,7 @@ optimist = require('optimist'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('h', 'port').describe('p', ':hem server host').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('h', 'host').describe('p', ':hem server host').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; argv.command = argv._[0]; diff --git a/src/hem.coffee b/src/hem.coffee index 783b8c5..7dee96b 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -15,7 +15,7 @@ argv = optimist.usage([ ' check :check slug file values' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') -.alias('h', 'port').describe('p',':hem server host') +.alias('h', 'host').describe('p',':hem server host') .alias('c', 'compress').describe('c',':all compilations are compressed/minified') .alias('w', 'watch').describe('w',':watch files when running tests') .alias('s', 'slug').describe('s',':run hem using a specified slug file') From b881b2438d1ac8e010b9f72727c3e0b1ad61c8f8 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 13 Nov 2013 11:31:11 -0600 Subject: [PATCH 133/167] updated package # --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09c3658..0237ed5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.23", + "version": "0.4.24", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 60d3038eec2408004a03aa6f8b6ea00d08c186e7 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 8 Jan 2014 14:59:12 -0600 Subject: [PATCH 134/167] updating jade compiler to work with both old/new jade compile api --- lib/compilers.js | 5 +++-- src/compilers.coffee | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 0097ebb..0a14865 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -121,11 +121,12 @@ require.extensions['.jeco'] = require.extensions['.eco']; compilers.jade = function(_path) { - var content, ex, jade, source, template; + var content, ex, jCompile, jade, source, template; jade = requireLocalModule('jade', _path); content = fs.readFileSync(_path, 'utf8'); try { - template = jade.compile(content, { + jCompile = jade.compileClient || jade.compile; + template = jCompile(content, { filename: _path, compileDebug: this.argv.command === "server", client: true diff --git a/src/compilers.coffee b/src/compilers.coffee index 23abf2f..26905b4 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -125,7 +125,9 @@ compilers.jade = (_path) -> jade = requireLocalModule('jade', _path) content = fs.readFileSync(_path, 'utf8') try - template = jade.compile content, + # look first for compileClient (starting with jade v1.0.0) and fallback compile if not defined + jCompile = jade.compileClient or jade.compile + template = jCompile content, filename: _path compileDebug: @argv.command is "server" client: true From ba29198bda42fd7ab4686de1cac424447a5354cf Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 13 Jan 2014 17:14:12 -0600 Subject: [PATCH 135/167] fix command line argument switch --- src/hem.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hem.coffee b/src/hem.coffee index 7dee96b..c1239db 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -15,7 +15,7 @@ argv = optimist.usage([ ' check :check slug file values' ].join("\n")) .alias('p', 'port').describe('p',':hem server port') -.alias('h', 'host').describe('p',':hem server host') +.alias('h', 'host').describe('h',':hem server host') .alias('c', 'compress').describe('c',':all compilations are compressed/minified') .alias('w', 'watch').describe('w',':watch files when running tests') .alias('s', 'slug').describe('s',':run hem using a specified slug file') From 4c872db4abcf10e9ed595b6dc7eac5b7c925faba Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Apr 2014 11:01:18 -0500 Subject: [PATCH 136/167] compiled with latest coffeescript --- lib/compilers.js | 2 +- lib/dependency.js | 2 +- lib/events.js | 2 +- lib/hem.js | 4 ++-- lib/log.js | 2 +- lib/package.js | 38 ++++++++++++++++++++------------------ lib/phantom.js | 2 +- lib/resolve.js | 2 +- lib/server.js | 2 +- lib/stitch.js | 2 +- lib/test.js | 2 +- lib/utils.js | 2 +- 12 files changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 0a14865..d636ceb 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var compileCoffeescript, compilers, cs, fs, lmCache, log, path, projectPath, requireLocalModule; diff --git a/lib/dependency.js b/lib/dependency.js index 1dc3a4c..822b045 100644 --- a/lib/dependency.js +++ b/lib/dependency.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var Dependency, Module, compilers, detective, extname, fs, mtime, resolve, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; diff --git a/lib/events.js b/lib/events.js index 2c39903..b5bf464 100644 --- a/lib/events.js +++ b/lib/events.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var events; diff --git a/lib/hem.js b/lib/hem.js index fc430b4..ae44a2e 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var Hem, application, argv, compilers, events, fs, help, log, optimist, path, server, testing, utils, versioning, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -9,7 +9,7 @@ optimist = require('optimist'); - argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('h', 'host').describe('p', ':hem server host').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; + argv = optimist.usage(['usage:\nhem COMMAND', ' server :start a dynamic development server', ' build :serialize application to disk', ' watch :build & watch disk for changes', ' test :build and run tests', ' clean :clean compiled targets', ' version :version the application files', ' check :check slug file values'].join("\n")).alias('p', 'port').describe('p', ':hem server port').alias('h', 'host').describe('h', ':hem server host').alias('c', 'compress').describe('c', ':all compilations are compressed/minified').alias('w', 'watch').describe('w', ':watch files when running tests').alias('s', 'slug').describe('s', ':run hem using a specified slug file').alias('n', 'nocolors').describe('n', ':disable color in console output').alias('v', 'verbose').describe('v', ':make hem more talkative(verbose)').alias('g', 'grep').describe('g', ':only run specific modules during test').alias('r', 'runner').describe('r', ':override the default test runner').argv; argv.command = argv._[0]; diff --git a/lib/log.js b/lib/log.js index 37e0b61..248181e 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var log, sty; diff --git a/lib/package.js b/lib/package.js index 9a278a4..79c8f84 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, create, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, _argv, _hem, __slice = [].slice, @@ -174,16 +174,17 @@ }; Application.prototype.applyRootDir = function(value) { - var values, - _this = this; + var values; values = utils.toArray(value); - values = values.map(function(value) { - if (utils.startsWith(value, "." + path.sep)) { - return value; - } else { - return utils.cleanPath(_this.root, value); - } - }); + values = values.map((function(_this) { + return function(value) { + if (utils.startsWith(value, "." + path.sep)) { + return value; + } else { + return utils.cleanPath(_this.root, value); + } + }; + })(this)); return values; }; @@ -293,8 +294,7 @@ }; Package.prototype.watch = function() { - var dir, dirs, fileOrDir, watchOptions, _i, _j, _len, _len1, _ref, - _this = this; + var dir, dirs, fileOrDir, watchOptions, _i, _j, _len, _len1, _ref; watchOptions = { persistent: true, interval: 1000, @@ -316,12 +316,14 @@ dirs = utils.removeDuplicateValues(dirs); for (_j = 0, _len1 = dirs.length; _j < _len1; _j++) { dir = dirs[_j]; - require('watch').watchTree(dir, watchOptions, function(file, curr, prev) { - if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { - _this.build(file); - return events.emit("watch", _this.app, _this, file); - } - }); + require('watch').watchTree(dir, watchOptions, (function(_this) { + return function(file, curr, prev) { + if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { + _this.build(file); + return events.emit("watch", _this.app, _this, file); + } + }; + })(this)); } return dirs; }; diff --git a/lib/phantom.js b/lib/phantom.js index 4d5cd22..3bac067 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var err, jasmine_checkTestResults, jasmine_parseTestResults, log, phantom, reporters, run, waitFor; diff --git a/lib/resolve.js b/lib/resolve.js index aa4f91c..9f75ac6 100644 --- a/lib/resolve.js +++ b/lib/resolve.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var Module, basename, dirname, extname, invalidDirs, isAbsolute, join, modulePaths, modulerize, repl, resolve, sep, _ref, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; diff --git a/lib/server.js b/lib/server.js index 57674cb..f7c692a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, log, mime, patchServerResponseForRedirects, server, utils; diff --git a/lib/stitch.js b/lib/stitch.js index 1a95f62..8f6f2a2 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var Module, Stitch, compilers, flatten, fs, modulerize, npath, _createModule, _modules, _walk; diff --git a/lib/test.js b/lib/test.js index b0e5dbb..bb1cfde 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var async, createKarmaFileList, events, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils; diff --git a/lib/utils.js b/lib/utils.js index 15ac94c..b6812f1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var clean, extend, flatten, fs, isWin, path, tmplCache, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, From 6e16cfa12fb387ac82e4933b17e509ed9c61a40f Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Apr 2014 11:01:39 -0500 Subject: [PATCH 137/167] new versioning based off environment variable. --- lib/versioning.js | 48 +++++++++++++++++++++++++++++++++++++------ src/versioning.coffee | 22 ++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/versioning.js b/lib/versioning.js index bc7f085..73de2bd 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,6 +1,6 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { - var NpmPackageVersion, fs, log, path, types, updateVersionInAppFiles, updateVersionInData, utils; + var BuildVersion, NpmPackageVersion, fs, log, path, types, updateVersionInAppFiles, updateVersionInData, utils; fs = require('fs'); @@ -44,14 +44,15 @@ types["package"] = NpmPackageVersion = (function() { function NpmPackageVersion(app, options) { - var _this = this; if (options == null) { options = {}; } this.app = app; - this.files = utils.toArray(options.files).map(function(file) { - return _this.app.applyRootDir(file)[0]; - }); + this.files = utils.toArray(options.files).map((function(_this) { + return function(file) { + return _this.app.applyRootDir(file)[0]; + }; + })(this)); } NpmPackageVersion.prototype.getVersion = function() { @@ -70,6 +71,41 @@ })(); + types.build = BuildVersion = (function() { + function BuildVersion(app, options) { + if (options == null) { + options = {}; + } + this.app = app; + this.files = utils.toArray(options.files).map((function(_this) { + return function(file) { + return _this.app.applyRootDir(file)[0]; + }; + })(this)); + this.envVariable = options.envVariable || "BUILD_NUMBER"; + } + + BuildVersion.prototype.getVersion = function() { + if (process.env[this.envVariable]) { + return process.env[this.envVariable]; + } else { + log("ERROR: " + this.envVariable + " not set correctly as an environment variable."); + return process.exit(1); + } + }; + + BuildVersion.prototype.update = function() { + return updateVersionInAppFiles(this.files, this.app.packages, this.getVersion()); + }; + + BuildVersion.prototype.trim = function(url) { + return url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2"); + }; + + return BuildVersion; + + })(); + module.exports = types; }).call(this); diff --git a/src/versioning.coffee b/src/versioning.coffee index f7306c6..6e2fc78 100644 --- a/src/versioning.coffee +++ b/src/versioning.coffee @@ -45,6 +45,28 @@ types.package = class NpmPackageVersion trim: (url) -> url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2") +types.build = class BuildVersion + + constructor: (app, options = {}) -> + @app = app + @files = utils.toArray(options.files).map (file) => + @app.applyRootDir(file)[0] + @envVariable = options.envVariable or "BUILD_NUMBER" + + getVersion: -> + if process.env[@envVariable] + process.env[@envVariable] + else + log "ERROR: #{@envVariable} not set correctly as an environment variable." + process.exit(1) + + update: () -> + updateVersionInAppFiles(@files, @app.packages, @getVersion()) + + trim: (url) -> + url.replace(/^([^.]+).*(\.css|\.js)$/i, "$1$2") + + # TODO: other types that could be made # 1) based on git commits/tags # 2) backed on jenkinds builds or env values From b7de47776a8f8a8eeeeee74c40ba9b0e8790c241 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 10 Apr 2014 11:01:45 -0500 Subject: [PATCH 138/167] updated package # --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0237ed5..71dd8d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.24", + "version": "0.4.25", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", From 10dea25fa4ae141bf4030f4af1947b7ec69097b1 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 30 Apr 2014 17:06:21 -0500 Subject: [PATCH 139/167] increase phantom timeout --- src/phantom.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/phantom.coffee b/src/phantom.coffee index 3311825..4693a4d 100644 --- a/src/phantom.coffee +++ b/src/phantom.coffee @@ -64,7 +64,7 @@ waitFor = (-> getTime = -> (new Date).getTime() return (test, doIt, duration) -> - duration or= 6000 + duration or= 60000 start = getTime() finish = start + duration int = undefined @@ -99,8 +99,6 @@ jasmine_parseTestResults = (report) -> # need to turn report back into a real javascript function eval("report = " + report) - # TODO: at some point, do we need to inject jQuery? - # handle looping over suites printSuites = (root, level) -> level or= 0 From 30db67e9c62b76751b0666717ad0b51b9caea1df Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 30 Apr 2014 17:06:52 -0500 Subject: [PATCH 140/167] refactoring of phantom/karma test runners, should be somewhat usable now. --- src/test.coffee | 50 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/test.coffee b/src/test.coffee index 6a050c7..7149672 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -49,8 +49,7 @@ runBrowser = (apps, options, done) -> async.series(tasks) else q = async.queue( ((task, callback) -> task(callback)), 1) - for task, taskObject of tasks - events.on("watch", (app, pkg, file) -> q.push(tasks[app.name])) + events.on("watch", (app, pkg, file) -> q.push(tasks[app.name])) runPhantom = (apps, options, done) -> # set some other defaults @@ -60,6 +59,7 @@ runPhantom = (apps, options, done) -> # loop over apps to create test runner functions for app in apps testName = app.name + # TODO: perform injection with phantomjs instead! testFile = app.getTestPackage().getTestIndexFile() testPort = 12300 + Object.keys(tasks).length @@ -81,38 +81,56 @@ runPhantom = (apps, options, done) -> exitCode += result.error and 1 or 0 process.exit(exitCode) ) - # else add to queue and setup watch else q = async.queue( ((task, callback) -> task(callback)), 1) - for task, taskObject of tasks - # setup watch and use q.push(taskObject) to assign to q - events.on("watch", (app, pkg, file) -> - q.push(tasks[app.name]) - ) + events.on("watch", (app, pkg, file) -> + q.push(tasks[app.name]) + ) runKarma = (apps, options = {}) -> - + # TODO: require karma from project folder instead! + karma = require('karma').server + tasks = {} + for app in apps # create config file to pass into server if user doesn't supply a file to use testConfig = - singleRun : options.singleRun + singleRun : true basePath : options.basePath reporters : [options.reporters or 'progress'] logLevel : options.logLevel or 'error' frameworks : [options.frameworks or 'jasmine'] browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] files : createKarmaFileList(app) - autoWatch : true + autoWatch : false - # callback - callback = (exitCode) -> - console.log "done!!" + # create task + tasks[app.name] = do(app, testConfig) -> + (done) -> + log("Testing application targets: #{app.name}") + # karma callback + callback = (exitCode) -> + # async.series calleback + done(null, { failed: exitCode } ) + # start testacular server + karma.start(testConfig, callback) + + # if single run then just add to async series + if options.singleRun + async.series(tasks, (err, results) -> + exitCode = 0 + for name, result of results + exitCode += result.failed and result.failed or 0 + exitCode += result.error and 1 or 0 process.exit(exitCode) + ) + # else add to queue and setup watch + else + q = async.queue( ((task, callback) -> task(callback)), 1) + events.on("watch", (app, pkg, file) -> q.push(tasks[app.name]) ) - # start testacular server - require('karma').server.start(testConfig, callback) createKarmaFileList = (app) -> files = [] From 7af9923b81867b5958a0a7aaa94d784833069b6b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 30 Apr 2014 17:07:50 -0500 Subject: [PATCH 141/167] updated package version and compiled js files --- lib/phantom.js | 2 +- lib/test.js | 70 +++++++++++++++++++++++++++++++------------------- package.json | 11 +++++--- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/lib/phantom.js b/lib/phantom.js index 3bac067..d923678 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -91,7 +91,7 @@ }; return function(test, doIt, duration) { var finish, int, looop, start; - duration || (duration = 6000); + duration || (duration = 60000); start = getTime(); finish = start + duration; int = void 0; diff --git a/lib/test.js b/lib/test.js index bb1cfde..c2a1371 100644 --- a/lib/test.js +++ b/lib/test.js @@ -35,7 +35,7 @@ }; runBrowser = function(apps, options, done) { - var app, open, q, task, taskObject, tasks, testFile, testName, _i, _len, _results; + var app, open, q, tasks, testFile, testName, _i, _len; open = require("open"); tasks = {}; for (_i = 0, _len = apps.length; _i < _len; _i++) { @@ -55,19 +55,14 @@ q = async.queue((function(task, callback) { return task(callback); }), 1); - _results = []; - for (task in tasks) { - taskObject = tasks[task]; - _results.push(events.on("watch", function(app, pkg, file) { - return q.push(tasks[app.name]); - })); - } - return _results; + return events.on("watch", function(app, pkg, file) { + return q.push(tasks[app.name]); + }); } }; runPhantom = function(apps, options, done) { - var app, q, task, taskObject, tasks, testFile, testName, testPort, _i, _len, _results; + var app, q, tasks, testFile, testName, testPort, _i, _len; options.output || (options.output = "passOrFail"); tasks = {}; for (_i = 0, _len = apps.length; _i < _len; _i++) { @@ -102,42 +97,63 @@ q = async.queue((function(task, callback) { return task(callback); }), 1); - _results = []; - for (task in tasks) { - taskObject = tasks[task]; - _results.push(events.on("watch", function(app, pkg, file) { - return q.push(tasks[app.name]); - })); - } - return _results; + return events.on("watch", function(app, pkg, file) { + return q.push(tasks[app.name]); + }); } }; runKarma = function(apps, options) { - var app, callback, testConfig, _i, _len, _results; + var app, karma, q, tasks, testConfig, _i, _len; if (options == null) { options = {}; } - _results = []; + karma = require('karma').server; + tasks = {}; for (_i = 0, _len = apps.length; _i < _len; _i++) { app = apps[_i]; testConfig = { - singleRun: options.singleRun, + singleRun: true, basePath: options.basePath, reporters: [options.reporters || 'progress'], logLevel: options.logLevel || 'error', frameworks: [options.frameworks || 'jasmine'], browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], files: createKarmaFileList(app), - autoWatch: true + autoWatch: false }; - callback = function(exitCode) { - console.log("done!!"); + tasks[app.name] = (function(app, testConfig) { + return function(done) { + var callback; + log("Testing application targets: " + app.name + ""); + callback = function(exitCode) { + return done(null, { + failed: exitCode + }); + }; + return karma.start(testConfig, callback); + }; + })(app, testConfig); + } + if (options.singleRun) { + return async.series(tasks, function(err, results) { + var exitCode, name, result; + exitCode = 0; + for (name in results) { + result = results[name]; + exitCode += result.failed && result.failed || 0; + exitCode += result.error && 1 || 0; + } return process.exit(exitCode); - }; - _results.push(require('karma').server.start(testConfig, callback)); + }); + } else { + q = async.queue((function(task, callback) { + return task(callback); + }), 1); + return events.on("watch", function(app, pkg, file) { + return q.push(tasks[app.name]); + }); } - return _results; }; createKarmaFileList = function(app) { diff --git a/package.json b/package.json index 71dd8d9..f47214d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.25", + "version": "0.4.30", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -47,10 +47,13 @@ "watch": "~0.8.0", "fs-extra": "~0.7.0", "async": "~0.2.9", - "open": "0.0.4" + "open": "0.0.4", + "karma-phantomjs-launcher": "^0.1.4", + "karma-jasmine": "^0.1.5", + "karma-chrome-launcher": "^0.1.3" }, "optionalDependencies": { - "phantom": "0.5.x", - "karma": "0.10.x" + "phantom": "0.6.x", + "karma": "0.12.x" } } From 28f2c6d8bcbed66320b00d0866712cb59d953f9e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 30 Apr 2014 18:00:10 -0500 Subject: [PATCH 142/167] added comments, updated modules --- package.json | 2 +- src/test.coffee | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f47214d..759f7eb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "watch": "~0.8.0", "fs-extra": "~0.7.0", "async": "~0.2.9", - "open": "0.0.4", + "open": "0.0.5", "karma-phantomjs-launcher": "^0.1.4", "karma-jasmine": "^0.1.5", "karma-chrome-launcher": "^0.1.3" diff --git a/src/test.coffee b/src/test.coffee index 7149672..a6d80f0 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -48,6 +48,7 @@ runBrowser = (apps, options, done) -> if options.singleRun async.series(tasks) else + # TODO: watch should refresh the current tabl, not re-open q = async.queue( ((task, callback) -> task(callback)), 1) events.on("watch", (app, pkg, file) -> q.push(tasks[app.name])) From 427533ff592047b878c75b9afe70898204d0c6ee Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 19 May 2014 12:54:11 -0500 Subject: [PATCH 143/167] test index file not being generated correctly for windows --- lib/package.js | 18 ++++++++++++++---- package.json | 2 +- src/package.coffee | 16 +++++++++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/package.js b/lib/package.js index 79c8f84..82b978b 100644 --- a/lib/package.js +++ b/lib/package.js @@ -430,7 +430,7 @@ if ((_ref = this.framework) !== 'jasmine' && _ref !== 'mocha') { log.errorAndExit("Test frameworks value is not valid: " + this.framework); } - this.after += "// HEM: load in specs from test js file\nvar onlyMatchingModules = \"" + (_argv.grep || "") + "\";\nfor (var key in " + this.commonjs + ".modules) {\n if (onlyMatchingModules && key.indexOf(onlyMatchingModules) == -1) {\n continue;\n }\n " + this.commonjs + "(key); \n}"; + this.after += "// HEM: load in specs from test js file\nvar onlyMatchingModules = \"" + (_argv.grep || "") + "\";\nfor (var key in " + this.commonjs + ".modules) {\n if (onlyMatchingModules && key.indexOf(onlyMatchingModules) == -1) {\n continue;\n }\n " + this.commonjs + "(key);\n}"; } TestPackage.prototype.build = function(file) { @@ -445,11 +445,21 @@ } targets = []; homeRoute = path.dirname(this.route); - relativeFn = function(home, target) { + relativeFn = function(home, target, url) { + var value; + if (url == null) { + url = true; + } + value = ""; if (relative) { - return path.relative(home, target); + value = path.relative(home, target); + } else { + value = target; + } + if (url) { + return value.replace(/\\/g, "/"); } else { - return target; + return value; } }; _ref = this.depends; diff --git a/package.json b/package.json index 759f7eb..a39ccba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.30", + "version": "0.4.31", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/package.coffee b/src/package.coffee index beb8d8d..6ce5cc6 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -342,7 +342,7 @@ class TestPackage extends JsPackage if (onlyMatchingModules && key.indexOf(onlyMatchingModules) == -1) { continue; } - #{@commonjs}(key); + #{@commonjs}(key); } """ @@ -355,11 +355,17 @@ class TestPackage extends JsPackage homeRoute = path.dirname(@route) # create function to determine route/path - relativeFn = (home, target) -> + relativeFn = (home, target, url = true) -> + value = "" if relative - path.relative(home, target) + value = path.relative(home, target) else - target + value = target + if url + # deal with windows :o( + value.replace(/\\/g, "/") + else + value # first get dependencies for dep in @depends @@ -367,7 +373,7 @@ class TestPackage extends JsPackage for pkg in depapp.packages continue unless pkg.constructor.name is "JsPackage" url = relativeFn(homeRoute, pkg.route) - pth = relativeFn(@testHome, pkg.target) + pth = relativeFn(@testHome, pkg.target) targets.push({ url: url, path: pth }) # get app targets From 63b526ccf1a38e023b2ffd33095566f431cd4037 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 19 May 2014 12:56:36 -0500 Subject: [PATCH 144/167] don't overwrite test index file if it already exists --- lib/package.js | 4 +++- src/package.coffee | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/package.js b/lib/package.js index 82b978b..71d78e9 100644 --- a/lib/package.js +++ b/lib/package.js @@ -540,7 +540,9 @@ files: files, before: this.before }); - fs.outputFileSync(indexFile, template); + if (!fs.existsSync(indexFile)) { + fs.outputFileSync(indexFile, template); + } frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); _ref = fs.readdirSync(frameworkPath); _results = []; diff --git a/src/package.coffee b/src/package.coffee index 6ce5cc6..6d5fa4c 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -410,7 +410,7 @@ class TestPackage extends JsPackage files.push.apply(files, @getFrameworkFiles()) files.push.apply(files, @getAllTestTargets()) template = utils.tmpl("testing/index", { commonjs: @commonjs, files: files, before: @before } ) - fs.outputFileSync(indexFile, template) + fs.outputFileSync(indexFile, template) unless fs.existsSync(indexFile) # copy the framework files if they aren't present frameworkPath = path.resolve(__dirname, "../assets/testing/#{@framework}") From 0a3ad0b11e7b49ec4f0c791f217b12ae88f285d3 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 2 Jun 2014 18:58:45 -0500 Subject: [PATCH 145/167] allowing junit reporter options --- lib/test.js | 9 ++++++++- src/test.coffee | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index c2a1371..b179281 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,6 +1,7 @@ // Generated by CoffeeScript 1.7.1 (function() { - var async, createKarmaFileList, events, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils; + var async, createKarmaFileList, events, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; fs = require('fs'); @@ -122,6 +123,12 @@ files: createKarmaFileList(app), autoWatch: false }; + if (__indexOf.call(testConfig.reporters, 'junit') >= 0) { + testConfig.junitReporter = { + outputFile: app.name + '-test-results.xml', + suite: '' + }; + } tasks[app.name] = (function(app, testConfig) { return function(done) { var callback; diff --git a/src/test.coffee b/src/test.coffee index a6d80f0..dd4342f 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -107,6 +107,12 @@ runKarma = (apps, options = {}) -> files : createKarmaFileList(app) autoWatch : false + # handle junit special case for report file location + if 'junit' in testConfig.reporters + testConfig.junitReporter = + outputFile: app.name + '-test-results.xml' + suite: '' + # create task tasks[app.name] = do(app, testConfig) -> (done) -> From 3c7b96b3288169f93dabd1bac254794773b63440 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 2 Jun 2014 19:00:23 -0500 Subject: [PATCH 146/167] added karma junit reporter --- package.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index a39ccba..bfa31fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.31", + "version": "0.4.32", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -36,21 +36,22 @@ }, "preferGlobal": true, "dependencies": { - "fast-detective": "~0.0.2", - "uglifycss": "0.0.5", + "async": "~0.2.9", "coffee-script": "~1.6.3", "connect": "~2.9.0", - "http-proxy": "~0.10.3", - "uglify-js": "~2.4.0", - "sty": "~0.6.1", - "optimist": "~0.6.0", - "watch": "~0.8.0", + "fast-detective": "~0.0.2", "fs-extra": "~0.7.0", - "async": "~0.2.9", - "open": "0.0.5", - "karma-phantomjs-launcher": "^0.1.4", + "http-proxy": "~0.10.3", + "karma-chrome-launcher": "^0.1.3", "karma-jasmine": "^0.1.5", - "karma-chrome-launcher": "^0.1.3" + "karma-junit-reporter": "^0.2.2", + "karma-phantomjs-launcher": "^0.1.4", + "open": "0.0.5", + "optimist": "~0.6.0", + "sty": "~0.6.1", + "uglify-js": "~2.4.0", + "uglifycss": "0.0.5", + "watch": "~0.8.0" }, "optionalDependencies": { "phantom": "0.6.x", From 6d8cf8fa34cbe6f4969127d8f113410dfe64c770 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 2 Jun 2014 20:59:28 -0500 Subject: [PATCH 147/167] include suite name in junit format --- lib/test.js | 2 +- package.json | 2 +- src/test.coffee | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/test.js b/lib/test.js index b179281..0361ec0 100644 --- a/lib/test.js +++ b/lib/test.js @@ -126,7 +126,7 @@ if (__indexOf.call(testConfig.reporters, 'junit') >= 0) { testConfig.junitReporter = { outputFile: app.name + '-test-results.xml', - suite: '' + suite: app.name }; } tasks[app.name] = (function(app, testConfig) { diff --git a/package.json b/package.json index bfa31fe..223f0a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.32", + "version": "0.4.33", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/test.coffee b/src/test.coffee index dd4342f..ded26ae 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -111,7 +111,7 @@ runKarma = (apps, options = {}) -> if 'junit' in testConfig.reporters testConfig.junitReporter = outputFile: app.name + '-test-results.xml' - suite: '' + suite: app.name # create task tasks[app.name] = do(app, testConfig) -> From a73ee7a34248ef8d2d4e9b550d1658c1536f2937 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 2 Jul 2014 14:30:43 -0500 Subject: [PATCH 148/167] removing build as a step for running tests, use watch instead --- lib/hem.js | 1 - package.json | 2 +- src/hem.coffee | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/hem.js b/lib/hem.js index ae44a2e..4c05575 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -170,7 +170,6 @@ this.watch(); testOptions.singleRun = false; } else { - this.buildApps(); testOptions.singleRun = true; } return testing.run(this.apps, testOptions); diff --git a/package.json b/package.json index 223f0a6..b685b8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.33", + "version": "0.4.4", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/hem.coffee b/src/hem.coffee index c1239db..25a88c4 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -147,7 +147,6 @@ class Hem @watch() testOptions.singleRun = false else - @buildApps() testOptions.singleRun = true # run tests From 7215ca4cae3697bec4a2b297a711b5c9dd8522a5 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Jan 2015 12:47:33 -0600 Subject: [PATCH 149/167] updated the way coffeescript is required --- bin/hem | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/hem b/bin/hem index 4567860..5bae5d4 100755 --- a/bin/hem +++ b/bin/hem @@ -3,6 +3,9 @@ var path = require('path'); var fs = require('fs'); +// require coffeescript +require('coffee-script').register(); + // Try to find a local install var hem = path.resolve(process.cwd(), 'node_modules', 'hem', 'lib', 'hem'); From a226820f3cfecb2b2aca0e1c985d071d9a25711e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Jan 2015 12:48:03 -0600 Subject: [PATCH 150/167] need to match route on lower case to hit all cases --- src/package.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.coffee b/src/package.coffee index 6d5fa4c..b4d88df 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -91,7 +91,7 @@ class Application route = @versioning.trim(route) # compare against package route values for pkg in @packages - return pkg if route is pkg.route + return pkg if route is pkg.route.toLowerCase() # return nothing return From 879020adc561e3ec7bd954bd1cfb44399dc436cc Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Jan 2015 12:48:21 -0600 Subject: [PATCH 151/167] latest karma tweeks --- src/test.coffee | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/test.coffee b/src/test.coffee index ded26ae..ff22120 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -85,8 +85,8 @@ runPhantom = (apps, options, done) -> # else add to queue and setup watch else q = async.queue( ((task, callback) -> task(callback)), 1) - events.on("watch", (app, pkg, file) -> - q.push(tasks[app.name]) + events.on("watch", (app, pkg, file) -> + q.push(tasks[app.name]) ) runKarma = (apps, options = {}) -> @@ -94,18 +94,36 @@ runKarma = (apps, options = {}) -> karma = require('karma').server tasks = {} + # handle defaults + options.reporters or= 'progress' + options.frameworks or= 'jasmine' + options.browsers or= 'PhantomJS' + + # make sure some values are arrays + options.reporters = Array.isArray(options.reporters) or options.reporters.split(/[ ,]+/) + options.frameworks = Array.isArray(options.frameworks) or options.frameworks.split(/[ ,]+/) + options.browsers = Array.isArray(options.browsers) or options.browsers.split(/[ ,]+/) + for app in apps # create config file to pass into server if user doesn't supply a file to use testConfig = - singleRun : true - basePath : options.basePath - reporters : [options.reporters or 'progress'] - logLevel : options.logLevel or 'error' - frameworks : [options.frameworks or 'jasmine'] - browsers : options.browser and options.browser.split(/[ ,]+/) or ['PhantomJS'] - files : createKarmaFileList(app) - autoWatch : false + singleRun : true + autoWatch : false + basePath : options.basePath + logLevel : options.logLevel or 'error' + reporters : options.reporters + frameworks : options.frameworks + browsers : options.browsers + preprocessors : options.preprocessors or null + + # set files to test + testConfig.files = createKarmaFileList(app) + + # coverage reporter option + testConfig.coverageReporter = options.coverageReporter or null + + console.log testConfig # handle junit special case for report file location if 'junit' in testConfig.reporters From 1c9051364210f41baa0a27b8eed616915051c6e5 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Jan 2015 12:48:54 -0600 Subject: [PATCH 152/167] updated to latest coffeescript and karma, bumped version number --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b685b8c..a468e24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.4", + "version": "0.4.5", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -37,12 +37,13 @@ "preferGlobal": true, "dependencies": { "async": "~0.2.9", - "coffee-script": "~1.6.3", + "coffee-script": "1.8.X", "connect": "~2.9.0", "fast-detective": "~0.0.2", "fs-extra": "~0.7.0", "http-proxy": "~0.10.3", "karma-chrome-launcher": "^0.1.3", + "karma-coverage": "^0.2.6", "karma-jasmine": "^0.1.5", "karma-junit-reporter": "^0.2.2", "karma-phantomjs-launcher": "^0.1.4", From 6e5e52c1870591602e963cfb3caa1b485e311375 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Jan 2015 12:49:17 -0600 Subject: [PATCH 153/167] latest compiled js files --- lib/compilers.js | 2 +- lib/dependency.js | 2 +- lib/events.js | 2 +- lib/hem.js | 4 ++-- lib/log.js | 2 +- lib/package.js | 7 ++++--- lib/phantom.js | 2 +- lib/resolve.js | 2 +- lib/server.js | 2 +- lib/stitch.js | 2 +- lib/test.js | 21 +++++++++++++++------ lib/utils.js | 2 +- 12 files changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index d636ceb..130e03a 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var compileCoffeescript, compilers, cs, fs, lmCache, log, path, projectPath, requireLocalModule; diff --git a/lib/dependency.js b/lib/dependency.js index 822b045..bcfa47e 100644 --- a/lib/dependency.js +++ b/lib/dependency.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var Dependency, Module, compilers, detective, extname, fs, mtime, resolve, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; diff --git a/lib/events.js b/lib/events.js index b5bf464..3797a50 100644 --- a/lib/events.js +++ b/lib/events.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var events; diff --git a/lib/hem.js b/lib/hem.js index 4c05575..7bf290a 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var Hem, application, argv, compilers, events, fs, help, log, optimist, path, server, testing, utils, versioning, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -227,7 +227,7 @@ this.slug = require(slugPath); } catch (_error) { error = _error; - log.errorAndExit("Couldn't load slug file " + slugPath + "."); + log.errorAndExit(("Couldn't load slug file " + slugPath + ": ") + error.message); } return this.slug.config || this.slug; }; diff --git a/lib/log.js b/lib/log.js index 248181e..edc04e9 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var log, sty; diff --git a/lib/package.js b/lib/package.js index 71d78e9..30e0f6c 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, create, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, _argv, _hem, __slice = [].slice, @@ -114,7 +114,7 @@ _ref = this.packages; for (_i = 0, _len = _ref.length; _i < _len; _i++) { pkg = _ref[_i]; - if (route === pkg.route) { + if (route === pkg.route.toLowerCase()) { return pkg; } } @@ -298,7 +298,8 @@ watchOptions = { persistent: true, interval: 1000, - ignoreDotFiles: true + ignoreDotFiles: true, + maxListeners: 128 }; dirs = []; _ref = this.getWatchedDirs(); diff --git a/lib/phantom.js b/lib/phantom.js index d923678..de63897 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var err, jasmine_checkTestResults, jasmine_parseTestResults, log, phantom, reporters, run, waitFor; diff --git a/lib/resolve.js b/lib/resolve.js index 9f75ac6..93c229f 100644 --- a/lib/resolve.js +++ b/lib/resolve.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var Module, basename, dirname, extname, invalidDirs, isAbsolute, join, modulePaths, modulerize, repl, resolve, sep, _ref, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; diff --git a/lib/server.js b/lib/server.js index f7c692a..92ebcd8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, log, mime, patchServerResponseForRedirects, server, utils; diff --git a/lib/stitch.js b/lib/stitch.js index 8f6f2a2..9b1c480 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var Module, Stitch, compilers, flatten, fs, modulerize, npath, _createModule, _modules, _walk; diff --git a/lib/test.js b/lib/test.js index 0361ec0..dce9166 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var async, createKarmaFileList, events, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -111,18 +111,27 @@ } karma = require('karma').server; tasks = {}; + options.reporters || (options.reporters = 'progress'); + options.frameworks || (options.frameworks = 'jasmine'); + options.browsers || (options.browsers = 'PhantomJS'); + options.reporters = Array.isArray(options.reporters) || options.reporters.split(/[ ,]+/); + options.frameworks = Array.isArray(options.frameworks) || options.frameworks.split(/[ ,]+/); + options.browsers = Array.isArray(options.browsers) || options.browsers.split(/[ ,]+/); for (_i = 0, _len = apps.length; _i < _len; _i++) { app = apps[_i]; testConfig = { singleRun: true, + autoWatch: false, basePath: options.basePath, - reporters: [options.reporters || 'progress'], logLevel: options.logLevel || 'error', - frameworks: [options.frameworks || 'jasmine'], - browsers: options.browser && options.browser.split(/[ ,]+/) || ['PhantomJS'], - files: createKarmaFileList(app), - autoWatch: false + reporters: options.reporters, + frameworks: options.frameworks, + browsers: options.browsers, + preprocessors: options.preprocessors || null }; + testConfig.files = createKarmaFileList(app); + testConfig.coverageReporter = options.coverageReporter || null; + console.log(testConfig); if (__indexOf.call(testConfig.reporters, 'junit') >= 0) { testConfig.junitReporter = { outputFile: app.name + '-test-results.xml', diff --git a/lib/utils.js b/lib/utils.js index b6812f1..0b791a1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var clean, extend, flatten, fs, isWin, path, tmplCache, utils, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, From 46c3c97dbd10ed3d64f2aee7326dcf97744dbbc3 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 28 Jan 2015 12:49:34 -0600 Subject: [PATCH 154/167] forgot one --- lib/versioning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/versioning.js b/lib/versioning.js index 73de2bd..9866710 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.8.0 (function() { var BuildVersion, NpmPackageVersion, fs, log, path, types, updateVersionInAppFiles, updateVersionInData, utils; From 6ea03e913457a983bf20e7dff4bef26fdf2559ef Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 11 Feb 2015 15:04:23 -0600 Subject: [PATCH 155/167] some minor output tweaks --- src/hem.coffee | 2 +- src/package.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hem.coffee b/src/hem.coffee index 25a88c4..3414e70 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -192,7 +192,7 @@ class Hem delete require.cache[slugPath] @slug = require(slugPath) catch error - log.errorAndExit("Couldn't load slug file #{slugPath}.") + log.errorAndExit("Couldn't load slug file #{slugPath}: " + error.message) # return config portion of slug file @slug.config or @slug diff --git a/src/package.coffee b/src/package.coffee index b4d88df..edae802 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -219,7 +219,7 @@ class Package source watch: -> - watchOptions = { persistent: true, interval: 1000, ignoreDotFiles: true } + watchOptions = { persistent: true, interval: 1000, ignoreDotFiles: true, maxListeners: 128 } # get dirs to watch dirs = [] for fileOrDir in @getWatchedDirs() From f24b844399c3713b6043b726ff6b0e1c22b166b2 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 19 Feb 2015 16:22:56 -0600 Subject: [PATCH 156/167] more effecient html templates --- lib/compilers.js | 1 + package.json | 2 +- src/compilers.coffee | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/compilers.js b/lib/compilers.js index 130e03a..ad3820d 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -46,6 +46,7 @@ compilers.html = function(_path) { var content; content = fs.readFileSync(_path, 'utf8'); + content = content.replace(/\n/g, "").replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; diff --git a/package.json b/package.json index a468e24..eab0156 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.5", + "version": "0.4.6", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/compilers.coffee b/src/compilers.coffee index 26905b4..fe28cea 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -48,6 +48,13 @@ require.extensions['.css'] = (module, filename) -> compilers.html = (_path) -> content = fs.readFileSync(_path, 'utf8') + # remove whitespace + content = content + .replace(/\n/g, "") + .replace(/[\t ]+\[\t ]+\<") + .replace(/\>[\t ]+$/g, ">") + # export "module.exports = #{JSON.stringify(content)};\n" require.extensions['.html'] = (module, filename) -> From 4355ffca3b536a5cd6955c46fe0637266cd266b9 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 19 Feb 2015 16:58:23 -0600 Subject: [PATCH 157/167] another attempt at html minify --- lib/compilers.js | 2 +- package.json | 3 ++- src/compilers.coffee | 6 +----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index ad3820d..85dd273 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -46,7 +46,7 @@ compilers.html = function(_path) { var content; content = fs.readFileSync(_path, 'utf8'); - content = content.replace(/\n/g, "").replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); + content = require('html-minifier').minify(content); return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; diff --git a/package.json b/package.json index eab0156..7db1861 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.6", + "version": "0.4.7", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -41,6 +41,7 @@ "connect": "~2.9.0", "fast-detective": "~0.0.2", "fs-extra": "~0.7.0", + "html-minifier": "^0.7.0", "http-proxy": "~0.10.3", "karma-chrome-launcher": "^0.1.3", "karma-coverage": "^0.2.6", diff --git a/src/compilers.coffee b/src/compilers.coffee index fe28cea..fb0bfdc 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -49,11 +49,7 @@ require.extensions['.css'] = (module, filename) -> compilers.html = (_path) -> content = fs.readFileSync(_path, 'utf8') # remove whitespace - content = content - .replace(/\n/g, "") - .replace(/[\t ]+\[\t ]+\<") - .replace(/\>[\t ]+$/g, ">") + content = require('html-minifier').minify(content) # export "module.exports = #{JSON.stringify(content)};\n" From c952778a03a6ba003cb7abf5574a6ba72a0f6e48 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Thu, 19 Feb 2015 17:16:14 -0600 Subject: [PATCH 158/167] make sure templates have their own compiler, was causing errors during minify html --- lib/compilers.js | 17 ++++++++++++++--- package.json | 2 +- src/compilers.coffee | 11 +++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 85dd273..eb72bf9 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -43,10 +43,21 @@ return module._compile("module.exports = " + source, filename); }; - compilers.html = function(_path) { + compilers.tmpl = function(_path) { var content; content = fs.readFileSync(_path, 'utf8'); - content = require('html-minifier').minify(content); + return "module.exports = " + (JSON.stringify(content)) + ";\n"; + }; + + compilers.html = function(_path) { + var content, err; + content = fs.readFileSync(_path, 'utf8'); + try { + content = require('html-minifier').minify(content); + } catch (_error) { + err = _error; + console.log(_path, err); + } return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; @@ -55,7 +66,7 @@ }; require.extensions['.tmpl'] = function(module, filename) { - return module._compile(compilers.html(filename), filename); + return module._compile(compilers.tmpl(filename), filename); }; cs = require('coffee-script'); diff --git a/package.json b/package.json index 7db1861..8b0cfbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.7", + "version": "0.4.8", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/compilers.coffee b/src/compilers.coffee index fb0bfdc..7be434e 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -46,10 +46,17 @@ require.extensions['.css'] = (module, filename) -> ## HTML and Tmpl files ## +compilers.tmpl = (_path) -> + content = fs.readFileSync(_path, 'utf8') + "module.exports = #{JSON.stringify(content)};\n" + compilers.html = (_path) -> content = fs.readFileSync(_path, 'utf8') # remove whitespace - content = require('html-minifier').minify(content) + try + content = require('html-minifier').minify(content) + catch err + console.log _path, err # export "module.exports = #{JSON.stringify(content)};\n" @@ -57,7 +64,7 @@ require.extensions['.html'] = (module, filename) -> module._compile compilers.html(filename), filename require.extensions['.tmpl'] = (module, filename) -> - module._compile compilers.html(filename), filename + module._compile compilers.tmpl(filename), filename ## ## Compile Coffeescript From a8e5963f2b2bc36a97c5058c762951e27bb0efe5 Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 20 Feb 2015 08:54:59 -0600 Subject: [PATCH 159/167] going back to simple html whitespace remover --- lib/compilers.js | 17 ++++++----------- package.json | 2 +- src/compilers.coffee | 14 +++++++------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index eb72bf9..342a0a3 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -49,15 +49,14 @@ return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; + require.extensions['.tmpl'] = function(module, filename) { + return module._compile(compilers.tmpl(filename), filename); + }; + compilers.html = function(_path) { - var content, err; + var content; content = fs.readFileSync(_path, 'utf8'); - try { - content = require('html-minifier').minify(content); - } catch (_error) { - err = _error; - console.log(_path, err); - } + content = require('html-minifier').minify(content).replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; @@ -65,10 +64,6 @@ return module._compile(compilers.html(filename), filename); }; - require.extensions['.tmpl'] = function(module, filename) { - return module._compile(compilers.tmpl(filename), filename); - }; - cs = require('coffee-script'); compilers.coffee = function(_path) { diff --git a/package.json b/package.json index 8b0cfbd..b6d21c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.8", + "version": "0.4.9", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/compilers.coffee b/src/compilers.coffee index 7be434e..473da6b 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -50,22 +50,22 @@ compilers.tmpl = (_path) -> content = fs.readFileSync(_path, 'utf8') "module.exports = #{JSON.stringify(content)};\n" +require.extensions['.tmpl'] = (module, filename) -> + module._compile compilers.tmpl(filename), filename + compilers.html = (_path) -> content = fs.readFileSync(_path, 'utf8') # remove whitespace - try - content = require('html-minifier').minify(content) - catch err - console.log _path, err + content = require('html-minifier').minify(content) + .replace(/[\t ]+\[\t ]+\<") + .replace(/\>[\t ]+$/g, ">") # export "module.exports = #{JSON.stringify(content)};\n" require.extensions['.html'] = (module, filename) -> module._compile compilers.html(filename), filename -require.extensions['.tmpl'] = (module, filename) -> - module._compile compilers.tmpl(filename), filename - ## ## Compile Coffeescript ## From 94d2fa97f1aeb8de1e841963ed643d45445ad4ee Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 20 Feb 2015 09:52:34 -0600 Subject: [PATCH 160/167] ok maybe time to get eat some surgery stuff --- src/compilers.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compilers.coffee b/src/compilers.coffee index 473da6b..833af25 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -56,7 +56,7 @@ require.extensions['.tmpl'] = (module, filename) -> compilers.html = (_path) -> content = fs.readFileSync(_path, 'utf8') # remove whitespace - content = require('html-minifier').minify(content) + content = content .replace(/[\t ]+\[\t ]+\<") .replace(/\>[\t ]+$/g, ">") From b765ff54989f0015085c5adc59fe4d15b392f95d Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 20 Feb 2015 09:53:33 -0600 Subject: [PATCH 161/167] nom nom nom food --- lib/compilers.js | 2 +- package.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 342a0a3..48eeca4 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -56,7 +56,7 @@ compilers.html = function(_path) { var content; content = fs.readFileSync(_path, 'utf8'); - content = require('html-minifier').minify(content).replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); + content = content.replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; diff --git a/package.json b/package.json index b6d21c9..c01bc4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.9", + "version": "0.4.10", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -41,7 +41,6 @@ "connect": "~2.9.0", "fast-detective": "~0.0.2", "fs-extra": "~0.7.0", - "html-minifier": "^0.7.0", "http-proxy": "~0.10.3", "karma-chrome-launcher": "^0.1.3", "karma-coverage": "^0.2.6", From ee3bbfb7f9ea9afc2a0e70ff312811450ba6a6dc Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 20 Feb 2015 10:17:48 -0600 Subject: [PATCH 162/167] I should probably stick to scuplty art --- lib/compilers.js | 2 +- package.json | 2 +- src/compilers.coffee | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index 48eeca4..eecc5a0 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -56,7 +56,7 @@ compilers.html = function(_path) { var content; content = fs.readFileSync(_path, 'utf8'); - content = content.replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); + content = content.replace(/\n/g, "").replace(/[\t ]+\[\t ]+\<").replace(/\>[\t ]+$/g, ">"); return "module.exports = " + (JSON.stringify(content)) + ";\n"; }; diff --git a/package.json b/package.json index c01bc4c..0d75a16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.10", + "version": "0.4.11", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/compilers.coffee b/src/compilers.coffee index 833af25..cc8dc16 100644 --- a/src/compilers.coffee +++ b/src/compilers.coffee @@ -57,6 +57,7 @@ compilers.html = (_path) -> content = fs.readFileSync(_path, 'utf8') # remove whitespace content = content + .replace(/\n/g, "") .replace(/[\t ]+\[\t ]+\<") .replace(/\>[\t ]+$/g, ">") From 6480bc562c514534f81e1d350c26ffe2ff3f512b Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Fri, 20 Feb 2015 10:21:31 -0600 Subject: [PATCH 163/167] updated coffeescript to 1.9.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0d75a16..4b21d95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.11", + "version": "0.4.12", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", @@ -37,7 +37,7 @@ "preferGlobal": true, "dependencies": { "async": "~0.2.9", - "coffee-script": "1.8.X", + "coffee-script": "1.9.X", "connect": "~2.9.0", "fast-detective": "~0.0.2", "fs-extra": "~0.7.0", From e21521422252d8639c8e35a6e72da8a56eb6596e Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Wed, 6 May 2015 18:00:31 -0500 Subject: [PATCH 164/167] make sure build is always run before testing and better checks for auto generating test files --- lib/compilers.js | 6 +- lib/dependency.js | 46 +++++----- lib/events.js | 2 +- lib/hem.js | 137 ++++++++++++++-------------- lib/log.js | 4 +- lib/package.js | 218 ++++++++++++++++++++++----------------------- lib/phantom.js | 14 +-- lib/resolve.js | 16 ++-- lib/server.js | 50 +++++------ lib/stitch.js | 46 +++++----- lib/test.js | 33 ++++--- lib/utils.js | 42 ++++----- lib/versioning.js | 14 +-- package.json | 2 +- src/hem.coffee | 1 + src/package.coffee | 27 +++--- src/test.coffee | 2 - 17 files changed, 330 insertions(+), 330 deletions(-) diff --git a/lib/compilers.js b/lib/compilers.js index eecc5a0..395ee83 100644 --- a/lib/compilers.js +++ b/lib/compilers.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var compileCoffeescript, compilers, cs, fs, lmCache, log, path, projectPath, requireLocalModule; @@ -18,7 +18,7 @@ requireLocalModule = function(localModule, _path) { var error, modulePath, relativePath; - modulePath = "" + projectPath + "/node_modules/" + localModule; + modulePath = projectPath + "/node_modules/" + localModule; try { return lmCache[localModule] || (lmCache[localModule] = require(modulePath)); } catch (_error) { @@ -142,7 +142,7 @@ return "module.exports = " + source + ";"; } catch (_error) { ex = _error; - throw new Error("" + ex + " in " + _path); + throw new Error(ex + " in " + _path); } }; diff --git a/lib/dependency.js b/lib/dependency.js index bcfa47e..9b02c6b 100644 --- a/lib/dependency.js +++ b/lib/dependency.js @@ -1,7 +1,7 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var Dependency, Module, compilers, detective, extname, fs, mtime, resolve, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; extname = require('path').extname; @@ -21,8 +21,8 @@ Module.walk = ['js', 'coffee']; function Module(request, parent) { - var _ref; - _ref = resolve(request, parent), this.id = _ref[0], this.filename = _ref[1]; + var ref; + ref = resolve(request, parent), this.id = ref[0], this.filename = ref[1]; this.ext = extname(this.filename).slice(1); this.mtime = mtime(this.filename); this.paths = resolve.paths(this.filename); @@ -48,19 +48,19 @@ }; Module.prototype.resolve = function() { - var path, _i, _len, _ref, _results; - _ref = this.calls(); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - path = _ref[_i]; - _results.push(new this.constructor(path, this)); + var i, len, path, ref, results; + ref = this.calls(); + results = []; + for (i = 0, len = ref.length; i < len; i++) { + path = ref[i]; + results.push(new this.constructor(path, this)); } - return _results; + return results; }; Module.prototype.calls = function() { - var _ref; - if (_ref = this.ext, __indexOf.call(this.constructor.walk, _ref) >= 0) { + var ref; + if (ref = this.ext, indexOf.call(this.constructor.walk, ref) >= 0) { return detective(this.compile()); } else { return []; @@ -82,20 +82,20 @@ Dependency.prototype.resolve = function() { var path; this.modules || (this.modules = (function() { - var _i, _len, _ref, _results; - _ref = this.paths; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - path = _ref[_i]; - _results.push(new Module(path)); + var i, len, ref, results; + ref = this.paths; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + path = ref[i]; + results.push(new Module(path)); } - return _results; + return results; }).call(this)); return this.deepResolve(this.modules); }; Dependency.prototype.deepResolve = function(modules, result, search) { - var module, _i, _len; + var i, len, module; if (modules == null) { modules = []; } @@ -105,8 +105,8 @@ if (search == null) { search = {}; } - for (_i = 0, _len = modules.length; _i < _len; _i++) { - module = modules[_i]; + for (i = 0, len = modules.length; i < len; i++) { + module = modules[i]; if (!(!search[module.filename])) { continue; } diff --git a/lib/events.js b/lib/events.js index 3797a50..f6019b7 100644 --- a/lib/events.js +++ b/lib/events.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var events; diff --git a/lib/hem.js b/lib/hem.js index 7bf290a..7f4a947 100644 --- a/lib/hem.js +++ b/lib/hem.js @@ -1,7 +1,7 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var Hem, application, argv, compilers, events, fs, help, log, optimist, path, server, testing, utils, versioning, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; fs = require('fs'); @@ -42,8 +42,8 @@ application.argv = argv; help = function() { - var _ref; - log("HEM Version: " + ((_ref = require('../package.json')) != null ? _ref.version : void 0) + "\n"); + var ref; + log("HEM Version: " + ((ref = require('../package.json')) != null ? ref.version : void 0) + "\n"); optimist.showHelp(); return process.exit(); }; @@ -68,7 +68,7 @@ Hem.prototype.home = process.cwd(); function Hem(options) { - var config, name, slug, _base, _base1, _base2, _base3, _base4, _base5, _base6, _ref; + var base, base1, base2, base3, base4, base5, base6, config, name, ref, slug; switch (typeof options) { case "string": slug = options; @@ -82,13 +82,13 @@ if (slug) { this.options = this.readSlug(slug); } - (_base = this.options).hem || (_base.hem = {}); - (_base1 = this.options.hem).port || (_base1.port = 9294); - (_base2 = this.options.hem).host || (_base2.host = "localhost"); - (_base3 = this.options.hem).test || (_base3.test = {}); - (_base4 = this.options.hem.test).runner || (_base4.runner = "karma"); - (_base5 = this.options.hem.test).reporters || (_base5.reporters = "progress"); - (_base6 = this.options.hem.test).frameworks || (_base6.frameworks = "jasmine"); + (base = this.options).hem || (base.hem = {}); + (base1 = this.options.hem).port || (base1.port = 9294); + (base2 = this.options.hem).host || (base2.host = "localhost"); + (base3 = this.options.hem).test || (base3.test = {}); + (base4 = this.options.hem.test).runner || (base4.runner = "karma"); + (base5 = this.options.hem.test).reporters || (base5.reporters = "progress"); + (base6 = this.options.hem.test).frameworks || (base6.frameworks = "jasmine"); if (argv.port) { this.options.hem.port = argv.port; } @@ -98,9 +98,9 @@ if (argv.runner) { this.options.hem.test.runner = argv.runner; } - _ref = this.options; - for (name in _ref) { - config = _ref[name]; + ref = this.options; + for (name in ref) { + config = ref[name]; if (name === "hem" || typeof config === 'function') { continue; } @@ -109,29 +109,29 @@ } Hem.prototype.server = function() { - var app, value, _i, _len, _ref, _results; + var app, i, len, ref, results, value; value = "http://" + (this.options.hem.host || "*") + ":" + this.options.hem.port; log("Starting Server at " + value + ""); app = server.start(this); events.emit("server", app); - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.watch()); + ref = this.apps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; + results.push(app.watch()); } - return _results; + return results; }; Hem.prototype.clean = function() { - var app, _i, _len, _ref, _results; - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.unlink()); + var app, i, len, ref, results; + ref = this.apps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; + results.push(app.unlink()); } - return _results; + return results; }; Hem.prototype.build = function() { @@ -140,30 +140,31 @@ }; Hem.prototype.version = function() { - var app, _i, _len, _ref, _results; - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.version()); + var app, i, len, ref, results; + ref = this.apps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; + results.push(app.version()); } - return _results; + return results; }; Hem.prototype.watch = function() { - var app, _i, _len, _ref, _results; + var app, i, len, ref, results; this.buildApps(); - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.watch()); + ref = this.apps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; + results.push(app.watch()); } - return _results; + return results; }; Hem.prototype.test = function() { var testOptions; + this.build(); testOptions = this.options.hem.tests || {}; testOptions.basePath || (testOptions.basePath = this.home); if (argv.watch) { @@ -176,7 +177,7 @@ }; Hem.prototype.check = function() { - var app, inspect, printOptions, _i, _len, _ref, _results; + var app, i, inspect, len, printOptions, ref, results; printOptions = { showHidden: false, colors: !argv.nocolors, @@ -186,19 +187,19 @@ log("> Configuration for hem:"); console.log(inspect(this.options.hem, printOptions)); log(""); - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; + ref = this.apps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; log("> Configuration values for " + app.name + ":"); console.log(inspect(app, printOptions)); - _results.push(log("")); + results.push(log("")); } - return _results; + return results; }; Hem.prototype.exec = function(command) { - var _base; + var base; if (command == null) { command = argv.command; } @@ -206,8 +207,8 @@ return help(); } this.apps = this.getTargetApps(); - if (typeof (_base = this.slug).custom === "function") { - _base.custom(this); + if (typeof (base = this.slug).custom === "function") { + base.custom(this); } return this[command](); }; @@ -233,31 +234,31 @@ }; Hem.prototype.getTargetApps = function(targets) { - var app, targetAll, _i, _len, _ref, _ref1, _results; + var app, i, len, ref, ref1, results, targetAll; if (targets == null) { targets = argv.targets; } targetAll = targets.length === 0; - _ref = this.allApps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - if ((_ref1 = app.name, __indexOf.call(targets, _ref1) >= 0) || targetAll) { - _results.push(app); + ref = this.allApps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; + if ((ref1 = app.name, indexOf.call(targets, ref1) >= 0) || targetAll) { + results.push(app); } } - return _results; + return results; }; Hem.prototype.buildApps = function() { - var app, _i, _len, _ref, _results; - _ref = this.apps; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - app = _ref[_i]; - _results.push(app.build()); + var app, i, len, ref, results; + ref = this.apps; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + app = ref[i]; + results.push(app.build()); } - return _results; + return results; }; Hem.prototype.module = function(name) { diff --git a/lib/log.js b/lib/log.js index edc04e9..a6cbc61 100644 --- a/lib/log.js +++ b/lib/log.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var log, sty; @@ -24,7 +24,7 @@ if (parse == null) { parse = true; } - return console.log("" + (sty.red('ERROR:')) + " " + (parse && sty.parse(message) || message)); + return console.log((sty.red('ERROR:')) + " " + (parse && sty.parse(message) || message)); }; log.errorAndExit = function(error, parse) { diff --git a/lib/package.js b/lib/package.js index 30e0f6c..b8964d0 100644 --- a/lib/package.js +++ b/lib/package.js @@ -1,9 +1,9 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { - var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, create, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, _argv, _hem, - __slice = [].slice, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + var Application, CssPackage, Dependency, JsPackage, Package, Stitch, TestPackage, _argv, _hem, create, events, fs, log, path, uglifycss, uglifyjs, utils, versioning, + slice = [].slice, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; fs = require('fs-extra'); @@ -31,7 +31,7 @@ Application = (function() { function Application(name, config) { - var defaults, err, key, loadedDefaults, packager, route, value, verType, _ref; + var defaults, err, key, loadedDefaults, packager, ref, route, value, verType; if (config == null) { config = {}; } @@ -60,9 +60,9 @@ } this["static"] = []; this.packages = []; - _ref = config["static"]; - for (route in _ref) { - value = _ref[route]; + ref = config["static"]; + for (route in ref) { + value = ref[route]; this["static"].push({ url: this.applyBaseRoute(route), path: this.applyRootDir(value)[0] @@ -96,10 +96,10 @@ } Application.prototype.getTestPackage = function() { - var pkg, _i, _len, _ref; - _ref = this.packages; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; + var i, len, pkg, ref; + ref = this.packages; + for (i = 0, len = ref.length; i < len; i++) { + pkg = ref[i]; if (pkg.constructor.name === "TestPackage") { return pkg; } @@ -107,13 +107,13 @@ }; Application.prototype.isMatchingRoute = function(route) { - var pkg, _i, _len, _ref; + var i, len, pkg, ref; if (this.versioning) { route = this.versioning.trim(route); } - _ref = this.packages; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; + ref = this.packages; + for (i = 0, len = ref.length; i < len; i++) { + pkg = ref[i]; if (route === pkg.route.toLowerCase()) { return pkg; } @@ -121,41 +121,41 @@ }; Application.prototype.unlink = function() { - var pkg, _i, _len, _ref, _results; + var i, len, pkg, ref, results1; log("Removing application: " + this.name + ""); - _ref = this.packages; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - _results.push(pkg.unlink()); + ref = this.packages; + results1 = []; + for (i = 0, len = ref.length; i < len; i++) { + pkg = ref[i]; + results1.push(pkg.unlink()); } - return _results; + return results1; }; Application.prototype.build = function() { - var pkg, _i, _len, _ref, _results; + var i, len, pkg, ref, results1; log("Building application: " + this.name + ""); - _ref = this.packages; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - _results.push(pkg.build()); + ref = this.packages; + results1 = []; + for (i = 0, len = ref.length; i < len; i++) { + pkg = ref[i]; + results1.push(pkg.build()); } - return _results; + return results1; }; Application.prototype.watch = function() { var dirs, pkg; log("Watching application: " + this.name + ""); dirs = (function() { - var _i, _len, _ref, _results; - _ref = this.packages; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pkg = _ref[_i]; - _results.push(pkg.watch()); + var i, len, ref, results1; + ref = this.packages; + results1 = []; + for (i = 0, len = ref.length; i < len; i++) { + pkg = ref[i]; + results1.push(pkg.watch()); } - return _results; + return results1; }).call(this); if (dirs.length) { return log.info("- Watching directories: " + dirs + ""); @@ -190,7 +190,7 @@ Application.prototype.applyBaseRoute = function() { var values; - values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + values = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (this.route) { values.unshift(this.route); } @@ -203,7 +203,7 @@ Package = (function() { function Package(app, config) { - var regexp, route, targetFile, targetUrl, _i, _len, _ref; + var i, len, ref, regexp, route, targetFile, targetUrl; this.app = app; this.name = config.name; this.src = this.app.applyRootDir(config.src || ""); @@ -217,7 +217,7 @@ this.target = utils.cleanPath(this.target, targetFile); } if (!utils.endsWith(this.target, "." + this.ext)) { - this.target = "" + this.target + "." + this.ext; + this.target = this.target + "." + this.ext; } if (config.route) { if (utils.startsWith(this.target, "/")) { @@ -226,9 +226,9 @@ this.route = this.app.applyBaseRoute(config.route); } } else { - _ref = this.app["static"]; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - route = _ref[_i]; + ref = this.app["static"]; + for (i = 0, len = ref.length; i < len; i++) { + route = ref[i]; if (!this.route) { if (utils.startsWith(this.target, route.path)) { regexp = new RegExp("^" + (route.path.replace(/\\/g, "\\\\")) + "(\\\\|\/)?"); @@ -294,7 +294,7 @@ }; Package.prototype.watch = function() { - var dir, dirs, fileOrDir, watchOptions, _i, _j, _len, _len1, _ref; + var dir, dirs, fileOrDir, i, j, len, len1, ref, watchOptions; watchOptions = { persistent: true, interval: 1000, @@ -302,9 +302,9 @@ maxListeners: 128 }; dirs = []; - _ref = this.getWatchedDirs(); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - fileOrDir = _ref[_i]; + ref = this.getWatchedDirs(); + for (i = 0, len = ref.length; i < len; i++) { + fileOrDir = ref[i]; if (!fs.existsSync(fileOrDir)) { continue; } @@ -315,8 +315,8 @@ } } dirs = utils.removeDuplicateValues(dirs); - for (_j = 0, _len1 = dirs.length; _j < _len1; _j++) { - dir = dirs[_j]; + for (j = 0, len1 = dirs.length; j < len1; j++) { + dir = dirs[j]; require('watch').watchTree(dir, watchOptions, (function(_this) { return function(file, curr, prev) { if (curr && (curr.nlink === 0 || +curr.mtime !== +(prev != null ? prev.mtime : void 0))) { @@ -339,8 +339,8 @@ })(); - JsPackage = (function(_super) { - __extends(JsPackage, _super); + JsPackage = (function(superClass) { + extend(JsPackage, superClass); function JsPackage(app, config) { JsPackage.__super__.constructor.call(this, app, config); @@ -380,7 +380,7 @@ }; JsPackage.prototype.compileLibs = function(files, parentDir) { - var dir, file, results, slash, stats, _i, _len, _ref; + var dir, file, i, len, ref, results, slash, stats; if (files == null) { files = this.libs; } @@ -388,8 +388,8 @@ parentDir = ""; } results = []; - for (_i = 0, _len = files.length; _i < _len; _i++) { - file = files[_i]; + for (i = 0, len = files.length; i < len; i++) { + file = files[i]; if (utils.endsWith(file, ";")) { results.join(file); } else { @@ -400,7 +400,7 @@ if (stats.isDirectory()) { dir = fs.readdirSync(file); results.push(this.compileLibs(dir, file)); - } else if (stats.isFile() && ((_ref = path.extname(file)) === '.js' || _ref === '.coffee')) { + } else if (stats.isFile() && ((ref = path.extname(file)) === '.js' || ref === '.coffee')) { results.push(fs.readFileSync(file, 'utf8')); } } @@ -419,16 +419,16 @@ })(Package); - TestPackage = (function(_super) { - __extends(TestPackage, _super); + TestPackage = (function(superClass) { + extend(TestPackage, superClass); function TestPackage(app, config) { - var _ref; + var ref; TestPackage.__super__.constructor.call(this, app, config); this.depends = utils.toArray(config.depends); this.testHome = path.dirname(this.target); this.framework = _hem.options.hem.test.frameworks; - if ((_ref = this.framework) !== 'jasmine' && _ref !== 'mocha') { + if ((ref = this.framework) !== 'jasmine' && ref !== 'mocha') { log.errorAndExit("Test frameworks value is not valid: " + this.framework); } this.after += "// HEM: load in specs from test js file\nvar onlyMatchingModules = \"" + (_argv.grep || "") + "\";\nfor (var key in " + this.commonjs + ".modules) {\n if (onlyMatchingModules && key.indexOf(onlyMatchingModules) == -1) {\n continue;\n }\n " + this.commonjs + "(key);\n}"; @@ -440,7 +440,7 @@ }; TestPackage.prototype.getAllTestTargets = function(relative) { - var dep, depapp, homeRoute, pkg, pth, relativeFn, targets, url, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3; + var dep, depapp, homeRoute, i, j, k, l, len, len1, len2, len3, pkg, pth, ref, ref1, ref2, ref3, relativeFn, targets, url; if (relative == null) { relative = true; } @@ -463,16 +463,16 @@ return value; } }; - _ref = this.depends; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - dep = _ref[_i]; - _ref1 = _hem.allApps; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - depapp = _ref1[_j]; + ref = this.depends; + for (i = 0, len = ref.length; i < len; i++) { + dep = ref[i]; + ref1 = _hem.allApps; + for (j = 0, len1 = ref1.length; j < len1; j++) { + depapp = ref1[j]; if (depapp.name === dep) { - _ref2 = depapp.packages; - for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { - pkg = _ref2[_k]; + ref2 = depapp.packages; + for (k = 0, len2 = ref2.length; k < len2; k++) { + pkg = ref2[k]; if (pkg.constructor.name !== "JsPackage") { continue; } @@ -486,9 +486,9 @@ } } } - _ref3 = this.app.packages; - for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { - pkg = _ref3[_l]; + ref3 = this.app.packages; + for (l = 0, len3 = ref3.length; l < len3; l++) { + pkg = ref3[l]; if (pkg.constructor.name !== "JsPackage") { continue; } @@ -509,14 +509,14 @@ }; TestPackage.prototype.getFrameworkFiles = function() { - var file, frameworkPath, targets, url, _i, _len, _ref, _ref1; + var file, frameworkPath, i, len, ref, ref1, targets, url; targets = []; frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); - _ref = fs.readdirSync(frameworkPath); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; - if ((_ref1 = path.extname(file)) === ".js" || _ref1 === ".css") { - url = "" + this.framework + "/" + file; + ref = fs.readdirSync(frameworkPath); + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if ((ref1 = path.extname(file)) === ".js" || ref1 === ".css") { + url = this.framework + "/" + file; targets.push({ url: url, path: url @@ -531,47 +531,47 @@ }; TestPackage.prototype.createTestFiles = function() { - var file, filepath, files, frameworkPath, indexFile, template, _i, _len, _ref, _ref1, _results; + var file, filepath, files, frameworkPath, i, indexFile, len, ref, ref1, results1, template; indexFile = this.getTestIndexFile(); - files = []; - files.push.apply(files, this.getFrameworkFiles()); - files.push.apply(files, this.getAllTestTargets()); - template = utils.tmpl("testing/index", { - commonjs: this.commonjs, - files: files, - before: this.before - }); if (!fs.existsSync(indexFile)) { + files = []; + files.push.apply(files, this.getFrameworkFiles()); + files.push.apply(files, this.getAllTestTargets()); + template = utils.tmpl("testing/index", { + commonjs: this.commonjs, + files: files, + before: this.before + }); fs.outputFileSync(indexFile, template); - } - frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); - _ref = fs.readdirSync(frameworkPath); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; - if ((_ref1 = path.extname(file)) === ".js" || _ref1 === ".css") { - filepath = path.resolve(this.testHome, "" + this.framework + "/" + file); - _results.push(utils.copyFile(path.resolve(frameworkPath, file), filepath)); - } else { - _results.push(void 0); + frameworkPath = path.resolve(__dirname, "../assets/testing/" + this.framework); + ref = fs.readdirSync(frameworkPath); + results1 = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if ((ref1 = path.extname(file)) === ".js" || ref1 === ".css") { + filepath = path.resolve(this.testHome, this.framework + "/" + file); + results1.push(utils.copyFile(path.resolve(frameworkPath, file), filepath)); + } else { + results1.push(void 0); + } } + return results1; } - return _results; }; return TestPackage; })(JsPackage); - CssPackage = (function(_super) { - __extends(CssPackage, _super); + CssPackage = (function(superClass) { + extend(CssPackage, superClass); function CssPackage(app, config) { CssPackage.__super__.constructor.call(this, app, config); } CssPackage.prototype.compile = function() { - var ex, file, fileOrDir, output, requireCss, result, _i, _j, _len, _len1, _ref, _ref1; + var ex, file, fileOrDir, i, j, len, len1, output, ref, ref1, requireCss, result; try { output = []; requireCss = function(filepath) { @@ -579,13 +579,13 @@ delete require.cache[filepath]; return require(filepath); }; - _ref = this.src; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - fileOrDir = _ref[_i]; + ref = this.src; + for (i = 0, len = ref.length; i < len; i++) { + fileOrDir = ref[i]; if (utils.isDirectory(fileOrDir)) { - _ref1 = fs.readdirSync(fileOrDir); - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - file = _ref1[_j]; + ref1 = fs.readdirSync(fileOrDir); + for (j = 0, len1 = ref1.length; j < len1; j++) { + file = ref1[j]; if (!require.extensions[path.extname(file)]) { continue; } diff --git a/lib/phantom.js b/lib/phantom.js index de63897..e65d51f 100644 --- a/lib/phantom.js +++ b/lib/phantom.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var err, jasmine_checkTestResults, jasmine_parseTestResults, log, phantom, reporters, run, waitFor; @@ -15,9 +15,9 @@ errorsOnly: function(el, level, strong) { var desc, indent, tick; indent = function(level) { - var i, ret, _i; + var i, j, ref, ret; ret = ''; - for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { + for (i = j = 0, ref = level; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) { ret = ret + ' '; } return ret; @@ -49,9 +49,9 @@ formatColors: function(el, level, strong) { var desc, indent, results, tick; indent = function(level) { - var i, ret, _i; + var i, j, ref, ret; ret = ''; - for (i = _i = 0; 0 <= level ? _i <= level : _i >= level; i = 0 <= level ? ++_i : --_i) { + for (i = j = 0, ref = level; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) { ret = ret + ' '; } return ret; @@ -156,8 +156,8 @@ return function(checkComplete) { var isCheckComplete; isCheckComplete = function() { - var _ref; - return (_ref = document.querySelector(".duration")) != null ? _ref.innerText : void 0; + var ref; + return (ref = document.querySelector(".duration")) != null ? ref.innerText : void 0; }; return page.evaluate(isCheckComplete, checkComplete); }; diff --git a/lib/resolve.js b/lib/resolve.js index 93c229f..926224b 100644 --- a/lib/resolve.js +++ b/lib/resolve.js @@ -1,11 +1,11 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { - var Module, basename, dirname, extname, invalidDirs, isAbsolute, join, modulePaths, modulerize, repl, resolve, sep, _ref, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + var Module, basename, dirname, extname, invalidDirs, isAbsolute, join, modulePaths, modulerize, ref, repl, resolve, sep, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Module = require('module'); - _ref = require('path'), join = _ref.join, extname = _ref.extname, dirname = _ref.dirname, basename = _ref.basename, resolve = _ref.resolve, sep = _ref.sep; + ref = require('path'), join = ref.join, extname = ref.extname, dirname = ref.dirname, basename = ref.basename, resolve = ref.resolve, sep = ref.sep; isAbsolute = function(path) { return /^\//.test(path); @@ -38,20 +38,20 @@ }; module.exports = function(request, parent) { - var dir, filename, id, index, paths, _, _ref1; + var _, dir, filename, id, index, paths, ref1; if (parent == null) { parent = repl; } - _ref1 = Module._resolveLookupPaths(request, parent), _ = _ref1[0], paths = _ref1[1]; + ref1 = Module._resolveLookupPaths(request, parent), _ = ref1[0], paths = ref1[1]; filename = Module._findPath(request, paths); if (!filename) { throw new Error("Cannot find module: " + request + ". Have you run `npm install .` ?"); } dir = filename; - while (__indexOf.call(invalidDirs, dir) < 0 && __indexOf.call(modulePaths, dir) < 0) { + while (indexOf.call(invalidDirs, dir) < 0 && indexOf.call(modulePaths, dir) < 0) { dir = dirname(dir); } - if (__indexOf.call(invalidDirs, dir) >= 0) { + if (indexOf.call(invalidDirs, dir) >= 0) { index = filename.lastIndexOf("" + sep + request); if (index > 0) { dir = filename.substring(0, index); diff --git a/lib/server.js b/lib/server.js index 92ebcd8..9326792 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var checkForRedirect, connect, createRoutingProxy, fs, http, httpProxy, log, mime, patchServerResponseForRedirects, server, utils; @@ -32,33 +32,33 @@ }; server.middleware = function(hem) { - var app, backend, display, options, pkg, route, statics, value, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4; + var app, backend, display, i, j, k, l, len, len1, len2, len3, options, pkg, ref, ref1, ref2, ref3, ref4, route, statics, value; backend = connect(); options = hem.options.hem; statics = []; - _ref = options != null ? options["static"] : void 0; - for (route in _ref) { - value = _ref[route]; + ref = options != null ? options["static"] : void 0; + for (route in ref) { + value = ref[route]; statics.push({ url: route, path: value }); } - _ref1 = hem.apps; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - app = _ref1[_i]; + ref1 = hem.apps; + for (i = 0, len = ref1.length; i < len; i++) { + app = ref1[i]; log.info("> Apply route mappings for application: " + app.name + ""); - _ref2 = app.packages; - for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { - pkg = _ref2[_j]; + ref2 = app.packages; + for (j = 0, len1 = ref2.length; j < len1; j++) { + pkg = ref2[j]; if (options.baseAppRoute) { pkg.route = utils.cleanRoute(options.baseAppRoute, pkg.route); } log.info(" - Mapping route " + pkg.route + " to " + pkg.target + ""); } - _ref3 = app["static"]; - for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { - route = _ref3[_k]; + ref3 = app["static"]; + for (k = 0, len2 = ref3.length; k < len2; k++) { + route = ref3[k]; if (options.baseAppRoute) { route.url = utils.cleanRoute(options.baseAppRoute, route.url); } @@ -66,8 +66,8 @@ statics.push(route); } } - for (_l = 0, _len3 = statics.length; _l < _len3; _l++) { - route = statics[_l]; + for (l = 0, len3 = statics.length; l < len3; l++) { + route = statics[l]; if (!fs.existsSync(route.path)) { log.errorAndExit("The resource " + route.path + " not found for static mapping " + route.url + ""); } @@ -90,20 +90,20 @@ })(route)); } } - _ref4 = options.proxy; - for (route in _ref4) { - value = _ref4[route]; - display = "" + value.host + ":" + (value.port || 80) + value.path; + ref4 = options.proxy; + for (route in ref4) { + value = ref4[route]; + display = value.host + ":" + (value.port || 80) + value.path; log.info("> Proxy requests " + route + " to " + display + ""); backend.use(route, createRoutingProxy(value)); } return function(req, res, next) { - var str, url, _len4, _m, _ref5, _ref6; - url = ((_ref5 = require("url").parse(req.url)) != null ? _ref5.pathname.toLowerCase() : void 0) || ""; + var len4, m, ref5, ref6, str, url; + url = ((ref5 = require("url").parse(req.url)) != null ? ref5.pathname.toLowerCase() : void 0) || ""; if (url.match(/(\.js|\.css)$/)) { - _ref6 = hem.apps; - for (_m = 0, _len4 = _ref6.length; _m < _len4; _m++) { - app = _ref6[_m]; + ref6 = hem.apps; + for (m = 0, len4 = ref6.length; m < len4; m++) { + app = ref6[m]; if (pkg = app.isMatchingRoute(url)) { str = pkg.build(); res.charset = 'utf-8'; diff --git a/lib/stitch.js b/lib/stitch.js index 9b1c480..5184880 100644 --- a/lib/stitch.js +++ b/lib/stitch.js @@ -1,6 +1,6 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { - var Module, Stitch, compilers, flatten, fs, modulerize, npath, _createModule, _modules, _walk; + var Module, Stitch, _createModule, _modules, _walk, compilers, flatten, fs, modulerize, npath; npath = require('path'); @@ -15,7 +15,7 @@ _modules = {}; _walk = function(path, parent, result) { - var child, module, stat, _i, _len, _ref; + var child, i, len, module, ref, stat; if (parent == null) { parent = path; } @@ -25,9 +25,9 @@ if (!fs.existsSync(path)) { return; } - _ref = fs.readdirSync(path); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - child = _ref[_i]; + ref = fs.readdirSync(path); + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; child = npath.join(path, child); stat = fs.statSync(child); if (stat.isDirectory()) { @@ -67,28 +67,28 @@ var path; this.paths = paths != null ? paths : []; this.paths = (function() { - var _i, _len, _ref, _results; - _ref = this.paths; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - path = _ref[_i]; - _results.push(npath.resolve(path)); + var i, len, ref, results; + ref = this.paths; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + path = ref[i]; + results.push(npath.resolve(path)); } - return _results; + return results; }).call(this); } Stitch.prototype.resolve = function() { var path; return flatten((function() { - var _i, _len, _ref, _results; - _ref = this.paths; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - path = _ref[_i]; - _results.push(_walk(path)); + var i, len, ref, results; + ref = this.paths; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + path = ref[i]; + results.push(_walk(path)); } - return _results; + return results; }).call(this)); }; @@ -97,9 +97,9 @@ })(); Module = (function() { - function Module(filename, parent) { - this.filename = filename; - this.parent = parent; + function Module(filename1, parent1) { + this.filename = filename1; + this.parent = parent1; this.ext = npath.extname(this.filename).slice(1); this.id = modulerize(this.filename.replace(npath.join(this.parent, npath.sep), '')); } diff --git a/lib/test.js b/lib/test.js index dce9166..289561d 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,7 +1,7 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var async, createKarmaFileList, events, fs, log, path, phantom, run, runBrowser, runKarma, runPhantom, utils, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; fs = require('fs'); @@ -36,11 +36,11 @@ }; runBrowser = function(apps, options, done) { - var app, open, q, tasks, testFile, testName, _i, _len; + var app, i, len, open, q, tasks, testFile, testName; open = require("open"); tasks = {}; - for (_i = 0, _len = apps.length; _i < _len; _i++) { - app = apps[_i]; + for (i = 0, len = apps.length; i < len; i++) { + app = apps[i]; testName = app.name; testFile = app.getTestPackage().getTestIndexFile(); tasks[testName] = (function(testFile) { @@ -63,11 +63,11 @@ }; runPhantom = function(apps, options, done) { - var app, q, tasks, testFile, testName, testPort, _i, _len; + var app, i, len, q, tasks, testFile, testName, testPort; options.output || (options.output = "passOrFail"); tasks = {}; - for (_i = 0, _len = apps.length; _i < _len; _i++) { - app = apps[_i]; + for (i = 0, len = apps.length; i < len; i++) { + app = apps[i]; testName = app.name; testFile = app.getTestPackage().getTestIndexFile(); testPort = 12300 + Object.keys(tasks).length; @@ -105,7 +105,7 @@ }; runKarma = function(apps, options) { - var app, karma, q, tasks, testConfig, _i, _len; + var app, i, karma, len, q, tasks, testConfig; if (options == null) { options = {}; } @@ -117,8 +117,8 @@ options.reporters = Array.isArray(options.reporters) || options.reporters.split(/[ ,]+/); options.frameworks = Array.isArray(options.frameworks) || options.frameworks.split(/[ ,]+/); options.browsers = Array.isArray(options.browsers) || options.browsers.split(/[ ,]+/); - for (_i = 0, _len = apps.length; _i < _len; _i++) { - app = apps[_i]; + for (i = 0, len = apps.length; i < len; i++) { + app = apps[i]; testConfig = { singleRun: true, autoWatch: false, @@ -131,8 +131,7 @@ }; testConfig.files = createKarmaFileList(app); testConfig.coverageReporter = options.coverageReporter || null; - console.log(testConfig); - if (__indexOf.call(testConfig.reporters, 'junit') >= 0) { + if (indexOf.call(testConfig.reporters, 'junit') >= 0) { testConfig.junitReporter = { outputFile: app.name + '-test-results.xml', suite: app.name @@ -173,11 +172,11 @@ }; createKarmaFileList = function(app) { - var files, target, _i, _len, _ref; + var files, i, len, ref, target; files = []; - _ref = app.getTestPackage().getAllTestTargets(false); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - target = _ref[_i]; + ref = app.getTestPackage().getAllTestTargets(false); + for (i = 0, len = ref.length; i < len; i++) { + target = ref[i]; files.push(target.path); } return files; diff --git a/lib/utils.js b/lib/utils.js index 0b791a1..ad7527e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,8 +1,8 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var clean, extend, flatten, fs, isWin, path, tmplCache, utils, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, - __slice = [].slice; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + slice = [].slice; path = require('path'); @@ -13,12 +13,12 @@ isWin = !!require('os').platform().match(/^win/); utils.flatten = flatten = function(array, results) { - var item, _i, _len; + var i, item, len; if (results == null) { results = []; } - for (_i = 0, _len = array.length; _i < _len; _i++) { - item = array[_i]; + for (i = 0, len = array.length; i < len; i++) { + item = array[i]; if (Array.isArray(item)) { flatten(item, results); } else if (item) { @@ -29,11 +29,11 @@ }; utils.arrayToString = function(value) { - var line, result, _i, _len; + var i, len, line, result; if (Array.isArray(value)) { result = ""; - for (_i = 0, _len = value.length; _i < _len; _i++) { - line = value[_i]; + for (i = 0, len = value.length; i < len; i++) { + line = value[i]; result += line + "\n"; } return result; @@ -43,11 +43,11 @@ }; utils.removeDuplicateValues = function(array) { - var newArray, value, _i, _len; + var i, len, newArray, value; newArray = []; - for (_i = 0, _len = array.length; _i < _len; _i++) { - value = array[_i]; - if (__indexOf.call(newArray, value) < 0) { + for (i = 0, len = array.length; i < len; i++) { + value = array[i]; + if (indexOf.call(newArray, value) < 0) { newArray.push(value); } } @@ -91,7 +91,7 @@ }; utils.copyFile = function(from, to) { - var BUF_LENGTH, bytesRead, fdr, fdw, pos, _buff; + var BUF_LENGTH, _buff, bytesRead, fdr, fdw, pos; fs.createFileSync(to); BUF_LENGTH = 64 * 1024; _buff = new Buffer(BUF_LENGTH); @@ -127,7 +127,7 @@ if (tmplCache[str]) { fn = tmplCache[str]; } else { - template = utils.loadAsset("" + str + ".tmpl"); + template = utils.loadAsset(str + ".tmpl"); fn = utils.tmpl(template); } } else { @@ -140,18 +140,18 @@ }; clean = function(values, sep, trimStart) { - var regexp, result, value, _i, _len; + var i, len, regexp, result, value; if (trimStart == null) { trimStart = false; } result = ""; - for (_i = 0, _len = values.length; _i < _len; _i++) { - value = values[_i]; + for (i = 0, len = values.length; i < len; i++) { + value = values[i]; if (value) { result = result + sep + value; } } - regexp = new RegExp("" + sep + "+", "g"); + regexp = new RegExp(sep + "+", "g"); result = result.replace(regexp, sep); if (trimStart && utils.startsWith(result, sep)) { result = result.slice(sep.length); @@ -164,7 +164,7 @@ utils.cleanPath = function() { var cleanPath, paths, result; - paths = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + paths = 1 <= arguments.length ? slice.call(arguments, 0) : []; result = clean(paths, path.sep, true); if (isWin || true) { cleanPath = new RegExp(/\//g); @@ -175,7 +175,7 @@ utils.cleanRoute = function() { var routes; - routes = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + routes = 1 <= arguments.length ? slice.call(arguments, 0) : []; return clean(routes, "/"); }; diff --git a/lib/versioning.js b/lib/versioning.js index 9866710..a0dd210 100644 --- a/lib/versioning.js +++ b/lib/versioning.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.1 (function() { var BuildVersion, NpmPackageVersion, fs, log, path, types, updateVersionInAppFiles, updateVersionInData, utils; @@ -13,19 +13,19 @@ types = {}; updateVersionInAppFiles = function(files, packages, value) { - var data, file, key, pkg, _i, _len, _results; - _results = []; - for (_i = 0, _len = files.length; _i < _len; _i++) { - file = files[_i]; + var data, file, i, key, len, pkg, results; + results = []; + for (i = 0, len = files.length; i < len; i++) { + file = files[i]; log("- updating file " + file + " with version: " + value + ""); data = fs.readFileSync(file, 'utf8'); for (key in packages) { pkg = packages[key]; data = updateVersionInData(data, value, pkg); } - _results.push(fs.writeFileSync(file, data)); + results.push(fs.writeFileSync(file, data)); } - return _results; + return results; }; updateVersionInData = function(data, value, pkg) { diff --git a/package.json b/package.json index 4b21d95..1348dd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hem", - "version": "0.4.12", + "version": "0.4.13", "description": "stitches CommonJS, and ties up other lose ends of web-app development.", "keywords": [ "spine", diff --git a/src/hem.coffee b/src/hem.coffee index 3414e70..328a689 100644 --- a/src/hem.coffee +++ b/src/hem.coffee @@ -138,6 +138,7 @@ class Hem app.watch() for app in @apps test: -> + @build() # set test options testOptions = @options.hem.tests or {} testOptions.basePath or= @home diff --git a/src/package.coffee b/src/package.coffee index edae802..db1c7e0 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -404,20 +404,21 @@ class TestPackage extends JsPackage # TODO: only do this for browser tests??? createTestFiles: -> - # create index file + # create index file and libs if they currently don't exist indexFile = @getTestIndexFile() - files = [] - files.push.apply(files, @getFrameworkFiles()) - files.push.apply(files, @getAllTestTargets()) - template = utils.tmpl("testing/index", { commonjs: @commonjs, files: files, before: @before } ) - fs.outputFileSync(indexFile, template) unless fs.existsSync(indexFile) - - # copy the framework files if they aren't present - frameworkPath = path.resolve(__dirname, "../assets/testing/#{@framework}") - for file in fs.readdirSync(frameworkPath) - if path.extname(file) in [".js",".css"] - filepath = path.resolve(@testHome, "#{@framework}/#{file}") - utils.copyFile(path.resolve(frameworkPath, file), filepath) + unless fs.existsSync(indexFile) + files = [] + files.push.apply(files, @getFrameworkFiles()) + files.push.apply(files, @getAllTestTargets()) + template = utils.tmpl("testing/index", { commonjs: @commonjs, files: files, before: @before } ) + fs.outputFileSync(indexFile, template) + + # copy the framework files if they aren't present + frameworkPath = path.resolve(__dirname, "../assets/testing/#{@framework}") + for file in fs.readdirSync(frameworkPath) + if path.extname(file) in [".js",".css"] + filepath = path.resolve(@testHome, "#{@framework}/#{file}") + utils.copyFile(path.resolve(frameworkPath, file), filepath) class CssPackage extends Package diff --git a/src/test.coffee b/src/test.coffee index ff22120..c3c49b2 100644 --- a/src/test.coffee +++ b/src/test.coffee @@ -123,8 +123,6 @@ runKarma = (apps, options = {}) -> # coverage reporter option testConfig.coverageReporter = options.coverageReporter or null - console.log testConfig - # handle junit special case for report file location if 'junit' in testConfig.reporters testConfig.junitReporter = From c0187ba2cb275c6236c1e97c158c24db5cd4d2ba Mon Sep 17 00:00:00 2001 From: Chris Engebretson Date: Mon, 30 Nov 2015 00:02:09 -0600 Subject: [PATCH 165/167] updated examples and readme --- README.md | 261 +- examples/Procfile | 1 + examples/app/controllers/.gitkeep | 0 examples/app/controllers/test.coffee | 1 - examples/app/index.coffee | 11 +- examples/app/lib/setup.coffee | 6 + examples/app/models/.gitkeep | 0 examples/app/views/sample.jade | 2 + examples/css/index.styl | 13 +- examples/css/mixin.styl | 89 + examples/lib/custom.js | 1 - examples/lib/jade_runtime.js | 252 + examples/lib/jquery.js | 9210 ++++++++++++ examples/package.json | 9 + examples/public/application.css | 15 +- examples/public/application.js | 11692 +++++++++++++++- examples/public/favicon.ico | 0 examples/public/index.html | 25 +- examples/slug.coffee | 52 + examples/slug.json | 10 - examples/test/public/index.html | 113 + examples/test/public/jasmine/1.jasmine.js | 2600 ++++ .../test/public/jasmine/2.jasmine-html.js | 681 + examples/test/public/jasmine/jasmine.css | 82 + examples/test/public/specs.js | 68 + examples/test/specs/.gitkeep | 0 package.json | 6 +- src/test.coffee | 41 - 28 files changed, 25126 insertions(+), 115 deletions(-) create mode 100644 examples/Procfile create mode 100644 examples/app/controllers/.gitkeep delete mode 100644 examples/app/controllers/test.coffee create mode 100644 examples/app/lib/setup.coffee create mode 100644 examples/app/models/.gitkeep create mode 100644 examples/app/views/sample.jade create mode 100644 examples/css/mixin.styl delete mode 100644 examples/lib/custom.js create mode 100644 examples/lib/jade_runtime.js create mode 100644 examples/lib/jquery.js create mode 100644 examples/package.json create mode 100644 examples/public/favicon.ico create mode 100644 examples/slug.coffee delete mode 100644 examples/slug.json create mode 100644 examples/test/public/index.html create mode 100644 examples/test/public/jasmine/1.jasmine.js create mode 100644 examples/test/public/jasmine/2.jasmine-html.js create mode 100644 examples/test/public/jasmine/jasmine.css create mode 100644 examples/test/public/specs.js create mode 100644 examples/test/specs/.gitkeep diff --git a/README.md b/README.md index 55bc2dd..b0b5af2 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,235 @@ -Experimental Hem (aka version 4) -================ +# Introduction -This branch of hem is an experimental refactoring of the current hem code base. The changes in this branch are born from trying to get the current hem -to work with multiple frontend apps that shared a common code base but at the same time were sepearate web apps. +Hem is a project for compiling CommonJS modules when building JavaScript web applications. You can think of Hem as [Bundler](http://gembundler.com/) for Node, or [Stitch](https://github.com/sstephenson/stitch) on steroids. Or it is kind of like [Yeoman](http://yeoman.io/), tailored specifically for spine.js -Initially we had used multiple -hem/spine projects for each frontend and git sub-tree to share the common code, but even that seems tedius. The goal going forward is to be able to -have multiple applications definied with the slug.json and have the ability to be able to build/server/test them all together or on a per app basis. -This allows us to generate a common.js file that contains the main spine/jquery javascript which then can be included in each project's html file. +This is rather awesome, as it means you don't need to faff around with coping around JavaScript files. jQuery can be a npm dependency, so can jQueryUI and all your custom components. Hem will resolve dependencies dynamically, bundling them together into one file to be served up. Upon deployment, you can serialize your application to disk and serve it statically. -In addition, the current version of hem needs a bit of setup/configuration in the slug.json file. I am hoping that if a user follows certain -conventions and folder structures the amount of configuration should be minimal. The current spine structure seems to be... +Hem was primarily designed for developing Spine.js based Single Page Web Applications (SPA's), so a major role it fills is to tie up some of the other lose ends of a frontend development project - things like running tests, precompiling code, and preparing it for deployment. It can even help out in the API connection stuff if your app needs that. + +# Installation + + npm install -g hem + +or + + npm install -g git://github.com/spine/hem.git + +or ...fun trick. + + hem install -g hem + git clone https://github.com/spine/hem.git + cd hem + npm link + +the last approach is great if you want to customize hem for your own use, (...or for developing npm packages in general). Just fork and use you own path! + +## Dependencies + +Hem has two types of dependency resolutions: Node modules and Stitch modules. + +**Node modules**: Hem will recursively resolve any external Node modules your code references. This means that Spine, jQuery etc, can all be external Node modules - you don't have lots of libraries floating around your application. This also has the advantage of explicit versioning; you'll have much more control over external libraries. + +**Stitch modules**: Hem will bundle up your whole application (without any static dependency analysis), automatically converting CoffeeScript (.coffee) and jQuery template (.tmpl) files. Hem doesn't use static analysis of your application to determine dependencies, as that's often not-viable considering the amount of dynamically loaded dependencies most applications use. + +In a nutshell, Hem will make sure your application and all its dependencies are wrapped up in a single file, ready to be served to web browsers. + +## CommonJS + +CommonJS modules are at the core of Hem, and are the format Hem aspects every module to adhere to. As well as ensuring your code is encapsulated and modular, CommonJS modules give you dependency management, scope isolation, and namespacing. They should be used in any JavaScript application that spans more than a few files. + +To find out more about why CommonJS modules are a great solution to JavaScript dependency management, see the CommonJS guide. It's not that AMD pattern is bad by the way, just not the way hem went for now. + +### Example CommonJS + +The format is remarkably straightforward, but is something you'll have to adhere to in every file to make it work. CommonJS uses explicit exporting; so to expose a property inside a module to other modules, you'll need to do something like this: + +In app/controllers/users.coffee: + + class Users extends Spine.Controller + +Explicitly export the Users object + + module.exports = Users + +The format mandates that a module object will be available in every module. However, if you're targeting both the CommonJS format, and a normal environment, you can do a conditional export, checking that the module object exists. + + module?.exports = Users + +### Requiring modules + +Requiring other modules is just as straightforward; just use the require() function. + + Users = require("controllers/users") + +In Hem apps, all module paths are relative to the app folder - so don't require files relative to the specific module. +CSS + +## Styling your app + +Hem will also bundle up all your application's CSS into one file, ready to serve up to clients. CSS encapsulation and modularity is just as important as JavaScript de-coupling (and can get as equally messy if it's not done right); Hem goes some way to help you with this. To compile CSS, Hem uses an excellent library called Stylus. Stylus is mostly a superset of CSS, and the normal CSS syntax will work just fine if that's all you want. + +However the awesome part of Stylus is the extra syntactical sugar it brings to CSS. Features like optional braces and colons, mixins, variables and significant whitespace all vastly improve your application's CSS, and decreases the amount of typing necessary. In a nutshell, Stylus is to CSS as CoffeeScript is to JavaScript. + +Stylus files are indicated by the .styl extension, and are automatically compiled down to CSS by Hem. This all happens in the background, so you don't need to worry about it. + +Also in the pipeline is the ability to bundle up CSS from Node modules. + +## Configuration + +Hem has some good defaults (convention over configuration), but sometimes you'll need to change them, especially when adding libraries and dependencies. + +For configuration, Hem uses a `slug.coffee` file, located in the root of your application. Hem expects a certain directory structure. A main JavaScript/CoffeeScript file under app/index, a main CSS/Stylus file under css/index and a public directory to serve static assets from. If you're using `Spine.app`, these will all be generated for you. A typical spine app will have the following folder structure ``` -spineapp - - app // js/coffee files that are stitched together using commonjs - - public // static files and final app js/css build destination - - lib // plain old js/coffee files appended to js file. - - node_modules // node modules to include - - test // test/spec files - - css // css and stylus files to compile +├── app +│   ├── controllers +│   ├── index.coffee +│   ├── lib +│   ├── models +│   └── views +├── css +│   ├── index.styl +│   └── mixin.styl +├── lib +│   ├── jade_runtime.js +│   └── jquery.js +├── node_modules +│   ├── jade +│   ├── spine +│   ├── stylus +├── package.json +├── public +│   ├── application.css +│   ├── application.js +│   ├── favicon.ico +│   └── index.html +├── slug.coffee +└── test + ├── public + └── specs ``` -By using the above structure, everything should be hooked up and ready to go from hem's point of view. But I also want to allow the ability to -easily override this struture, perhaps for cases when somebody is creating a non-spine web app and just wants to make use of the compiler/server. +Hem also allows you to specify static JavaScript libraries to include, under the "libs" option: + + { + "libs": [ + "./lib/other.js" + ] + } + +These will be included before the rest of your JavaScript, and without being wrapped in the CommonJS module transport format. In addition, Hem lets you specify an array of npm/Node dependencies, to be included in your application. For example, in a default generated Spine.app slug.json file, you'll find the following dependencies: + + { + "dependencies": [ + "es5-shimify", + "json2ify", + "jqueryify", + "jquery.tmpl", + "spine" + ] + } + +These dependencies will be statically analyzed, to recursively resolve additional dependencies, and then wrapped in the CommonJS module format, being served up with the rest of your application's JavaScript. In other words, you don't have to have jquery.js, spine.js and json2.js floating around inside your application, they can be Node modules, installed through npm. + +## Usage + +Ok, so now we've looked at how to configure Hem, let's actually use it in an application. As I mentioned earlier, this step is much easier with an application previously generated by Spine.app, and I advise you go down this route if you're unfamiliar with Hem. + +Now, we can start a development server, which will dynamically build our application every request, using the server command: + + hem server + +By default, your spine application is served at http://localhost:9294. +You can configure the host and port from command line or as settings in your package.json + + hem server -p 9295 + +Would result in your application being served at http://localhost:9295/ + +If there's an index.html file under public, it'll be served up. Likewise, any calls to /application.js and /application.css will return the relevant JavaScript and CSS. + +For the sake of avoiding cross domain issues in development environments when your spine app is utilizing an ajax api there is a optional proxy server built into hem. +As of Hem 0.3 including a 'routes' block in your slug.json configures that: + + "server": { + "port" : 9294 + }, + "routes": [ + { "/myApiApp/mySpineApp" : "./public" }, + { "/myApiApp/mySpineApp/test" : "./test/public" }, + { "/myApiApp" : { "host": "127.0.0.1", "port": 8080, "hostPath": "/myApiApp", "patchRedirect": true } } + ], + "packages": { + "sampleApp": { + "libs" : ["lib/runtime.js"], + "modules" : [ + "es5-shimify", + "json2ify", + "jqueryify", + "spine", + "spine/lib/local", + "spine/lib/ajax", + "spine/lib/route", + "spine/lib/manager" + ], + "paths" : ["./app"], + "target" : "./public/application.js", + "jsAfter": "jade.rethrow = function rethrow(err, filename, lineno){ throw err; } " + }, + "css": { + "paths" : "./css", + "target" : "./public/application.css" + }, + "test": { + "identifier" : "specs", + "jsAfter" : "require('lib/setup'); for (var key in specs.modules) specs(key);", + "paths" : ["./test/specs"], + "target" : "./test/public/specs.js" + } + } + +now http://127.0.0.1:9294/myApiApp/mySpineApp/ will return the spine app. + +and http://127.0.0.1:9294/myApiApp/ will return your API App + +so relative links like @url = "../api/album/" from inside your spine app models can resolve against your api app without issue + +When you're ready to deploy, you should build your application, serializing it to disk. + + hem build + +This will write application.js and application.css and specs.js to the file system. You can then commit it with version control and have your server can statically serve your application, without having to use Node, or have any npm dependencies installed. + +**TODO**: hem build should have an option to version the js/css it produces and replace the references in index.html as well + +###Views + +Currently Hem supports three template options out of the box +* Basic HTML - stringifed html... that you can render... +* [Eco](https://github.com/sstephenson/eco) - erb like syntax, like ejs, but with coffeeScript. Nice, but seems to be a somewhat abandoned project +* [Jade](https://github.com/visionmedia/jade) - haml like syntax with optional coffeescript filter + * to use jade templates you must include jades [runtime.js](https://github.com/visionmedia/jade/blob/master/runtime.js) as a lib in your spine projects slug.json + "libs": ["lib/runtime.js"], + +###Testing -Other goals and features... ----- +[Karma(formally Testacular)](http://karma-runner.github.io/0.8/index.html) is a neat little tool that we leverage with hem. -In addition to the above ideas, I want to try to get some of the features below integrated into hem... + hem test -- [*] Easier to setup proxy -- [*] ability to act as middleware for connect/express apps -- [*] versioning abilities -- [ ] easier testing setup and execution (karma/phantomjs) -- [ ] Simple event system -- [ ] manifest creation -- [ ] livereload abilities for css and possibly js -- [ ] update examples/documention -- [ ] really do need to write some tests for hem +Will run tests in a spine projects test directory. Tests can be written in CoffeeScript! -Will look into but not 100% sure ---- + hem watch -t -These would be nice to have things, will have to research it more in the future... +Will run tests as test files are updated. Karma makes it smart. Only previously failing tests run. If there were no previously failing tests all will run. +Default is to run tests in a new Chrome window. Firefox, Phantom or some others can be used as well. -- [*] easier ways to add your own compilers/extensions -- [ ] source mapping -- [ ] integrate spine.app commands into hem -- [ ] possibly look at AMD vs commonjs??? -- [ ] jshint/lint checks?? + hem server -When will it be done?? ---- +will watch and compile jasmine tests, but you will have to go to localhost:9294/test (or whereever you configured hem to run...) and manually trigger page tests to run. -Thats a good question :) I think its possible to have the main features done, tested, and ready to go by the end of november :o). +#TODO +* Better document Karma usage instructions. +* Make template and CSS pre-processor choices configurable +* This would be cool -> integrate with live-reload for changes. We should be able to inject [live-reload](https://github.com/livereload/livereload-js) while in server mode and then run the livereload-server inside hem or could strata handle the incoming requests? Looks like simple json requests. Would we need an option for the browser to regain its focus? Another option is instead of injecting the script into the page is to use the live reload plugin. diff --git a/examples/Procfile b/examples/Procfile new file mode 100644 index 0000000..ad905e7 --- /dev/null +++ b/examples/Procfile @@ -0,0 +1 @@ +web: serveup ./public \ No newline at end of file diff --git a/examples/app/controllers/.gitkeep b/examples/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/app/controllers/test.coffee b/examples/app/controllers/test.coffee deleted file mode 100644 index 11ca105..0000000 --- a/examples/app/controllers/test.coffee +++ /dev/null @@ -1 +0,0 @@ -alert("a test controller") \ No newline at end of file diff --git a/examples/app/index.coffee b/examples/app/index.coffee index f9e8e80..675e3dc 100644 --- a/examples/app/index.coffee +++ b/examples/app/index.coffee @@ -1,4 +1,11 @@ +require('lib/setup') + Spine = require('spine') -$ = require('jquery-browserify') -exports.test = -> alert('test') \ No newline at end of file +class App extends Spine.Controller + constructor: -> + super + # Getting started - should be removed + @html require("views/sample")({version:Spine.version}) + +module.exports = App diff --git a/examples/app/lib/setup.coffee b/examples/app/lib/setup.coffee new file mode 100644 index 0000000..bcf2f32 --- /dev/null +++ b/examples/app/lib/setup.coffee @@ -0,0 +1,6 @@ +require('spine') +require('spine/lib/local') +require('spine/lib/manager') +require('spine/lib/route') + +#this is a good place to do settings that aren't related to spine diff --git a/examples/app/models/.gitkeep b/examples/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/app/views/sample.jade b/examples/app/views/sample.jade new file mode 100644 index 0000000..e06b067 --- /dev/null +++ b/examples/app/views/sample.jade @@ -0,0 +1,2 @@ +h2 Welcome to Spine.js version #{version} +p Time to get busy with this magic! diff --git a/examples/css/index.styl b/examples/css/index.styl index 88b8a97..9c6c7b7 100644 --- a/examples/css/index.styl +++ b/examples/css/index.styl @@ -1,5 +1,12 @@ +@import './mixin' +body + color: #222 -body { - font: 12px Helvetica, Arial, sans-serif; -} \ No newline at end of file +h2 + text-align: center + font-size: 22px + +p + text-align: center + font-size: 16px diff --git a/examples/css/mixin.styl b/examples/css/mixin.styl new file mode 100644 index 0000000..79d681b --- /dev/null +++ b/examples/css/mixin.styl @@ -0,0 +1,89 @@ +border-radius() + -moz-border-radius: arguments + -webkit-border-radius: arguments + border-radius: arguments + +/* Vertical Background Gradient */ +vbg-gradient(fc = #FFF, tc = #FFF) + background: fc + background: -webkit-gradient(linear, left top, left bottom, from(fc), to(tc)) + background: -moz-linear-gradient(top, fc, tc) + background: linear-gradient(top, fc, tc) + +/* Horizontal Background Gradient */ +hbg-gradient(fc = #FFF, tc = #FFF) + background: fc + background: -webkit-gradient(linear, left top, right top, from(fc), to(tc)) + background: -moz-linear-gradient(left, fc, tc) + background: linear-gradient(left, fc, tc) + +box-shadow() + -moz-box-shadow: arguments + -webkit-box-shadow: arguments + box-shadow: arguments + +inset-box-shadow() + -moz-box-shadow: inset arguments + -webkit-box-shadow: inset arguments + box-shadow: inset arguments + +box-flex(s = 0) + -webkit-box-flex: s + -moz-box-flex: s + box-flex: s + +hbox() + display: -webkit-box + -webkit-box-orient: horizontal + -webkit-box-align: stretch + -webkit-box-pack: start + + display: -moz-box + -moz-box-orient: horizontal + -moz-box-align: stretch + -moz-box-pack: start + +vbox() + display: -webkit-box + -webkit-box-orient: vertical + -webkit-box-align: stretch + + display: -moz-box + -moz-box-orient: vertical + -moz-box-align: stretch + +border-box() + -webkit-box-sizing: border-box + -moz-box-sizing: border-box + box-sizing: border-box + +transition(s = 0.3s, o = opacity, t = linear) + -webkit-transition: s o t + -moz-transition: s o t + transition: s o t + +ellipsis() + text-overflow: ellipsis + overflow: hidden + white-space:nowrap + +inset-line(opacity = 0.4, size = 1px) + inset-box-shadow(0, size, 0, rgba(255, 255, 255, opacity)) + +outset-line(opacity = 0.4, size = 1px) + box-shadow(0, size, 0, rgba(255, 255, 255, opacity)) + +box-pack(type = center) + -webkit-box-pack: type + -moz-box-pack: type + box-pack: type + +transform(tr) + -webkit-transform: tr + -moz-transform: tr + -ms-transform: tr + -o-transform: tr + transform: tr + +hacel() + transform(translate3d(0,0,0)) \ No newline at end of file diff --git a/examples/lib/custom.js b/examples/lib/custom.js deleted file mode 100644 index e47a290..0000000 --- a/examples/lib/custom.js +++ /dev/null @@ -1 +0,0 @@ -function custom(){ alert("Custom!"); } \ No newline at end of file diff --git a/examples/lib/jade_runtime.js b/examples/lib/jade_runtime.js new file mode 100644 index 0000000..6bd4a65 --- /dev/null +++ b/examples/lib/jade_runtime.js @@ -0,0 +1,252 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jade = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o} escaped + * @return {String} + */ +exports.cls = function cls(classes, escaped) { + var buf = []; + for (var i = 0; i < classes.length; i++) { + if (escaped && escaped[i]) { + buf.push(exports.escape(joinClasses([classes[i]]))); + } else { + buf.push(joinClasses(classes[i])); + } + } + var text = joinClasses(buf); + if (text.length) { + return ' class="' + text + '"'; + } else { + return ''; + } +}; + + +exports.style = function (val) { + if (val && typeof val === 'object') { + return Object.keys(val).map(function (style) { + return style + ':' + val[style]; + }).join(';'); + } else { + return val; + } +}; +/** + * Render the given attribute. + * + * @param {String} key + * @param {String} val + * @param {Boolean} escaped + * @param {Boolean} terse + * @return {String} + */ +exports.attr = function attr(key, val, escaped, terse) { + if (key === 'style') { + val = exports.style(val); + } + if ('boolean' == typeof val || null == val) { + if (val) { + return ' ' + (terse ? key : key + '="' + key + '"'); + } else { + return ''; + } + } else if (0 == key.indexOf('data') && 'string' != typeof val) { + if (JSON.stringify(val).indexOf('&') !== -1) { + console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' + + 'will be escaped to `&`'); + }; + if (val && typeof val.toISOString === 'function') { + console.warn('Jade will eliminate the double quotes around dates in ' + + 'ISO form after 2.0.0'); + } + return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'"; + } else if (escaped) { + if (val && typeof val.toISOString === 'function') { + console.warn('Jade will stringify dates in ISO form after 2.0.0'); + } + return ' ' + key + '="' + exports.escape(val) + '"'; + } else { + if (val && typeof val.toISOString === 'function') { + console.warn('Jade will stringify dates in ISO form after 2.0.0'); + } + return ' ' + key + '="' + val + '"'; + } +}; + +/** + * Render the given attributes object. + * + * @param {Object} obj + * @param {Object} escaped + * @return {String} + */ +exports.attrs = function attrs(obj, terse){ + var buf = []; + + var keys = Object.keys(obj); + + if (keys.length) { + for (var i = 0; i < keys.length; ++i) { + var key = keys[i] + , val = obj[key]; + + if ('class' == key) { + if (val = joinClasses(val)) { + buf.push(' ' + key + '="' + val + '"'); + } + } else { + buf.push(exports.attr(key, val, false, terse)); + } + } + } + + return buf.join(''); +}; + +/** + * Escape the given string of `html`. + * + * @param {String} html + * @return {String} + * @api private + */ + +var jade_encode_html_rules = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' +}; +var jade_match_html = /[&<>"]/g; + +function jade_encode_char(c) { + return jade_encode_html_rules[c] || c; +} + +exports.escape = jade_escape; +function jade_escape(html){ + var result = String(html).replace(jade_match_html, jade_encode_char); + if (result === '' + html) return html; + else return result; +}; + +/** + * Re-throw the given `err` in context to the + * the jade in `filename` at the given `lineno`. + * + * @param {Error} err + * @param {String} filename + * @param {String} lineno + * @api private + */ + +exports.rethrow = function rethrow(err, filename, lineno, str){ + if (!(err instanceof Error)) throw err; + if ((typeof window != 'undefined' || !filename) && !str) { + err.message += ' on line ' + lineno; + throw err; + } + try { + str = str || require('fs').readFileSync(filename, 'utf8') + } catch (ex) { + rethrow(err, null, lineno) + } + var context = 3 + , lines = str.split('\n') + , start = Math.max(lineno - context, 0) + , end = Math.min(lines.length, lineno + context); + + // Error context + var context = lines.slice(start, end).map(function(line, i){ + var curr = i + start + 1; + return (curr == lineno ? ' > ' : ' ') + + curr + + '| ' + + line; + }).join('\n'); + + // Alter exception message + err.path = filename; + err.message = (filename || 'Jade') + ':' + lineno + + '\n' + context + '\n\n' + err.message; + throw err; +}; + +exports.DebugItem = function DebugItem(lineno, filename) { + this.lineno = lineno; + this.filename = filename; +} + +},{"fs":2}],2:[function(require,module,exports){ + +},{}]},{},[1])(1) +}); diff --git a/examples/lib/jquery.js b/examples/lib/jquery.js new file mode 100644 index 0000000..eed1777 --- /dev/null +++ b/examples/lib/jquery.js @@ -0,0 +1,9210 @@ +/*! + * jQuery JavaScript Library v2.1.4 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2015-04-28T16:01Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Support: Firefox 18+ +// Can't be in strict mode, several libs including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.4", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.constructor && + !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android<4.0, iOS<6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE9-11+ + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + + // Support: iOS 8.2 (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.2.0-pre + * http://sizzlejs.com/ + * + * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-16 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // http://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + nodeType = context.nodeType; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed && documentIsHTML ) { + + // Try to shortcut find operations when possible (e.g., not under DocumentFragment) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType !== 1 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, parent, + doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + parent = doc.defaultView; + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", unloadHandler, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", unloadHandler ); + } + } + + /* Support tests + ---------------------------------------------------------------------- */ + documentIsHTML = !isXML( doc ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + docElem.appendChild( div ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ + if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibing-combinator selector` fails + if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Support: Blackberry 4.6 + // gEBID returns nodes no longer in the document (#6963) + if ( elem && elem.parentNode ) { + // Inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // Add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // If we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // We once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android<4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android<4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Safari<=5.1 + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Safari<=5.1, Android<4.2 + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<=11+ + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome<28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: Android<4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// Support: Firefox, Chrome, Safari +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, type, key, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optimization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "