rex-template
Version:
Rex-Template recursively compiles all of the Handlebars templates in a folder into a single file, watching for changes afterward.
325 lines (288 loc) • 10.2 kB
JavaScript
/*###############################################################################
#
# _ __ _____ __ Welcome to the _
# | '__/ _ \ \/ / ___ __ ___ ____ _ __| |_ ___ _ __
# | | | __/> < / -_) _/ _ (_-< || (_-< _/ -_) ' \
# |_| \___/_/\_\\___\__\___/__/\_, /__/\__\___|_|_|_|
# |__/
#
# The rex-* ecosystem is a collection of like-minded modules for Node.js/NPM
# that allow developers to reduce their time spent developing by a wide margin.
#
# Header File Version: 0.0.1, 06/08/2013
#
# The MIT License (MIT)
#
# Copyright (c) 2013 Pierce Moore <me@prex.io>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
#######*/
var cli = require('rex-shell')
, utils = require('rex-utils')
, _ = require('rex-utils')._
, handlebars = require('handlebars')
, fs = require('fs')
, path = require('path')
, watch = require('node-watch')
, async = require('async')
, EventEmitter = require('events').EventEmitter
, Miyagi = new EventEmitter()
, package = require('../package')
, app = package.config
, compiled = {}
, count = 0
, remaining = 0
, skipped = 0
, iteration = 1
, startTime = 0
, elapsedTime = 0
, timeSaved = 0.0
, lastCompileTime = new Date().getTime()
, argv = require('optimist')
.usage(app.cli.usage)
.alias('h','help')
.alias('v','version')
.alias('i', 'in')
.describe('i', app.cli.args.in.description)
.default('i', app.cli.args.in.default)
.alias('o', 'out')
.describe('o', app.cli.args.out.description)
.default('o', app.cli.args.out.default)
.alias('w', 'watch')
.describe('w', app.cli.args.watch.description)
.default('w', app.cli.args.watch.default)
.boolean('w')
.alias('q', 'quiet')
.describe('q', app.cli.args.quiet.description)
.default('q', app.cli.args.quiet.default)
.boolean('q')
.argv
;
exports.version = package.version
exports.package = package
exports.app = app
/**
* Bootstrap the application with CLI flags
*/
app.version = package.version
_.extend(app, {
infile : argv.in,
outfile : argv.out,
watch : argv.watch,
quiet : argv.quiet
})
cli.config.appName("Rex-Template")
if(app.quiet || _.contains(argv._, 'quiet'))
cli.config.hideName()
if(argv.help || _.contains(argv._, 'help')) {
_.showAdvancedHelp(package)
cli.$.red("╭ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ╮")
cli.$.red("⌦ Hi, I'm Rex-Template. I'm here to make your life easier. ⌫ ")
cli.$.red("╰ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ╯\r\n")
cli.$.blue(" What is this? \r\n\t╰ " + cli.$$.y(package.description) )
cli.$.blue(" I'm stuck and need help. \r\n\t╰ " + cli.$$.y(package.author))
cli.$.blue(" GitHub repo? \r\n\t╰ " + cli.$$.y(package.homepage))
cli.$.blue(" Command what?")
cli.$.GREEN(" rex-template \r\n\t╰ " + cli.$$.y("Compiles ./public/js/templates into ./public/js/templates.js."))
cli.$.GREEN(" rex-template -w \r\n\t╰ " + cli.$$.y("Compiles the folder of templates and and watches the folder for changes."))
cli.$.GREEN(" rex-template \r\n\t╰ " + cli.$$.y("Compiles the folder of templates and reduces the text logged to the console."))
cli.$.GREEN(" rex-template -i './other/templates/folder/' -o './js/folder/templates.js' -w -q \r\n\t╰ " + cli.$$.y("Changes the input/output paths, reduces console logging, and watches for changes."))
process.exit(0)
}
if(argv.version || _.contains(argv._, 'version')) {
_.displayVersion(package, {
Handlebars : handlebars.VERSION
})
process.exit(0)
}
var preProcessCheck = function() {
var newTime = new Date().getTime()
if(parseInt( newTime - lastCompileTime) >= app.timeout ) {
startTime = new Date().getTime()
count = skipped = remaining = 0
lastCompileTime = newTime
run()
} else {
cli.error("Duplicate compile detected! Difference of " + parseFloat( ( newTime - lastCompileTime ) / 1000) + " seconds")
return false
}
}
var noRecentChanges = function(tpl, filesystemLastModified) {
if(_.has(compiled, tpl)) {
if(new Date(compiled[tpl].modified).getTime() == new Date(filesystemLastModified).getTime()) {
skipped++
return true
}
}
return false
}
var precompile = function(data) {
try {
data = data || ""
if(data == "")
return ""
else
return handlebars.precompile( data, {})
} catch(err) {
cli.error(err);
compileErrors++
return "";
}
}
var addWorkers = function addWorkers(num) {
remaining = remaining + num
}
var done = function workerDone() {
remaining--
if(remaining == -1)
Miyagi.emit('app:cleanup')
}
var compileTime = function compileTime(start, end) {
return ( end - start ) / 1000 + " seconds"
}
var clean = function cleanOutput(output) {
output = output
.replace(/<!--(.*?)-->/gm, "")
.replace(/[\t]/gm, "")
.replace(/(\r\n|\n|\r|\\n)/gm,"")
.replace(/\s{2,}/g, ' ')
return output
}
var prepareFile = function signFile(output) {
var now = new Date()
elapsedTime = compileTime(startTime, now.getTime() )
timeSaved += parseFloat( ( ( now.getTime() - startTime ) * skipped ) / 1000 )
var tplHeader = handlebars.compile( app.tplHeader.join(" \n") )({
date : now.toLocaleString(),
templateCount : _.size(compiled),
compileTime : elapsedTime,
version : app.version,
infile : app.infile,
outfile : app.outfile,
extensions : app.extensions.join(", "),
timeout : app.timeout,
iteration : iteration,
skipped : skipped,
timeSaved : timeSaved,
compileErrors : compileErrors
})
return tplHeader + output
}
exports.init = function appInit(infile, outfile) {
app.infile = infile || app.infile
app.outfile = outfile || app.outfile
if(!fs.existsSync( _.osPath( app.infile ) ) ) {
cli.error("Input folder '" + app.infile + "' does not exist!")
_.showAdvancedHelp(package)
process.exit(1)
}
if(app.watch)
Miyagi.emit('app:watch')
/**
* And away we go! :D
*/
run()
}
var run = function RexCompiler() {
count = remaining = skipped = compileErrors = 0
lastCompileTime = new Date().getTime()
startTime = new Date().getTime()
Miyagi.once('app:cleanup', appCleanup)
Miyagi.emit('read:folder', _.osPath(app.infile))
}
Miyagi.once('app:watch', function() {
watch(app.infile, function(file) {
cli("File change detected: " + file)
preProcessCheck()
})
})
/**
* Functional Event Listeners
*/
Miyagi.on('read:folder', function readFolder(folder, namespace) {
fs.readdir(folder, function fileList(err, files) {
addWorkers(files.length)
async.each(files, function(file, fileProcessed) {
var fp = _.osPath(folder + path.sep + file)
fs.stat(fp, function(err, stats) {
namespace = namespace || ""
if(stats && stats.isDirectory()) {
Miyagi.emit('read:folder', folder + path.sep + file, namespace + file + "/" )
} else if(stats && stats.isFile()) {
// Is our file a template we care about?
if(_.contains(app.extensions, path.extname(file))) {
Miyagi.emit('read:file', file, namespace, stats)
} else {
done()
}
} else {
done()
}
fileProcessed()
})
}, function(err, results) {
done()
})
})
})
Miyagi.on('read:file', function readFile(file, namespace, stats) {
var ext = path.extname(file)
var name = namespace + file
var tpl = name.replace(ext, "")
// If this template has already been seen before, avoid compiling it if we can
if(noRecentChanges(tpl, stats.mtime)) {
done()
return false
} else {
fs.readFile(_.osPath(app.infile + path.sep + name), 'utf8', function readInFile(err, fileContents) {
if(err)
throw err
compiled[tpl] = {
name : tpl,
ext : ext,
template : clean( precompile(fileContents) ),
modified : stats.mtime
}
done()
})
}
})
var appCleanup = function appCleanup() {
var output = app.hb.startString
_.each(compiled, function(tpl) {
output += 'templates[\'' + tpl.name + '\'] = template(' + tpl.template + '); \n'
})
output += app.hb.endString
fs.writeFile( _.osPath(app.outfile), prepareFile(output), function(err) {
if(err) throw err
var msg = "[#" + iteration + ", " + new Date().toLocaleTimeString() + "] "
if(compileErrors)
msg += cli.$$.R("[" + compileErrors + ((compileErrors > 1) ? " ERRORS" : " ERROR") + "!] ")
msg += "Compiled " + _.size(compiled) + " templates to " + app.outfile + " in " + elapsedTime
if(skipped > 0)
msg += ", skipping " + skipped + " that didn't change."
if(compileErrors)
cli.error(msg)
else
cli.success(msg)
// Handle all our variables and whatnot
iteration++
})
}