├── .gitignore ├── README.md ├── index.js ├── package.json └── test ├── sample.ds └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macros 2 | 3 | `macros` is an easy way to define a pre-processor chain for your source. 4 | 5 | The macro processor is combined of steps: 6 | 7 | * Regex (string to string) 8 | * Token Transformations (token to token) 9 | * AST Transformations (valid JS AST to valid JS AST) 10 | * Prepend (valid JS str to valid JS str) 11 | * Append (valid JS str to valid JS str) 12 | * require (valid JS str to module.exports object) 13 | 14 | 15 | You define a file extension and a suite of filters that accepts source and returns transformed source. 16 | 17 | ## Examples: 18 | 19 | All of the steps are demonstrated in [the test file](https://github.com/aaronblohowiak/macros/blob/master/test/test.js) 20 | 21 | ### Regex: 22 | 23 | Don't you wish JavaScript had easy parse-time string substitution? ;) Caution: as this is just a straight string substitution, you will probably blow up your code. 24 | 25 | macros.register(".cjs", { 26 | regex: [ 27 | [/development.local/, "example.com"] 28 | ] 29 | }); 30 | 31 | ### Token: 32 | 33 | If you want to twiddle your tokens, use this handy token-substitution device. 34 | 35 | Example: decafscript is javascript where "f" is the same as "function". You can do it with a simple token transformation. all files with the ".ds" extension will treat "f" as the "function" keyword using this macro: 36 | 37 | macros.register(".ds",{ 38 | token: function(tok){ 39 | if(tok.type == "name" && tok.value == "f"){ 40 | tok.type = "keyword"; 41 | tok.value = "function"; 42 | } 43 | return tok; 44 | } 45 | }); 46 | 47 | ### AST 48 | 49 | If you want to do crazy transformations on your source tree, then you can modify the syntax tree directly. `macros` uses a fork of Uglify-JS, but the AST is the same. 50 | 51 | Here's an example with substack's handy `burrito`! It will wrap all function calls with another function call :D 52 | 53 | burrito = require("burrito"); 54 | 55 | macros.register(".trace", { 56 | ast: function(ast){ 57 | var str = burrito(ast, function(node){ 58 | if(node.name === 'call') node.wrap('wrapper(%s)'); 59 | }); 60 | return burrito.parse(str); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var ugly = require("aaronblohowiak-plugify-js"), 2 | fs = require("fs"); 3 | 4 | macros = {}; 5 | module.exports = macros; 6 | 7 | var passthrough = function(item){ 8 | return item; 9 | }; 10 | 11 | convert = function (code, options){ 12 | options || (options = {}); 13 | 14 | options.gen_options || (options.gen_options = {}) 15 | if(!options.gen_options.hasOwnProperty("beautify")){ 16 | options.gen_options.beautify = true; 17 | } 18 | 19 | options.regex || (options.regex = []); 20 | options.token || (options.token = passthrough); 21 | options.ast || (options.ast = passthrough); 22 | 23 | var re, sub; 24 | for (var i = options.regex.length - 1; i >= 0; i--){ 25 | re = options.regex[i][0]; 26 | sub = options.regex[i][1]; 27 | code = code.replace(re, sub); 28 | }; 29 | 30 | var jsp = ugly.parser; 31 | var pro = ugly.uglify; 32 | 33 | var ast = jsp.parse(code, options.strict_semicolons, true, options.token); // parse code and get the initial AST 34 | 35 | var ast = options.ast(ast); // run ast through custom converter 36 | 37 | var beautified_code = pro.gen_code(ast, options.gen_options); // final code here 38 | 39 | beautified_code = (options.prepend || "") + beautified_code ; 40 | beautified_code = beautified_code + (options.append || ""); 41 | return beautified_code; 42 | }; 43 | 44 | 45 | macros.register = function(extension, options){ 46 | require.extensions[extension] = function(module, filename) { 47 | var content; 48 | content = convert(fs.readFileSync(filename, 'utf8'), options); 49 | return module._compile(content, filename); 50 | }; 51 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Aaron Blohowiak", 3 | "name": "macros", 4 | "version": "0.0.1", 5 | "repository": { 6 | "url": "git://github.com/aaronblohowiak/macros.git" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "node test/test.js" 11 | }, 12 | "engines": { 13 | "node": "~v0.4.8" 14 | }, 15 | "dependencies": { 16 | "aaronblohowiak-plugify-js":"1.0.6" 17 | }, 18 | "devDependencies": { 19 | "burrito": "0.2.7" 20 | } 21 | } -------------------------------------------------------------------------------- /test/sample.ds: -------------------------------------------------------------------------------- 1 | f getRegex(){ 2 | return /f(){}/; 3 | } 4 | 5 | obj = {}; 6 | 7 | obj.getStr = f(){ 8 | return "f(){}"; 9 | } 10 | 11 | module.exports = f(){ 12 | var a = obj.getStr(); 13 | var b = getRegex(); 14 | return [b, a]; 15 | } 16 | 17 | module.exports.location = "development.local" -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | macros = require("../index.js"); 2 | 3 | burrito = require("burrito"); 4 | 5 | macros.register(".ds",{ 6 | prepend: "console.log('being processed: '+ (new Date));\n", 7 | regex: [ 8 | [/development.local/, "example.com"] 9 | ], 10 | token: function(tok){ 11 | if(tok.type == "name" && tok.value == "f"){ 12 | tok.type = "keyword"; 13 | tok.value = "function"; 14 | } 15 | return tok; 16 | }, 17 | ast: function(ast){ 18 | var str = burrito(ast, function(node){ 19 | if(node.name === 'call'){ 20 | node.wrap('wrapper(%s)'); 21 | } 22 | }); 23 | 24 | return burrito.parse(str); 25 | }, 26 | append: "function wrapper(fnCall){ return fnCall;}" 27 | }); 28 | 29 | sample = require("./sample"); 30 | result = sample(); 31 | 32 | expect = "f(){}"; 33 | 34 | reg = result[0].toString().substr(1, expect.length); 35 | str = result[1]; 36 | 37 | assert = require("assert"); 38 | 39 | assert.equal(expect, str); 40 | assert.equal(expect, reg); 41 | 42 | assert.equal("example.com", sample.location); 43 | 44 | 45 | console.log("ok"); --------------------------------------------------------------------------------