Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,23 @@ where

You can see some more examples in the tests here [here](test/errors/index.js)

## Sync API

sql-stamp has a synchronous API. This is useful for processing SQL templates and exporting from node modules.

```js
var sqlStamp = require("sql-stamp/sync");
var templater = sqlStamp([
/* Pass a list of SQL templates */
__dirname+"/friends.sql",
__dirname+"/example.sql"
]);
templater1(__dirname+"/example.sql", {foo: "bar"}); // => {sql: "select...", args: ["bar"]}

var files = glob.sync("./sql/**/*.sql")
var templater2 = sqlStamp(files);
templater2(__dirname+"../lib/sql/foo.sql", {foo: "bar"}); // => {sql: "select...", args: ["bar"]}
```

## Test
Run the unit tests
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ module.exports = function(files, opts, callback) {
}
})
.nodeify(callback);
}
};
13 changes: 6 additions & 7 deletions lib/operators/require.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
var assign = require("lodash.assign");
var Bluebird = require("bluebird");
var path = require("path");
var objByPath = require("obj-by-path");
var path = require("path");
var assign = require("lodash.assign");

module.exports = {
parse: function(relFilepath) {
var filepath = path.resolve(path.dirname(this.path), relFilepath);

if(!this.templates.hasOwnProperty(filepath)) {
return Bluebird.props({
return {
template: this.recurse(filepath)
});
};
} else {
return Bluebird.props({
return {
template: this.templates[filepath]
});
};
}
},
run: function(relFilepath, dataKey) {
Expand Down
50 changes: 9 additions & 41 deletions lib/parser/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
var Bluebird = require("bluebird");
var util = require("../util");
var errors = require("../errors");
var operators = require("../operators");
var parserRegex = require("./regex");
var runner = require("./runner");
var stack = require("./stack");
var errors = require("../errors");
var parserRegex = require("./regex");
var util = require("../util");

var fs = Bluebird.promisifyAll(
require("fs")
Expand All @@ -22,7 +22,7 @@ function parseRule(templates, filepath, raw, type, args, indent, ln, opts) {
return Bluebird.resolve()
.then(function() {
if(operator.parse) {
return operator.parse.apply({
var obj = operator.parse.apply({
templates: templates,
recurse: function(filepath) {
return parse(filepath, templates, opts)
Expand All @@ -34,6 +34,10 @@ function parseRule(templates, filepath, raw, type, args, indent, ln, opts) {
},
path: filepath
}, operatorArgs);

if (obj) {
return Bluebird.props(obj);
}
}
})
.then(function(_args) {
Expand All @@ -44,7 +48,7 @@ function parseRule(templates, filepath, raw, type, args, indent, ln, opts) {
var out = operator.run.apply({templates: templates, data: ctx, parseArgs: _args}, operatorArgs);

// TODO: Nasty
out.sql = out.sql.split("\n").map(function(line, idx) {
out.sql = String(out.sql).split("\n").map(function(line, idx) {
if(idx > 0) {
return indent + line;
} else {
Expand All @@ -71,42 +75,6 @@ function parse(filepath, templates, opts) {
var indent = "";
var foundChar = false;

/**
* Ok so this regexp is a little confusing, so lets break it down
*
* Firstly its aim is to walk over a string (using RegExp#exec). The RegExp will always match something and the grouping tells us what that something is
*
* It'll be either a
*
* - COMMAND, with a nested:
* - COMMAND_TYPE
* - COMMAND_ARGS
* - NEWLINE
* - SQL
*
* Which is mapped in 'posMap' below
*
* These are commented in the broken apart regexp below
*
* # group but ignore in output
* /(?:
* # Is the current part a known templating [COMMAND]
* (
* \{
* # Capture the [COMMAND_TYPE]
* ([#=?!>])
* # Capture the [COMMAND_ARGS], up to the next '{' or '}'
* ([^}{]*)
* \}
* )
* # OR is it a [NEWLINE]
* |([\n])
* # ELSE its some [SQL] match upto the next '{' or '}' this is kind of a hack to allow braces that are not a part of the syntax
* |(.[^}{\n]*)
* # Global and multiline
* )/gm
*
*/
var re = parserRegex.re();
var ln = 0;

Expand Down
143 changes: 143 additions & 0 deletions lib/parser/sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
var errors = require("../errors");
var fs = require("fs");
var operators = require("../operators");
var parserRegex = require("./regex");
var runner = require("./runner");
var stack = require("./stack");
var util = require("../util");

function parseRuleSync(templates, filepath, raw, type, args, indent, ln, opts) {
var operator = operators[type];
var operatorArgs = args.split(",")
.map(util.chomp)
.map(util.removeQuotes)
.filter(util.removeEmpty);

var _args;
// Operator
if(operator.parse) {
_args = operator.parse.apply({
templates: templates,
recurse: function(filepath) {
var data = parseSync(filepath, templates, opts);
// As a cache so we don't need to parse twice
templates[filepath] = data;
return data;
},
path: filepath
}, operatorArgs);
}

return {
sql: raw,
ln: ln,
fn: function(ctx) {
var out = operator.run.apply({templates: templates, data: ctx, parseArgs: _args}, operatorArgs);

// TODO: Nasty
out.sql = String(out.sql).split("\n").map(function(line, idx) {
if(idx > 0) {
return indent + line;
} else {
return line;
}
}).join("\n");
return out;
}
};
}

function parseSync(filepath, templates, opts) {
try {
var sqlRaw = util.chomp(
fs.readFileSync(filepath)
);

var tokens = [];
var errStack = [];
var indent = "";
var foundChar = false;

var ln = 0;

var re = parserRegex.re();
var posMap = parserRegex.posMap;

var idx = -1;
var m;

while(m = re.exec(sqlRaw)) {
idx++;
if(!foundChar && m[0].match(/^\s*$/m)) {
indent += m[0];
} else {
foundChar = true;
}

if(m[posMap.sql]) {
tokens.push({
sql: m[posMap.sql],
ln: ln,
idx: idx
});
} else if(m[posMap.cmd]) {
var _m = m;
var _ln = ln;
var _idx = idx;

if(m[posMap.cmdType].match(/^[=!>?]/)) {

try {
var token = parseRuleSync(templates, filepath, m[posMap.cmd], m[posMap.cmdType], m[posMap.cmdArgs], indent, ln, opts);
tokens.push(token);
} catch (err) {
errStack.push({
indent: indent,
idx: _idx,
err: err
});

tokens.push({
ln: _ln,
sql: _m[0],
idx: _idx
});
}
} else {
tokens.push({
sql: m[posMap.cmd],
ln: ln,
idx: idx
});
}

} else if(m[posMap.nl]) {
tokens.push({
sql: m[posMap.nl],
ln: ln,
idx: idx
});
indent = "";
foundChar = false;
ln++;
}
}

if(errStack.length > 0) {
// TODO: Should be tracing all errStack here...
throw errStack[0].err + stack.trace(tokens, errStack[0], 100);
}

// Return a template runner
var ret = runner.bind(null, tokens, opts);
return ret;

} catch (err) {
if(err.code === "ENOENT") {
throw new errors.SQLError("No such template '"+filepath+"'");
}
throw err;
}
}

module.exports = parseSync;
34 changes: 34 additions & 0 deletions sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var assign = require("lodash.assign");
var path = require("path");
var parser = require("./lib/parser/sync");
var errors = require("./lib/errors");

/**
* Initialize a SQL templater synchronously
* @param {Array} files
* @param {Object} [opts]
* @param {Function} [callback]
* @return {Promise}
*/
module.exports = function(files, opts) {
opts = assign({
prettyErrors: false
}, opts);

// Generate the templates
var templates = {};
files.forEach(function(filepath) {
filepath = path.resolve(filepath);
templates[filepath] = parser(filepath, templates, opts);
});

return function(key, data) {
key = path.normalize(key);

if(templates.hasOwnProperty(key)) {
return templates[key](data);
} else {
throw new errors.NoSuchTemplate(key);
}
}
}
39 changes: 39 additions & 0 deletions test/functional/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var assert = require("assert");
var sqlStamp = require("../../");
var util = require("../util");

var results = util.readSync([
"./out.sql"
], __dirname);

describe("end-to-end", function() {

describe("async", function () {
var tmpl;

before(function(done) {
sqlStamp([
__dirname+"/example.sql",
__dirname+"/friends.sql",
], {}, function(err, _tmpl) {
tmpl = _tmpl;
done();
});
});


it("should work", function() {
var out = tmpl(__dirname+"/example.sql", {
accountId: 1,
filterDisabled: false,
filterKey: "role",
filterVal: "dev"
});

assert.equal(out.args.length, 2);
assert.equal(out.args[0], 1);
assert.equal(out.args[1], "dev");
assert.equal(out.sql, results["./out.sql"]);
});
});
});
Loading