From cb80745322f64fd545de891c5dda8b7b92478502 Mon Sep 17 00:00:00 2001 From: Leonid Borisenko Date: Fri, 15 Feb 2013 17:09:00 +0300 Subject: [PATCH 1/3] Add example Sakefile with precompiling function --- examples/precompile-sakefile/Sakefile | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 examples/precompile-sakefile/Sakefile diff --git a/examples/precompile-sakefile/Sakefile b/examples/precompile-sakefile/Sakefile new file mode 100644 index 0000000..7447199 --- /dev/null +++ b/examples/precompile-sakefile/Sakefile @@ -0,0 +1,10 @@ +// sakefile: { +// function (sakefile) { +// return sakefile.replace("\\" +"default,", '"default",'); +// }; +// sakefile: } + +task(\default, function (t) { + log("Success"); + t.done(); +}); From 0985ae2e948c6ee2ff92a1edcfd1c5bc34bf4016 Mon Sep 17 00:00:00 2001 From: Leonid Borisenko Date: Fri, 15 Feb 2013 17:45:48 +0300 Subject: [PATCH 2/3] Add basic test for Sakefile precompiling --- test/test-precompile-sakefile.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/test-precompile-sakefile.js diff --git a/test/test-precompile-sakefile.js b/test/test-precompile-sakefile.js new file mode 100644 index 0000000..958570d --- /dev/null +++ b/test/test-precompile-sakefile.js @@ -0,0 +1,24 @@ + +var futils = require('sake/file-utils'), + sh = futils.sh; + +function shCallback (done) { + return function (error, result) { + if (error) { + throw new Error(error); + } + done(result); + }; +} +suite('Precompile Sakefile', function () { + + test('works', function (done) { + var cmd = 'sake -f examples/precompile-sakefile/Sakefile'; + + sh(cmd, shCallback(function (result) { + result.should.equal('Success\n'); + done(); + })); + }); + +}); From 9c4ba6ea443f3332b442fccf74059bd1bd4dbe6f Mon Sep 17 00:00:00 2001 From: Leonid Borisenko Date: Fri, 15 Feb 2013 21:37:35 +0300 Subject: [PATCH 3/3] Implement Sakefile precompiling Precompiling is useful for writing Sakefile in various compile-to-JS languages. Precompiling is performed by JavaScript function from region embedded in Sakefile and bounded by required marker lines. Marker line format is inspilred by Vim modeline (but it's different). Function region (along with marker lines) is suited to be located in comments of host language: * marker lines are independent of its prefixes, * prefix of start marker line is stripped from every following function source line. Function must take one argument (string with Sakefile source) and return string with compiled source. This commit contains further restrictions to general idea: * only files without extension are searched for function region, * start marker must be located in first 5 lines of Sakefile, * function length is limited to 100 lines. * format of marker lines: - start marker line: sakefile:{ - end marker line: sakefile:} where -- any characters -- one space or tab character -- zero or more spaces or tab characters --- node_modules/sake/sake.js | 83 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/node_modules/sake/sake.js b/node_modules/sake/sake.js index abf0888..8aada9e 100644 --- a/node_modules/sake/sake.js +++ b/node_modules/sake/sake.js @@ -18,6 +18,10 @@ var Proteus = require("proteus"), OPTIONS = {}, NAMESPACES = [], PATH_SPLIT_RE = process.platform === "win32" ? /[\/\\]+/ : /\/+/, + PRECOMPILE_FUN_REGION_START_RE = /^(.*)\ssakefile:\s*{/, + PRECOMPILE_FUN_REGION_END_RE = /\ssakefile:\s*}/, + PRECOMPILE_FUN_REGION_SEARCH_LINES_MAX = 5, + PRECOMPILE_FUN_REGION_LINES_MAX = 100, CURRENT_PATH, sakeContext, futil, @@ -67,6 +71,65 @@ function sakeRequire (name) { } } //--------------------------------------------------------------------------- +// Get source of bootstrap function for precompiling Sakefile. +// +// Function region is bounded by required marker lines which aren't included +// in returned function source. Prefix of start marker line is stripped from +// every function source line. +// +// If function region wasn't found or exceeded limit of lines, empty string +// is returned. +//--------------------------------------------------------------------------- +function extractPrecompileFunctionSource (sakefileSource) { + var codeLineNum = 0, + functionRegionLinesRead = 0, + functionSource = "", + newlinePos = 0, + pos = 0, + codeLine, + marker, + markerLinePrefix; + + while (pos < sakefileSource.length) { + // Find next code line. + newlinePos = sakefileSource.indexOf("\n", pos); + if (newlinePos === -1) { + newlinePos = sakefileSource.length + } + codeLine = sakefileSource.substr(pos, newlinePos - pos); + pos = newlinePos + 1; + + if (!functionRegionLinesRead) { + // Not in function region yet. + // Test read code line for being marker of function region start. + if (marker = PRECOMPILE_FUN_REGION_START_RE.exec(codeLine)) { + functionRegionLinesRead = 1; + markerLinePrefix = marker[1]; + } + else { + // Count read code lines and check for exceeding a limit of + // lines to look in for marker of function region start. + codeLineNum++; + if (codeLineNum >= PRECOMPILE_FUN_REGION_SEARCH_LINES_MAX) { + return ""; + } + } + continue; + } + + if (PRECOMPILE_FUN_REGION_END_RE.test(codeLine)) { + return functionSource; + } + + functionSource += codeLine.replace(markerLinePrefix, "") + "\n"; + + functionRegionLinesRead++; + if (functionRegionLinesRead >= PRECOMPILE_FUN_REGION_LINES_MAX) { + return ""; + } + } +} +//--------------------------------------------------------------------------- // Create the Run Context //--------------------------------------------------------------------------- function getRunContext () { @@ -148,7 +211,7 @@ function createTask (TaskClass, name, prereqs, action) { Proteus.merge(sake, Object.defineProperties({ run: function (filepath) { - var code, ret; + var code, ret, precompileFunSource; log.debug("sake#run"); @@ -164,7 +227,23 @@ Proteus.merge(sake, Object.defineProperties({ log.debug("Reading Sakefile: " + filepath); code = FS.readFileSync(filepath, "utf8"); - if (Path.extname(filepath).toLowerCase() === ".coffee") { + if (Path.extname(filepath) === "") { + precompileFunSource = extractPrecompileFunctionSource(code); + if (precompileFunSource.length > 0) { + try { + code = eval("(" + + precompileFunSource.replace(/;\s*$/, "") + + ")")(code); + } + catch (e) { + log.error("Precompile function failed. " + e + "\n" + + "Precompile function source:\n" + + precompileFunSource); + process.exit(1); + } + } + } + else if (Path.extname(filepath).toLowerCase() === ".coffee") { try { code = require("coffee-script").compile(code); }