среда, 13 апреля 2016 г.

JavaScript code to string converter

github.com/latentflip/deval

Sometimes you're doing interesting things, and you want a block of code as a multiline string.
But doing this is super annoying:

var codeString = [
  "var foo = 'bar'",
  "function stuff () {",
  "  console.log('The thing is \"10\"');"
  "}"
].join('\n');

Quotes everywhere, keeping track of indentation is a pain if you want it properly formatted, no syntax highlighting.

Function code to string makes it look like this:

var codeToString = require('codeToString');

var codeString = codeToString(function () {
    var foo = 'bar';
    function stuff () {
        console.log('The thing is "10"');
    }
});

// codeString -> "var foo = 'bar';\nfunction stuff () {\n    console.log('The thing is \"10\"');\n}"


It even figures out what indentation you meant and cleans that up.

If eval() takes a string representing code, and turns it into actual code, codeToString() takes actual code, and returns a string representation of it.

Basic usage.

Call codeToString() with a function containing the code you want to get back as a string.
The function wrapper will be removed.

var codeToString = require('codeToString');

var codeString = codeToString(function(){
    var foo = 'bar';
    function stuff () {
        console.log('The thing is "10"');
    }
});

// codeString will be:
//    "var foo = 'bar';
//    function stuff () {
//        console.log('The thing is \"10\"');
//    }"

Advanced usage.

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.

To interpolate:

- Name some positional arguments in the function you pass to codeToString: codeToString(function (arg1, arg2) { ...
- Insert them where you want them in your code by wrapping in dollars: $arg1$
- Pass the values of those arguments as additional arguments to codeToString itself. codeToString(function (arg1, arg2) { ... }, "one", 2)

var codeString = codeToString(function (foo, bar) {
    var thing = $bar$;
    console.log('$foo$');
    console.log(thing);
}, "hi", 5);

// codeString will be:
//    "var thing = 5;
//    console.log('hi');
//    console.log(thing)"

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$'

Source Code of codeToString():

var min = function (arr) {return Math.min.apply(Math, arr);};

var REGEXES = {
    functionOpening: /^function\s*\((.*)\)[^{]{/
};


module.exports = function (fn/*, interpolateArgs... */) {
    var str = fn.toString();
    var interpolateArgs = Array.prototype.slice.call(arguments, 1);
    var argNames;
    if (interpolateArgs.length) {argNames = getArgumentNames(str);}
    str = removeFunctionWrapper(str);
    str = dedent(str);
    if (argNames && argNames.length) {str = interpolate(str, argNames, interpolateArgs);}
    return str;
};

function getArgumentNames (str) {
    var argStr = str.match(REGEXES.functionOpening);
    return argStr[1].split(',').map(function (s) { return s.trim(); });
}

function removeFunctionWrapper (str) {
    var closingBraceIdx, finalNewlineIdx, lastLine;

    // remove opening function bit
    str = str.replace(REGEXES.functionOpening, '');

    // remove closing function brace
    closingBraceIdx = str.lastIndexOf('}');
    if (closingBraceIdx > 0) {str = str.slice(0, closingBraceIdx - 1);}

    // If there was no code on opening wrapper line, remove it
    str = str.replace(/^[^\S\n]*\n/, '');

    // If there was no code on final line, remove it
    finalNewlineIdx = str.lastIndexOf('\n');
    lastLine = str.slice(finalNewlineIdx);
    if (lastLine.trim() === '') str = str.slice(0, finalNewlineIdx);

    return str;
}

// Reset indent on the code to minimum possible
function dedent (str) {
    var lines = str.split('\n');
    var indent = min(lines.map(function (line) {return line.match(/^\s*/)[0].length;}));
    lines = lines.map(function (line) {return line.slice(indent);});
    return lines.join('\n');
}

function interpolate (str, argNames, args) {
    argNames.forEach(function (name, i) {
        var regex = new RegExp('\\$' + name + '\\$', 'g');
        str = str.replace(regex, args[i]);
    });
    return str;
}

Tests:

var test = require('tape');
var codeToString = require('./codeToString');

test('it serializes multiline code', function (t) {
    var serialized = codeToString(function () {
        console.log('hi');
        console.log('there');
    });

    var expected = [
        "console.log('hi');",
        "console.log('there');"
    ].join('\n');

    t.equal(serialized, expected);
    t.end();
});

test('it serializes inline code', function (t) {
    var serialized = codeToString(function () { console.log('hi'); console.log('there'); });

    var expected = [
        "console.log('hi'); console.log('there');"
    ].join('\n');

    t.equal(serialized, expected);
    t.end();
});

test('it even interpolates things', function (t) {
    var serialized = codeToString(function (foo, bar) {
        console.log('$foo$');
        console.log($bar$);
    }, "hi", 5);

    var expected = [
        "console.log('hi');",
        "console.log(5);"
    ].join('\n');

    t.equal(serialized, expected);
    t.end();
});

Комментариев нет:

Отправить комментарий