#! /usr/bin/env node /* * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey * http://lehni.org/ & http://jonathanpuckey.com/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. */ /** * Prepro.js - A simple preprocesssor for JavaScript that speaks JavaScript, * written in JavaScript, allowing preprocessing to either happen at build time * or compile time. Very useful for libraries that are built for distribution, * but can be also compiled from seperate sources directly for development, * supporting build time switches. * * Arguments: * -d DEFINE_JSON -- define a json containing defintions availabe to prepro * -i INCLUDE_JS -- include a JS file containing definitinos availabe to prepro * -c -- strip comments */ // Required libs var fs = require('fs'), path = require('path'), vm = require('vm'); // Preprocessing var code = []; function include(base, file) { // Compose a pathname from base and file, which is specified relatively, // and normalize the new path, to get rid of .. file = path.normalize(path.join(base, file)); var content = fs.readFileSync(file).toString(); content.split(/\r\n|\n|\r/mg).forEach(function(line) { // See if our line starts with the preprocess prefix. var match = line.match(/^\s*\/\*#\*\/\s*(.*)$/); if (match) { // Check if the preprocessing line is an include statement, and if // so, handle it straight away line = match[1]; if (match = line.match(/^include\(['"]([^;]*)['"]\);?$/)) { // Pass on the dirname of the current file as the new base include(path.dirname(file), match[1]); } else { // Any other preprocessing code is simply added, for later // evaluation. code.push(line); } } else { // Perhaps we need to replace some values? Supported formats are: // /*#=*/ eval (outside comments) // *#=* eval (inside comments) line = line.replace(/\/?\*#=\*\/?\s*([\w.]*)/g, function(all, val) { return eval(val); } ); // Now add a statement that when evaluated writes out this code line code.push('out.push(' + JSON.stringify(line) + ');'); } }); } function parse() { var out = []; // Evaluate the collected code: Collects result in out, through out.push() eval(code.join('\n')); // Start again with a new code buffer. code = []; // Return the resulting lines as one string. return out.join('\n'); } // Parse arguments var args = process.argv.slice(2), options = {}, files = [], strip = false; while (args.length > 0) { var arg = args.shift(); switch (arg) { case '-d': // Definitions are provided as JSON and supposed to be object literals var def = JSON.parse(args.shift()); // Merge new definitions into options object. for (var key in def) options[key] = def[key]; break; case '-i': // Include code to be present at prepro time, e.g. for on-the-fly // replacement of constants, using /*#=*/ statements. // Luckily we can reuse the include() / parse() functionality to do so: var file = args.shift(); if (file) { include(path.resolve(), path.normalize(file)); eval(parse()); } break; case '-c': strip = true; break; default: files.push(arg); } } // Include all files. Everything else happens from there, through include() files.forEach(function(file) { include(path.resolve(), file); }); var out = parse(); if (strip) { out = stripComments(out); // Strip empty lines that contain only white space and line breaks, as they // are left-overs from comment removal. out = out.replace(/^[ \t]+(\r\n|\n|\r)/gm, function(all) { return ''; }); // Replace a sequence of more than two line breaks with only two. out = out.replace(/(\r\n|\n|\r)(\r\n|\n|\r)+/g, function(all, lineBreak) { return lineBreak + lineBreak; }); } // Write the result out process.stdout.write(out); /** * Strips comments out of JavaScript code, based on: * http://james.padolsey.com/javascript/removing-comments-in-javascript/ */ function stripComments(str) { // Add some padding so we can always look ahead and behind by two chars str = ('__' + str + '__').split(''); var quote = false, quoteSign, blockComment = false, lineComment = false, preserveComment = false; for (var i = 0, l = str.length; i < l; i++) { if (quote) { // When checking for quote escaping, we also need to check that the // escape sign itself is not escaped, as otherwise '\\' would cause // the wrong impression of an unclosed string: if (str[i] === quoteSign && (str[i - 1] !== '\\' || str[i - 2] === '\\')) quote = false; } else if (blockComment) { // Is the block comment closing? if (str[i] === '*' && str[i + 1] === '/') { if (!preserveComment) str[i] = str[i + 1] = ''; blockComment = preserveComment = false; } else if (!preserveComment) { str[i] = ''; } } else if (lineComment) { // One-line comments end with the line-break if (/[\n\r]/.test(str[i + 1])) lineComment = false; str[i] = ''; } else { quote = /['"]/.test(str[i]); if (quote) quoteSign = str[i]; if (!blockComment && str[i] === '/') { if (str[i + 1] === '*') { // Do not filter out conditional comments /*@ ... */ // and comments marked as protected /*! ... */ preserveComment = /[@!]/.test(str[i + 2]); if (!preserveComment) str[i] = ''; blockComment = true; } else if (str[i + 1] === '/') { str[i] = ''; lineComment = true; } } } } // Remove padding again. return str.join('').slice(2, -2); }