diff --git a/lib/command.js b/lib/command.js index bdbdb58..a32f430 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,22 +1,35 @@ (function() { var compile, compileToFile, each, eachFile, eco, exec, fs, indent, mkdir, parseOptions, path, preprocessArgs, printUsage, printVersion, read, stripExtension, sys; var __slice = Array.prototype.slice; + fs = require("fs"); + path = require("path"); - sys = require("sys"); + + try { + sys = require("util"); + } catch (e) { + sys = require("sys"); + } + eco = require(".."); + exec = require("child_process").exec; + indent = require("./util").indent; + printUsage = function() { console.error("\nUsage: eco [options] path/to/template.eco\n\n -i, --identifier [NAME] set the name of the global template object\n -o, --output [DIR] set the directory for compiled JavaScript\n -p, --print print the compiled JavaScript to stdout\n -s, --stdio listen for and compile templates over stdio\n -v, --version display Eco version\n -h, --help display this help message\n"); return process.exit(1); }; + printVersion = function() { var package; package = JSON.parse(fs.readFileSync(__dirname + "/../package.json", "utf8")); console.error("Eco version " + package.version); return process.exit(0); }; + preprocessArgs = function(args) { var arg, char, match, result, _i, _j, _len, _len2, _ref; result = []; @@ -34,6 +47,7 @@ } return result; }; + parseOptions = function(args) { var arg, option, options, _i, _len, _ref; options = { @@ -78,11 +92,10 @@ } } } - if (option) { - printUsage(); - } + if (option) printUsage(); return options; }; + read = function(stream, callback) { var buffer; buffer = ""; @@ -94,6 +107,7 @@ return typeof callback === "function" ? callback(buffer) : void 0; }); }; + each = function(_arg, callback) { var proceed, values; values = 1 <= _arg.length ? __slice.call(_arg, 0) : []; @@ -101,23 +115,18 @@ return callback(values.shift(), proceed); })(); }; + eachFile = function(files, callback) { var traverse; traverse = function(root, dir, done) { return fs.readdir(dir, function(err, entries) { - if (err) { - return callback(err); - } + if (err) return callback(err); return each(entries, function(entry, proceed) { var file; - if (entry == null) { - return done(); - } + if (entry == null) return done(); file = path.join(dir, entry); return fs.stat(file, function(err, stat) { - if (err) { - return callback(err); - } + if (err) return callback(err); if (stat.isFile() && /\.eco$/.test(file)) { return callback(null, file, root, proceed); } else if (stat.isDirectory()) { @@ -130,14 +139,10 @@ }); }; return each(files, function(file, proceed) { - if (file == null) { - return; - } + if (file == null) return; return fs.stat(file, function(err, stat) { var root; - if (err) { - return callback(err); - } + if (err) return callback(err); if (stat.isDirectory()) { return traverse(file, file, proceed); } else { @@ -147,24 +152,26 @@ }); }); }; + stripExtension = function(name) { return name.replace(/(\.eco)?$/, ""); }; + compile = function(infile, identifier, name, callback) { return fs.readFile(infile, "utf8", function(err, source) { var template; - if (err) { - return callback(err); - } + if (err) return callback(err); template = indent(eco.precompile(source), 2); return callback(null, "(function() {\n this." + identifier + " || (this." + identifier + " = {});\n this." + identifier + "[" + (JSON.stringify(name)) + "] = " + (template.slice(2)) + ";\n}).call(this);"); }); }; + mkdir = function(dir, callback) { return exec("mkdir -p " + (JSON.stringify(dir)), function(err, stdout, stderr) { return callback(err); }); }; + compileToFile = function(infile, identifier, root, outdir, callback) { var name, outfile; root = path.join(root, "/"); @@ -175,64 +182,46 @@ } outfile = path.join(outdir != null ? outdir : root, name + ".js"); return mkdir(path.dirname(outfile), function(err) { - if (err) { - return callback(err); - } + if (err) return callback(err); return compile(infile, identifier, name, function(err, result) { return fs.writeFile(outfile, result + "\n", "utf8", function(err) { - if (err) { - return callback(err); - } + if (err) return callback(err); return callback(null, outfile, name); }); }); }); }; + exports.run = function(args) { var infile, name, options; - if (args == null) { - args = process.argv.slice(2); - } + if (args == null) args = process.argv.slice(2); options = parseOptions(args); if (options.stdio) { - if (options.files.length || options.output) { - printUsage(); - } + if (options.files.length || options.output) printUsage(); process.openStdin(); return read(process.stdin, function(source) { return sys.puts(eco.precompile(source)); }); } else if (options.print) { - if (options.files.length !== 1 || options.output) { - printUsage(); - } + if (options.files.length !== 1 || options.output) printUsage(); infile = options.files[0]; name = stripExtension(path.basename(infile)); return compile(infile, options.identifier, name, function(err, result) { - if (err) { - throw err; - } + if (err) throw err; return sys.puts(result); }); } else { - if (!options.files.length) { - printUsage(); - } + if (!options.files.length) printUsage(); return eachFile(options.files, function(err, infile, root, proceed) { - if (err) { - throw err; - } - if (infile == null) { - return; - } + if (err) throw err; + if (infile == null) return; return compileToFile(infile, options.identifier, root, options.output, function(err, outfile, name) { - if (err) { - throw err; - } + if (err) throw err; console.error("" + (JSON.stringify(name)) + ": " + infile + " -> " + outfile); return proceed(); }); }); } }; + }).call(this); diff --git a/lib/compiler.js b/lib/compiler.js index dd5259e..0eff0e5 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -1,8 +1,12 @@ (function() { var CoffeeScript, indent, precompile, preprocess; + CoffeeScript = require("coffee-script"); + preprocess = require("./preprocessor").preprocess; + indent = require("./util").indent; + exports.precompile = precompile = function(source) { var script; script = CoffeeScript.compile(preprocess(source), { @@ -10,7 +14,9 @@ }); return "function(__obj) {\n if (!__obj) __obj = {};\n var __out = [], __capture = function(callback) {\n var out = __out, result;\n __out = [];\n callback.call(this);\n result = __out.join('');\n __out = out;\n return __safe(result);\n }, __sanitize = function(value) {\n if (value && value.ecoSafe) {\n return value;\n } else if (typeof value !== 'undefined' && value != null) {\n return __escape(value);\n } else {\n return '';\n }\n }, __safe, __objSafe = __obj.safe, __escape = __obj.escape;\n __safe = __obj.safe = function(value) {\n if (value && value.ecoSafe) {\n return value;\n } else {\n if (!(typeof value !== 'undefined' && value != null)) value = '';\n var result = new String(value);\n result.ecoSafe = true;\n return result;\n }\n };\n if (!__escape) {\n __escape = __obj.escape = function(value) {\n return ('' + value)\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\x22/g, '"');\n };\n }\n (function() {\n" + (indent(script, 4)) + "\n }).call(__obj);\n __obj.safe = __objSafe, __obj.escape = __escape;\n return __out.join('');\n}"; }; + exports.compile = function(source) { return new Function("return " + (precompile(source)))(); }; + }).call(this); diff --git a/lib/index.js b/lib/index.js index edc345c..bca5ac8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,10 @@ (function() { var compile, eco, precompile, preprocess, _ref; + _ref = require("./compiler"), compile = _ref.compile, precompile = _ref.precompile; + preprocess = require("./preprocessor").preprocess; + module.exports = eco = function(source) { var _base, _ref2; if (eco.cache) { @@ -10,13 +13,19 @@ return compile(source); } }; + eco.cache = {}; + eco.preprocess = preprocess; + eco.precompile = precompile; + eco.compile = compile; + eco.render = function(source, data) { return (eco(source))(data); }; + if (require.extensions) { require.extensions[".eco"] = function(module, filename) { var source; @@ -24,4 +33,5 @@ return module._compile("module.exports = " + (precompile(source)), filename); }; } + }).call(this); diff --git a/lib/preprocessor.js b/lib/preprocessor.js index c500e82..5a33c64 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -1,14 +1,18 @@ (function() { var Preprocessor, Scanner, util; - var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + Scanner = require("./scanner"); + util = require("./util"); + module.exports = Preprocessor = (function() { + Preprocessor.preprocess = function(source) { var preprocessor; preprocessor = new Preprocessor(source); return preprocessor.preprocess(); }; + function Preprocessor(source) { this.scanner = new Scanner(source); this.output = ""; @@ -16,26 +20,32 @@ this.options = {}; this.captures = []; } + Preprocessor.prototype.preprocess = function() { + var _this = this; while (!this.scanner.done) { - this.scanner.scan(__bind(function(token) { - return this[token[0]].apply(this, token.slice(1)); - }, this)); + this.scanner.scan(function(token) { + return _this[token[0]].apply(_this, token.slice(1)); + }); } return this.output; }; + Preprocessor.prototype.record = function(line) { this.output += util.repeat(" ", this.level); return this.output += line + "\n"; }; + Preprocessor.prototype.printString = function(string) { if (string.length) { return this.record("__out.push " + (util.inspectString(string))); } }; + Preprocessor.prototype.beginCode = function(options) { return this.options = options; }; + Preprocessor.prototype.recordCode = function(code) { if (code !== "end") { if (this.options.print) { @@ -49,6 +59,7 @@ } } }; + Preprocessor.prototype.indent = function(capture) { this.level++; if (capture) { @@ -57,19 +68,22 @@ return this.indent(); } }; + Preprocessor.prototype.dedent = function() { this.level--; - if (this.level < 0) { - this.fail("unexpected dedent"); - } + if (this.level < 0) this.fail("unexpected dedent"); if (this.captures[0] === this.level) { this.captures.shift(); return this.dedent(); } }; + Preprocessor.prototype.fail = function(message) { throw "Parse error on line " + this.scanner.lineNo + ": " + message; }; + return Preprocessor; + })(); + }).call(this); diff --git a/lib/scanner.js b/lib/scanner.js index dd9736e..cd7258d 100644 --- a/lib/scanner.js +++ b/lib/scanner.js @@ -1,14 +1,20 @@ (function() { var Scanner, StringScanner, trim; + StringScanner = require("strscan").StringScanner; + trim = require("./util").trim; + module.exports = Scanner = (function() { + Scanner.modePatterns = { data: /(.*?)(<%%|<%\s*(\#)|<%(([=-])?)|\n|$)/, code: /(.*?)((((:|(->|=>))\s*))?%>|\n|$)/, comment: /(.*?)(%>|\n|$)/ }; + Scanner.dedentablePattern = /^(end|when|else|catch|finally)(?:\W|$)/; + Scanner.scan = function(source) { var scanner, tokens; tokens = []; @@ -20,6 +26,7 @@ } return tokens; }; + function Scanner(source) { this.source = source.replace(/\r\n?/g, "\n"); this.scanner = new StringScanner(this.source); @@ -28,6 +35,7 @@ this.lineNo = 1; this.done = false; } + Scanner.prototype.scan = function(callback) { if (this.done) { return callback(); @@ -51,6 +59,7 @@ } } }; + Scanner.prototype.advance = function() { this.scanner.scanUntil(Scanner.modePatterns[this.mode]); this.buffer += this.scanner.getCapture(0); @@ -59,6 +68,7 @@ this.directive = this.scanner.getCapture(4); return this.arrow = this.scanner.getCapture(5); }; + Scanner.prototype.scanData = function(callback) { if (this.tail === "<%%") { this.buffer += "<%"; @@ -82,6 +92,7 @@ } } }; + Scanner.prototype.scanCode = function(callback) { var code; if (this.tail === "\n") { @@ -89,18 +100,13 @@ } else if (this.tail) { this.mode = "data"; code = trim(this.flush()); - if (this.arrow) { - code += " " + this.arrow; - } - if (this.isDedentable(code)) { - callback(["dedent"]); - } + if (this.arrow) code += " " + this.arrow; + if (this.isDedentable(code)) callback(["dedent"]); callback(["recordCode", code]); - if (this.directive) { - return callback(["indent", this.arrow]); - } + if (this.directive) return callback(["indent", this.arrow]); } }; + Scanner.prototype.scanComment = function(callback) { if (this.tail === "\n") { return callback(["fail", "unexpected newline in code block"]); @@ -109,15 +115,20 @@ return this.buffer = ""; } }; + Scanner.prototype.flush = function() { var buffer; buffer = this.buffer; this.buffer = ""; return buffer; }; + Scanner.prototype.isDedentable = function(code) { return code.match(Scanner.dedentablePattern); }; + return Scanner; + })(); + }).call(this); diff --git a/lib/util.js b/lib/util.js index 423a9a6..80fed3e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,8 +1,10 @@ (function() { var repeat, specialCharacters; + exports.repeat = repeat = function(string, count) { return Array(count + 1).join(string); }; + exports.indent = function(string, width) { var line, lines, space; space = repeat(" ", width); @@ -18,9 +20,11 @@ })(); return lines.join("\n"); }; + exports.trim = function(string) { return string.replace(/^\s+/, "").replace(/\s+$/, ""); }; + specialCharacters = { '\\': '\\\\', '\b': '\\b', @@ -29,6 +33,7 @@ '\r': '\\r', '\t': '\\t' }; + exports.inspectString = function(string) { var contents; contents = string.replace(/[\x00-\x1f\\]/g, function(character) { @@ -37,12 +42,11 @@ return specialCharacters[character]; } else { code = character.charCodeAt(0).toString(16); - if (code.length === 1) { - code = "0" + code; - } + if (code.length === 1) code = "0" + code; return "\\u00" + code; } }); return "'" + contents.replace(/'/g, '\\\'') + "'"; }; + }).call(this); diff --git a/src/command.coffee b/src/command.coffee index f3facea..27a7c21 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -1,6 +1,9 @@ fs = require "fs" path = require "path" -sys = require "sys" +try + sys = require "util" +catch e + sys = require "sys" eco = require ".." {exec} = require "child_process"