├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── README.md ├── UNLICENSE ├── lib └── function-to-string.js ├── package.json └── test ├── function-to-string_test.js └── test-files ├── anonymous.js ├── callback.js ├── comments.js ├── fn-string.js ├── inline.js ├── noop.js └── params-comments.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | - "0.8" 6 | 7 | notifications: 8 | email: false 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # function-to-string changelog 2 | 0.2.0 - Added ability to pass string as an argument 3 | 4 | 0.1.0 - Initial release 5 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | jshint: { 6 | files: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'], 7 | options: { 8 | curly: true, 9 | eqeqeq: true, 10 | immed: true, 11 | latedef: true, 12 | newcap: true, 13 | noarg: true, 14 | sub: true, 15 | undef: true, 16 | boss: true, 17 | eqnull: true, 18 | node: true, 19 | 20 | strict: false, 21 | globals: { 22 | exports: true, 23 | describe: true, 24 | before: true, 25 | it: true 26 | } 27 | } 28 | }, 29 | watch: { 30 | 'default': { 31 | files: '<%= jshint.files %>', 32 | tasks: ['default'] 33 | } 34 | } 35 | }); 36 | 37 | // Load in grunt tasks 38 | grunt.loadNpmTasks('grunt-contrib-jshint'); 39 | grunt.loadNpmTasks('grunt-contrib-watch'); 40 | 41 | // Default task. 42 | grunt.registerTask('default', ['jshint']); 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # function-to-string [![Build status](https://travis-ci.org/twolfson/function-to-string.png?branch=master)](https://travis-ci.org/twolfson/function-to-string) 2 | 3 | Extract parameters and body of a function into strings 4 | 5 | This was built as part of the [gifsockets][] project to pass arbitrary canvas commands with a callback to a [rgba generating PhantomJS server][]. 6 | 7 | [gifsockets]: https://github.com/twolfson/gifsockets-server 8 | [rgba generating PhantomJS server]: https://github.com/twolfson/phantomjs-pixel-server 9 | 10 | ## Getting Started 11 | Install the module with: `npm install function-to-string` 12 | 13 | ```javascript 14 | var functionToString = require('function-to-string'); 15 | functionToString(function hello(world) { 16 | // This is a comment 17 | return 'some text'; 18 | }); 19 | 20 | // Returns: 21 | { 22 | name: 'hello', 23 | params: ['world'], 24 | body: '\n // This is a comment\n return \'some text\';\n' 25 | } 26 | ``` 27 | 28 | ## Documentation 29 | We chose to use [esprima][] over [regular expression][] magic. If you are interested in the regular expression route, checkout [AngularJS' source code][] 30 | 31 | [esprima]: http://esprima.org/ 32 | [regular expression]: http://en.wikipedia.org/wiki/Regular_expression 33 | [AngularJS' source code]: https://github.com/angular/angular.js/blob/61943276f026e632dccae6405a05f79d486ed898/src/auto/injector.js#L33-L74 34 | 35 | `functionToString` exposes a single function 36 | 37 | ``` 38 | functionToString(fn) 39 | /** 40 | * Parses function into AST, extracts parameters and body, and returns information 41 | * @param {Function|String} fn Function/Source code of function to parse 42 | * @returns {Object} retObj 43 | * @returns {String} retObj.name Name of `fn` 44 | * @returns {String[]} retObj.params Array of parameters for `fn` 45 | * @returns {String} retObj.body Content of `fn` 46 | */ 47 | ``` 48 | 49 | ### Reconstructing a function 50 | Functions can be reconstructed via the [`Function`][] constructor: 51 | 52 | [`Function`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function 53 | 54 | ``` 55 | var info = { 56 | name: 'hello', 57 | params: ['world'], 58 | body: '\n // This is a comment\n return \'some text\';\n' 59 | }; 60 | var hello = Function.apply({}, info.params.concat([info.body])); 61 | console.log(hello()); // 'some text' 62 | ``` 63 | 64 | ## Examples 65 | `functionToString` accepts function source code as well 66 | 67 | ```js 68 | // Equivalent to `var fn = (function (hello){return 'world';}).toString()` 69 | var fn = 'function (hello){return \'world\';}'; 70 | functionToString(hello); 71 | 72 | // Returns: 73 | { 74 | name: '', 75 | params: ['hello'], 76 | body: 'return \'world\';' 77 | } 78 | ``` 79 | 80 | ## Donating 81 | Support this project and [others by twolfson][gittip] via [gittip][]. 82 | 83 | [![Support via Gittip][gittip-badge]][gittip] 84 | 85 | [gittip-badge]: https://rawgithub.com/twolfson/gittip-badge/master/dist/gittip.png 86 | [gittip]: https://www.gittip.com/twolfson/ 87 | 88 | ## Contributing 89 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint via [grunt](https://github.com/gruntjs/grunt) and test via `npm test`. 90 | 91 | ## Unlicense 92 | As of Nov 16 2013, Todd Wolfson has released this repository and its contents to the public domain. 93 | 94 | It has been released under the [UNLICENSE][]. 95 | 96 | [UNLICENSE]: UNLICENSE 97 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lib/function-to-string.js: -------------------------------------------------------------------------------- 1 | var esprima = require('esprima'); 2 | 3 | /** 4 | * Parses function into AST, extracts parameters and body, and returns information 5 | * @param {Function|String} fn Function/Source code of function to parse 6 | * @returns {Object} retObj 7 | * @returns {String} retObj.name Name of `fn` 8 | * @returns {String[]} retObj.params Array of parameters for `fn` 9 | * @returns {String} retObj.body Content of `fn` 10 | */ 11 | function functionToString(fn) { 12 | // Coerce strings to strings and functions to strings 13 | var fnStr = fn.toString(); 14 | 15 | var fnAst; 16 | try { 17 | // Attempt to parse the AST 18 | var ast = esprima.parse(fnStr, { 19 | range: true 20 | }); 21 | fnAst = ast.body[0]; 22 | } catch (e) { 23 | // If there was an issue, try again with variable assignment 24 | // DEV: This handles anonymous funcitons (`function () {}`) 25 | fnStr = 'var x = ' + fnStr; 26 | var ast = esprima.parse(fnStr, { 27 | range: true 28 | }); 29 | fnAst = ast.body[0].declarations[0].init; 30 | } 31 | 32 | // Extract the function body from the fnAst 33 | var fnBodyAst = fnAst.body; 34 | 35 | // Prepare and return the function information 36 | return { 37 | name: fnAst.id ? fnAst.id.name : '', 38 | params: fnAst.params.map(function getName(paramAst) { 39 | return paramAst.name; 40 | }), 41 | // TODO: Native code? 42 | body: fnStr.slice(fnBodyAst.range[0] + 1, fnBodyAst.range[1] - 1) 43 | }; 44 | } 45 | 46 | module.exports = functionToString; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "function-to-string", 3 | "description": "Extract parameters and body of a function into strings", 4 | "version": "0.2.0", 5 | "homepage": "https://github.com/twolfson/function-to-string", 6 | "author": { 7 | "name": "Todd Wolfson", 8 | "email": "todd@twolfson.com", 9 | "url": "http://twolfson.com/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/twolfson/function-to-string.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/twolfson/function-to-string/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "UNLICENSE", 21 | "url": "https://github.com/twolfson/function-to-string/blob/master/UNLICENSE" 22 | } 23 | ], 24 | "main": "lib/function-to-string", 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | }, 28 | "scripts": { 29 | "test": "mocha" 30 | }, 31 | "dependencies": { 32 | "esprima": "~1.0.4" 33 | }, 34 | "devDependencies": { 35 | "mocha": "~1.11.0", 36 | "grunt": "~0.4.1", 37 | "grunt-contrib-jshint": "~0.6.0", 38 | "grunt-contrib-watch": "~0.4.0", 39 | "glob": "~3.2.7" 40 | }, 41 | "keywords": [ 42 | "function", 43 | "string", 44 | "extract", 45 | "convert", 46 | "arguments", 47 | "body" 48 | ] 49 | } -------------------------------------------------------------------------------- /test/function-to-string_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var glob = require('glob'); 3 | var testFiles = glob.sync(__dirname + '/test-files/**/*.js'); 4 | var functionToString = require('../'); 5 | 6 | describe('functionToString', function () { 7 | testFiles.forEach(function (testFile) { 8 | describe('interpretting ' + testFile, function () { 9 | before(function () { 10 | var testCase = require(testFile); 11 | this.testCase = testCase; 12 | this.actualInfo = functionToString(testCase.fn); 13 | }); 14 | 15 | it('has the expected name', function () { 16 | assert.deepEqual(this.testCase.name, this.actualInfo.name); 17 | }); 18 | 19 | it('has the expected parameters', function () { 20 | assert.deepEqual(this.testCase.params, this.actualInfo.params); 21 | }); 22 | 23 | it('has the expected body', function () { 24 | assert.strictEqual(this.testCase.body, this.actualInfo.body); 25 | }); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/test-files/anonymous.js: -------------------------------------------------------------------------------- 1 | exports.fn = function(){}; 2 | 3 | exports.name = ''; 4 | exports.params = []; 5 | exports.body = ''; -------------------------------------------------------------------------------- /test/test-files/callback.js: -------------------------------------------------------------------------------- 1 | exports.fn = function callback (info, cb) { 2 | var hello = 'world'; 3 | cb(null, hello); 4 | }; 5 | 6 | exports.name = 'callback'; 7 | exports.params = ['info', 'cb']; 8 | exports.body = [ 9 | "", 10 | " var hello = 'world';", 11 | " cb(null, hello);", 12 | "" 13 | ].join('\n'); -------------------------------------------------------------------------------- /test/test-files/comments.js: -------------------------------------------------------------------------------- 1 | exports.fn = function comments (data) { 2 | // This is a comment 3 | return data; 4 | }; 5 | 6 | exports.name = 'comments'; 7 | exports.params = ['data']; 8 | exports.body = [ 9 | "", 10 | " // This is a comment", 11 | " return data;", 12 | "" 13 | ].join('\n'); -------------------------------------------------------------------------------- /test/test-files/fn-string.js: -------------------------------------------------------------------------------- 1 | exports.fn = "function fnString (data) {'hai';}"; 2 | 3 | exports.name = 'fnString'; 4 | exports.params = ['data']; 5 | exports.body = "'hai';"; -------------------------------------------------------------------------------- /test/test-files/inline.js: -------------------------------------------------------------------------------- 1 | exports.fn = function inline () {'hai';}; 2 | 3 | exports.name = 'inline'; 4 | exports.params = []; 5 | exports.body = "'hai';"; -------------------------------------------------------------------------------- /test/test-files/noop.js: -------------------------------------------------------------------------------- 1 | function noop() {} 2 | exports.fn = noop; 3 | 4 | exports.name = 'noop'; 5 | exports.params = []; 6 | exports.body = ''; -------------------------------------------------------------------------------- /test/test-files/params-comments.js: -------------------------------------------------------------------------------- 1 | exports.fn = function paramsComments (/* Number */ data) { 2 | return data; 3 | }; 4 | 5 | exports.name = 'paramsComments'; 6 | exports.params = ['data']; 7 | exports.body = [ 8 | "", 9 | " return data;", 10 | "" 11 | ].join('\n'); --------------------------------------------------------------------------------