Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit 53670b7124adedb6dfe5ef543a93ad8c041c21b2 0 parents
@evocateur evocateur authored
2  .gitignore
@@ -0,0 +1,2 @@
+node_modules
+test/output
67 lib/argv.js
@@ -0,0 +1,67 @@
+
+module.exports = {
+ options: {
+ 'v': {
+ type: 'boolean',
+ description: 'Verbose logging',
+ alias: 'verbose'
+ },
+ 'd': {
+ type: 'string',
+ description: 'Output base directory',
+ alias: 'outputDir'
+ },
+ 'f': {
+ type: 'string',
+ description: 'Output File',
+ alias: 'outputFile'
+ },
+ 'p': {
+ type: 'string',
+ description: 'Module prefix',
+ alias: 'modulePrefix',
+ default: 'z-template'
+ },
+ 'k': {
+ type: 'string',
+ description: 'Known helpers',
+ alias: 'known'
+ },
+ 'o': {
+ type: 'boolean',
+ description: 'Known helpers only',
+ alias: 'knownOnly'
+ },
+ 'b': {
+ type: 'boolean',
+ description: 'Beautify output',
+ alias: 'beautify'
+ },
+ 'm': {
+ type: 'boolean',
+ description: 'Minimize output',
+ alias: 'min'
+ },
+ 's': {
+ type: 'boolean',
+ description: 'Output template function only',
+ alias: 'simple'
+ },
+ 'r': {
+ type: 'string',
+ description: 'Template root, stripped from template names.',
+ alias: 'root'
+ }
+ },
+ defaults: {
+ verbose: false,
+ outputDir: '',
+ outputFile: '',
+ known: '',
+ knownOnly: false,
+ beautify: false,
+ min: false,
+ simple: false,
+ root: ''
+ }
+};
32 lib/cli.js
@@ -0,0 +1,32 @@
+#!/usr/bin/env node
+
+var fs = require('fs'),
+ optimist = require('optimist')
+ .usage('Precompile handlebars templates.\nUsage: $0 template...')
+ .options(require('./argv').options)
+ .check(function (argv) {
+ if (!argv._.length) {
+ throw 'Must define at least one template or directory.';
+ }
+
+ argv._.forEach(function (template) {
+ try {
+ fs.statSync(template);
+ } catch (err) {
+ throw 'Unable to open template file "' + template + '"';
+ }
+ });
+ })
+ .check(function (argv) {
+ if (argv.beautify && argv.min) {
+ throw 'Unable to beautify minified output';
+ }
+ if (argv.simple && argv.min) {
+ throw 'Unable to minimize simple output';
+ }
+ if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
+ throw 'Unable to output multiple templates in simple mode';
+ }
+ });
+
+new (require('./mustachio'))(optimist.argv);
211 lib/mustachio.js
@@ -0,0 +1,211 @@
+/**
+ * Mustachio - Not just for Salvador, anymore!
+ */
+
+var fs = require('fs'),
+ path = require('path'),
+ mkdirp = require('mkdirp'),
+ uglify = require('uglify-js'),
+
+ Handlebars = require('yui/handlebars').Handlebars,
+
+ defaults = require('./argv').defaults;
+
+function Mustachio(argv) {
+ this.argv = merge(defaults, argv);
+
+ this.known = {};
+ this.output = [];
+ this.prefix = (this.argv.modulePrefix + '-').replace('--', '-');
+ this.pregex = new RegExp('"(' + this.prefix + '[\\w\\-]+)"');
+
+ if (this.argv.verbose) {
+ this.log = function () {
+ console.log.apply(null, arguments);
+ };
+ }
+
+ if (this.argv.min) {
+ this.render = function (ary) {
+ return uglify(ary.join('')) + ';\n';
+ };
+ }
+ else if (this.argv.beautify) {
+ this.render = function (ary) {
+ var code = ary.join(''),
+ ast = uglify.parser.parse(code);
+ // ast = uglify.uglify.ast_mangle(ast);
+ // ast = uglify.uglify.ast_squeeze(ast);
+ return uglify.uglify.gen_code(ast, {
+ beautify: true
+ }) + '\n';
+ };
+ }
+
+ // Convert the known list into a hash
+ if (this.argv.known && !Array.isArray(this.argv.known)) {
+ this.argv.known = [this.argv.known];
+ }
+ if (this.argv.known) {
+ var i = 0, len = this.argv.known.length;
+ for (; i < len; i++) {
+ this.known[this.argv.known[i]] = true;
+ }
+ }
+
+ if (argv._ && argv._.length) {
+ this.invoke(argv._);
+ }
+}
+
+Mustachio.prototype = {
+
+ log: function () {},
+
+ render: function (ary) {
+ return ary.join('');
+ },
+
+ invoke: function (templates) {
+ this.log("\nProcessing templates: " + templates + "\n#####################\n");
+
+ var self = this,
+ root = this.argv.root;
+
+ templates.forEach(function (template) {
+ self.processTemplate(template, root);
+ });
+
+ this.renderOutput();
+ },
+
+ processTemplate: function (template, root) {
+ var precompiled,
+ self = this,
+ extn = /\.(handlebars|mustache)$/,
+ tmpl = template,
+ stat = fs.statSync(tmpl);
+
+ if (stat.isDirectory()) {
+ fs.readdirSync(template).forEach(function (file) {
+ var path = template + '/' + file;
+
+ if (extn.test(path) || fs.statSync(path).isDirectory()) {
+ self.processTemplate(path, root || template);
+ }
+ });
+ }
+ else {
+ precompiled = Handlebars.precompile(fs.readFileSync(tmpl, 'utf8'), {
+ knownHelpers: this.known,
+ knownHelpersOnly: this.argv.o
+ });
+
+ if (this.argv.simple) {
+ this.output.push(precompiled + '\n');
+ }
+ else {
+ // Clean the template name
+ if (!root) {
+ template = path.basename(template);
+ } else if (template.indexOf(root) === 0) {
+ template = template.substring(root.length + 1);
+ }
+ template = template.replace(extn, '');
+ this.log(' raw template name: "' + template + '"');
+
+ this.output.push(this.wrapModule(template, precompiled));
+ }
+ }
+ },
+
+ wrapModule: function (templateName, precompiledFn) {
+ // replace slashes in templateName with dashes
+ templateName = templateName.replace(/\//g, '-');
+ this.log('module template name: "' + templateName + '"\n');
+
+ return [
+ 'YUI.add("' + this.prefix + templateName + '", function (Y) {\n',
+ ' Y.namespace("Z.Template")["' + templateName + '"] = Y.Handlebars.template(' + precompiledFn + ');\n',
+ '}, "@VERSION@", { "requires": ["handlebars-base"] });\n'
+ ];
+ },
+
+ renderOutput: function () {
+ var self = this,
+ output = self.output,
+ pregex = self.pregex,
+ render = self.render;
+
+ if (output.length) {
+ if (self.argv.outputDir) {
+ self.log("\nWriting module files: " + self.argv.outputDir + "\n#####################\n");
+
+ output.forEach(function (moduleData) {
+ // read out the generated module name from the module data
+ var moduleName = moduleData[0].match(pregex)[1],
+ modulePath = path.resolve(self.argv.outputDir + '/' + moduleName),
+ moduleFile = modulePath + '/' + moduleName + '.js';
+
+ self.log(' "' + moduleName + '"');
+ self.log(moduleData[0]);
+
+ mkdirp(modulePath, function (dirErr, made) {
+ if (dirErr) {
+ console.error(dirErr);
+ throw dirErr;
+ }
+
+ fs.writeFile(moduleFile, render(moduleData), 'utf8', function (fileErr) {
+ if (fileErr) {
+ console.error(fileErr);
+ throw fileErr;
+ }
+
+ console.log('Wrote ' + moduleFile);
+ });
+ });
+ });
+ } else if (self.argv.outputFile) {
+ fs.writeFileSync(self.argv.outputFile, render(output), 'utf8');
+ } else {
+ console.log(render(output));
+ }
+ }
+ }
+
+};
+
+module.exports = Mustachio;
+
+// mix & merge lifted from yui-base, simplified
+function mix(receiver, supplier, overwrite) {
+ var key;
+
+ if (!receiver || !supplier) {
+ return receiver || {};
+ }
+
+ for (key in supplier) {
+ if (supplier.hasOwnProperty(key)) {
+ if (overwrite || !receiver.hasOwnProperty(key)) {
+ receiver[key] = supplier[key];
+ }
+ }
+ }
+
+ return receiver;
+}
+
+function merge() {
+ var args = arguments,
+ i = 0,
+ len = args.length,
+ result = {};
+
+ for (; i < len; i += 1) {
+ mix(result, args[i], true);
+ }
+
+ return result;
+}
149 lib/utils.js
@@ -0,0 +1,149 @@
+/**
+ * Random utility functions
+ */
+
+/**
+Mixes _supplier_'s properties into _receiver_.
+
+Properties on _receiver_ or _receiver_'s prototype will not be overwritten or
+shadowed unless the _overwrite_ parameter is `true`, and will not be merged
+unless the _merge_ parameter is `true`.
+
+In the default mode (0), only properties the supplier owns are copied (prototype
+properties are not copied). The following copying modes are available:
+
+ * `0`: _Default_. Object to object.
+ * `1`: Prototype to prototype.
+ * `2`: Prototype to prototype and object to object.
+ * `3`: Prototype to object.
+ * `4`: Object to prototype.
+
+@method mix
+@param {Function|Object} receiver The object or function to receive the mixed
+ properties.
+@param {Function|Object} supplier The object or function supplying the
+ properties to be mixed.
+@param {Boolean} [overwrite=false] If `true`, properties that already exist
+ on the receiver will be overwritten with properties from the supplier.
+@param {String[]} [whitelist] An array of property names to copy. If
+ specified, only the whitelisted properties will be copied, and all others
+ will be ignored.
+@param {Number} [mode=0] Mix mode to use. See above for available modes.
+@param {Boolean} [merge=false] If `true`, objects and arrays that already
+ exist on the receiver will have the corresponding object/array from the
+ supplier merged into them, rather than being skipped or overwritten. When
+ both _overwrite_ and _merge_ are `true`, _merge_ takes precedence.
+@return {Function|Object|YUI} The receiver, or the YUI instance if the
+ specified receiver is falsy.
+**/
+Y.mix = function(receiver, supplier, overwrite, whitelist, mode, merge) {
+ var alwaysOverwrite, exists, from, i, key, len, to;
+
+ // If no supplier is given, we return the receiver. If no receiver is given,
+ // we return Y. Returning Y doesn't make much sense to me, but it's
+ // grandfathered in for backcompat reasons.
+ if (!receiver || !supplier) {
+ return receiver || Y;
+ }
+
+ if (mode) {
+ // In mode 2 (prototype to prototype and object to object), we recurse
+ // once to do the proto to proto mix. The object to object mix will be
+ // handled later on.
+ if (mode === 2) {
+ Y.mix(receiver.prototype, supplier.prototype, overwrite,
+ whitelist, 0, merge);
+ }
+
+ // Depending on which mode is specified, we may be copying from or to
+ // the prototypes of the supplier and receiver.
+ from = mode === 1 || mode === 3 ? supplier.prototype : supplier;
+ to = mode === 1 || mode === 4 ? receiver.prototype : receiver;
+
+ // If either the supplier or receiver doesn't actually have a
+ // prototype property, then we could end up with an undefined `from`
+ // or `to`. If that happens, we abort and return the receiver.
+ if (!from || !to) {
+ return receiver;
+ }
+ } else {
+ from = supplier;
+ to = receiver;
+ }
+
+ // If `overwrite` is truthy and `merge` is falsy, then we can skip a
+ // property existence check on each iteration and save some time.
+ alwaysOverwrite = overwrite && !merge;
+
+ if (whitelist) {
+ for (i = 0, len = whitelist.length; i < len; ++i) {
+ key = whitelist[i];
+
+ // We call `Object.prototype.hasOwnProperty` instead of calling
+ // `hasOwnProperty` on the object itself, since the object's
+ // `hasOwnProperty` method may have been overridden or removed.
+ // Also, some native objects don't implement a `hasOwnProperty`
+ // method.
+ if (!hasOwn.call(from, key)) {
+ continue;
+ }
+
+ // The `key in to` check here is (sadly) intentional for backwards
+ // compatibility reasons. It prevents undesired shadowing of
+ // prototype members on `to`.
+ exists = alwaysOverwrite ? false : key in to;
+
+ if (merge && exists && isObject(to[key], true)
+ && isObject(from[key], true)) {
+ // If we're in merge mode, and the key is present on both
+ // objects, and the value on both objects is either an object or
+ // an array (but not a function), then we recurse to merge the
+ // `from` value into the `to` value instead of overwriting it.
+ //
+ // Note: It's intentional that the whitelist isn't passed to the
+ // recursive call here. This is legacy behavior that lots of
+ // code still depends on.
+ Y.mix(to[key], from[key], overwrite, null, 0, merge);
+ } else if (overwrite || !exists) {
+ // We're not in merge mode, so we'll only copy the `from` value
+ // to the `to` value if we're in overwrite mode or if the
+ // current key doesn't exist on the `to` object.
+ to[key] = from[key];
+ }
+ }
+ } else {
+ for (key in from) {
+ // The code duplication here is for runtime performance reasons.
+ // Combining whitelist and non-whitelist operations into a single
+ // loop or breaking the shared logic out into a function both result
+ // in worse performance, and Y.mix is critical enough that the byte
+ // tradeoff is worth it.
+ if (!hasOwn.call(from, key)) {
+ continue;
+ }
+
+ // The `key in to` check here is (sadly) intentional for backwards
+ // compatibility reasons. It prevents undesired shadowing of
+ // prototype members on `to`.
+ exists = alwaysOverwrite ? false : key in to;
+
+ if (merge && exists && isObject(to[key], true)
+ && isObject(from[key], true)) {
+ Y.mix(to[key], from[key], overwrite, null, 0, merge);
+ } else if (overwrite || !exists) {
+ to[key] = from[key];
+ }
+ }
+
+ // If this is an IE browser with the JScript enumeration bug, force
+ // enumeration of the buggy properties by making a recursive call with
+ // the buggy properties as the whitelist.
+ if (Y.Object._hasEnumBug) {
+ Y.mix(to, from, overwrite, Y.Object._forceEnum, mode, merge);
+ }
+ }
+
+ return receiver;
+};
+
+exports.mix = mix;
27 package.json
@@ -0,0 +1,27 @@
+{
+ "name": "mustachio",
+ "version": "0.1.0",
+ "description": "Wrap precompiled Mustache templates (using Handlebars.js) in YUI modules.",
+ "main": "./lib/mustachio.js",
+ "bin": "./lib/cli.js",
+ "scripts": {
+ "test": "nodeunit test"
+ },
+ "keywords": [
+ "yui",
+ "mustache",
+ "handlebars",
+ "template"
+ ],
+ "author": "Daniel Stockman",
+ "license": "BSD",
+ "dependencies": {
+ "yui": "~3.5.1",
+ "mkdirp": "~0.3.3",
+ "uglify-js": "~1.3.2",
+ "optimist": "~0.3.4"
+ },
+ "devDependencies": {
+ "nodeunit": "~0.7.4"
+ }
+}
0  test/mustachio-test.js
No changes.
0  test/templates/alpha.mustache
No changes.
0  test/templates/beta.mustache
No changes.
0  test/templates/gamma.handlebars
No changes.
Please sign in to comment.
Something went wrong with that request. Please try again.