├── .gitignore ├── package.json ├── test.js ├── deval.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deval", 3 | "version": "0.1.1", 4 | "description": "Like eval, but backwards.", 5 | "main": "deval.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "tape": "~2.10.2" 9 | }, 10 | "scripts": { 11 | "test": "node test.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:latentflip/deval.git" 16 | }, 17 | "author": "Philip Roberts (@philip_roberts)", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/latentflip/deval/issues" 21 | }, 22 | "homepage": "https://github.com/latentflip/deval" 23 | } 24 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var deval = require('./deval'); 3 | 4 | test('it serializes multiline code', function (t) { 5 | var serialized = deval(function () { 6 | console.log('hi'); 7 | console.log('there'); 8 | }); 9 | 10 | var expected = [ 11 | "console.log('hi');", 12 | "console.log('there');" 13 | ].join('\n'); 14 | 15 | t.equal(serialized, expected); 16 | t.end(); 17 | }); 18 | 19 | test('it serializes inline code', function (t) { 20 | var serialized = deval(function () { console.log('hi'); console.log('there'); }); 21 | 22 | var expected = [ 23 | "console.log('hi'); console.log('there');" 24 | ].join('\n'); 25 | 26 | t.equal(serialized, expected); 27 | t.end(); 28 | }); 29 | 30 | test('it even interpolates things', function (t) { 31 | var serialized = deval(function (foo, bar) { 32 | console.log('$foo$'); 33 | console.log($bar$); 34 | }, "hi", 5); 35 | 36 | var expected = [ 37 | "console.log('hi');", 38 | "console.log(5);" 39 | ].join('\n'); 40 | 41 | t.equal(serialized, expected); 42 | t.end(); 43 | }); 44 | -------------------------------------------------------------------------------- /deval.js: -------------------------------------------------------------------------------- 1 | var min = function (arr) { 2 | return Math.min.apply(Math, arr); 3 | }; 4 | 5 | var REGEXES = { 6 | functionOpening: /^function\s*\((.*)\)[^{]{/ 7 | }; 8 | 9 | 10 | module.exports = function (fn/*, interpolateArgs... */) { 11 | var str = fn.toString(); 12 | var interpolateArgs = Array.prototype.slice.call(arguments, 1); 13 | var argNames; 14 | 15 | if (interpolateArgs.length) { 16 | argNames = getArgumentNames(str); 17 | } 18 | str = removeFunctionWrapper(str); 19 | str = dedent(str); 20 | if (argNames && argNames.length) { 21 | str = interpolate(str, argNames, interpolateArgs); 22 | } 23 | return str; 24 | }; 25 | 26 | function getArgumentNames (str) { 27 | var argStr = str.match(REGEXES.functionOpening); 28 | return argStr[1].split(',').map(function (s) { return s.trim(); }); 29 | } 30 | 31 | function removeFunctionWrapper (str) { 32 | var closingBraceIdx, finalNewlineIdx, lastLine; 33 | 34 | //remove opening function bit 35 | str = str.replace(REGEXES.functionOpening, ''); 36 | 37 | //remove closing function brace 38 | closingBraceIdx = str.lastIndexOf('}'); 39 | if (closingBraceIdx > 0) { 40 | str = str.slice(0, closingBraceIdx - 1); 41 | } 42 | 43 | //If there was no code on opening wrapper line, remove it 44 | str = str.replace(/^[^\S\n]*\n/, ''); 45 | 46 | //If there was no code on final line, remove it 47 | finalNewlineIdx = str.lastIndexOf('\n'); 48 | lastLine = str.slice(finalNewlineIdx); 49 | if (lastLine.trim() === '') str = str.slice(0, finalNewlineIdx); 50 | 51 | return str; 52 | } 53 | 54 | //Reset indent on the code to minimum possible 55 | function dedent (str) { 56 | var lines = str.split('\n'); 57 | var indent = min(lines.map(function (line) { 58 | return line.match(/^\s*/)[0].length; 59 | })); 60 | lines = lines.map(function (line) { 61 | return line.slice(indent); 62 | }); 63 | return lines.join('\n'); 64 | } 65 | 66 | function interpolate (str, argNames, args) { 67 | argNames.forEach(function (name, i) { 68 | var regex = new RegExp('\\$' + name + '\\$', 'g'); 69 | str = str.replace(regex, args[i]); 70 | }); 71 | return str; 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deval 2 | 3 | ![npm info](https://nodei.co/npm/deval.png?compact=true) 4 | 5 | If eval takes a string representing code, and turns it into actual code, deval takes actual code, and returns a string representation of it. 6 | 7 | Browserify/node/commonjs compatible: 8 | 9 | ``` 10 | npm install deval 11 | ``` 12 | 13 | ## Why? 14 | 15 | Sometimes you're doing fun/evil/interesting things, and you want a block of code as a multiline string. But doing this is super annoying: 16 | 17 | ```javascript 18 | 19 | var codeString = [ 20 | "var foo = 'bar'", 21 | "function stuff () {", 22 | " console.log('The thing is \"10\"');" 23 | "}" 24 | ].join('\n'); 25 | ``` 26 | 27 | Quotes everywhere, keeping track of indentation is a pain if you want it properly formatted, no syntax highlighting, bleurgh. 28 | 29 | Deval makes it look like this: 30 | 31 | ```javascript 32 | var deval = require('deval'); 33 | 34 | var codeString = deval(function () { 35 | var foo = 'bar'; 36 | function stuff () { 37 | console.log('The thing is "10"'); 38 | } 39 | }); 40 | //codeString -> "var foo = 'bar';\nfunction stuff () {\n console.log('The thing is \"10\"');\n}" 41 | ``` 42 | 43 | It even figures out what indentation you meant and cleans that up. 44 | 45 | ## Usage 46 | 47 | ### Basic 48 | 49 | Call `deval` with a function containing the code you want to get back as a string. The function wrapper will be removed. 50 | 51 | ```javascript 52 | var deval = require('deval'); 53 | 54 | var codeString = deval(function () { 55 | var foo = 'bar'; 56 | function stuff () { 57 | console.log('The thing is "10"'); 58 | } 59 | }); 60 | 61 | //codeString will be: 62 | // "var foo = 'bar'; 63 | // function stuff () { 64 | // console.log('The thing is \"10\"'); 65 | // }" 66 | ``` 67 | 68 | ### Advanced 69 | 70 | Sometimes you want to interpolate strings/numbers/etc into your generated code. **You can't just use normal scoping rules, because this code won't be executed in the current scope.** So instead you can do a little templating magic. 71 | 72 | To interpolate: 73 | 74 | * Name some positional arguments in the function you pass to deval: `deval(function (arg1, arg2) { ...` 75 | * Insert them where you want them in your code by wrapping in dollars: `$arg1$` 76 | * Pass the values of those arguments as additional arguments to deval itself. `deval(function (arg, arg2) { ... }, "one", 2)` 77 | 78 | #### Example 79 | 80 | ```javascript 81 | var codeString = deval(function (foo, bar) { 82 | var thing = $bar$; 83 | console.log('$foo$'); 84 | console.log(thing); 85 | }, "hi", 5); 86 | 87 | //codeString will be: 88 | // "var thing = 5; 89 | // console.log('hi'); 90 | // console.log(thing)" 91 | ``` 92 | 93 | note: Don't try to be too clever with this, and if you're passing strings, you'll want to wrap them in quotes inside the code block, as shown about for `"hi" -> '$foo$'` 94 | 95 | ## License 96 | 97 | MIT 98 | --------------------------------------------------------------------------------