stcg
Version:
Scripted Template Content Generator
334 lines (333 loc) • 11.7 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Stcg = exports.StcgError = void 0;
var vm2_1 = require("vm2");
/**
* Generator error
*/
var StcgError = /** @class */ (function (_super) {
__extends(StcgError, _super);
function StcgError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return StcgError;
}(Error));
exports.StcgError = StcgError;
/**
* A single code slice
*/
var StcgCodeSlice = /** @class */ (function () {
function StcgCodeSlice(slice) {
this.slice = slice.replace(/\r?\n/gm, ' ');
}
/**
* Transform to code
*/
StcgCodeSlice.prototype.toCode = function () {
return this.slice + "\n";
};
StcgCodeSlice.prototype.isValid = function () {
return this.slice.trim().length !== 0;
};
return StcgCodeSlice;
}());
/**
* A single text slice
*/
var StcgTextSlice = /** @class */ (function () {
function StcgTextSlice(slice) {
this.slice = slice.replace(/\r?\n/gm, '\\n');
}
/**
* Transform to code
*/
StcgTextSlice.prototype.toCode = function () {
return "__out__('" + this.slice + "')\n";
};
/**
* Produces results
*/
StcgTextSlice.prototype.isValid = function () {
return this.slice.length !== 0;
};
/**
* Trim leading spaces untill and includeing the newline
*/
StcgTextSlice.prototype.trimLeading = function () {
this.slice = this.slice.replace(/^ *\\n/, '');
};
return StcgTextSlice;
}());
/**
* A single output slice
*/
var StcgOutputSlice = /** @class */ (function () {
function StcgOutputSlice(slice) {
this.slice = slice;
}
/**
* Transform to code
*/
StcgOutputSlice.prototype.toCode = function () {
return "__out__(" + this.slice + ")\n";
};
/**
* Produces results
*/
StcgOutputSlice.prototype.isValid = function () {
return this.slice.length !== 0;
};
return StcgOutputSlice;
}());
/**
* Template slicer
*/
var StcgTemplateSlicer = /** @class */ (function () {
/**
* Constructor
* @param debugLen Exception message information length
* @param cBeg Code begin marker
* @param cEnd Code end marker
* @param oBeg Output begin marker
* @param oEnd Output end marker
*/
function StcgTemplateSlicer(debugLen, cBeg, cEnd, oBeg, oEnd) {
this.debugLen = debugLen;
/* Search for the following */
var kWords = [cBeg, cEnd, oBeg, oEnd];
/* Detector state machine function */
var gotoFunction = {
0: {},
};
/* Output function */
var outFunction = {};
/* Internal states */
var cGotoState = 0;
/* Create the detection state machine */
for (var _i = 0, kWords_1 = kWords; _i < kWords_1.length; _i++) {
var word = kWords_1[_i];
var cState = 0;
for (var _a = 0, word_1 = word; _a < word_1.length; _a++) {
var char = word_1[_a];
if (gotoFunction[cState] && char in gotoFunction[cState]) {
cState = gotoFunction[cState][char];
}
else {
cGotoState++;
gotoFunction[cState][char] = cGotoState;
gotoFunction[cGotoState] = {};
cState = cGotoState;
}
}
/* Update the output table */
if (outFunction[cState]) {
throw new StcgError("Marker clash on \"" + word + "\"");
}
else {
outFunction[cState] = word;
}
}
/* Check for blocking markers */
for (var marker in outFunction) {
if (Object.keys(gotoFunction[marker]).length !== 0) {
throw new StcgError("Marker \"" + outFunction[marker] + "\" blocks other markers");
}
}
/* Update */
this.gotoFunction = gotoFunction;
this.outFunction = outFunction;
/* Slicer states */
this.emmiterFunction = { 0: {}, 1: {}, 2: {} };
this.emmiterFunction[0][cBeg] = { to: 1, factory: function (slice) { return new StcgTextSlice(slice); } };
this.emmiterFunction[0][oBeg] = { to: 2, factory: function (slice) { return new StcgTextSlice(slice); } };
this.emmiterFunction[1][cEnd] = { to: 0, factory: function (slice) { return new StcgCodeSlice(slice); } };
this.emmiterFunction[2][oEnd] = { to: 0, factory: function (slice) { return new StcgOutputSlice(slice); } };
}
/**
* Slice the template
* @param input Input template
*/
StcgTemplateSlicer.prototype.slice = function (input) {
var _this = this;
/* Output array */
var rVal = [];
/* Error location extractor */
var errLoc = function (ind, len) {
/* form <leading> >>> <marker> <<< <trailing> */
var le = ind - len;
var ls = le - _this.debugLen;
ls = ls < 0 ? 0 : ls;
var te = ind + _this.debugLen;
te = te > input.length ? input.length : te;
var lead = le > ls ? input.slice(ls, le) + " >>> " : '';
var emph = input.slice(ind - len, ind);
var trail = ind < te ? " <<< " + input.slice(ind, te) : '';
return "" + lead + emph + trail;
};
/* Internal states */
var dState = 0;
var cState = 0;
var lIndex = 0;
var cIndex = 0;
var lastLen = 0;
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
var char = input_1[_i];
/* Next index */
cIndex++;
/* Reset on failure */
if (dState > 0 && !(char in this.gotoFunction[dState])) {
dState = 0;
}
/* No match */
if (!(char in this.gotoFunction[dState])) {
continue;
}
/* Next */
dState = this.gotoFunction[dState][char];
/* Call a state function */
var mWord = this.outFunction[dState];
if (mWord) {
/* Last detected length */
lastLen = mWord.length;
/* On valid transition */
if (mWord in this.emmiterFunction[cState]) {
/* Create a slice */
var lStop = cIndex - lastLen;
var nTransition = this.emmiterFunction[cState][mWord];
rVal.push(nTransition.factory(input.slice(lIndex, lStop)));
/* Advance */
cState = nTransition.to;
lIndex = cIndex;
}
else {
/* Invalid transition */
throw new StcgError("Unexpected \"" + mWord + "\" marker: \"" + errLoc(cIndex, lastLen) + "\"");
}
}
}
/* Last slice */
var lTransition = this.emmiterFunction[cState];
if (cState !== 0) {
throw new StcgError("Marker not closed: \"" + errLoc(lIndex, lastLen) + "\"");
}
else {
/* Random factory */
rVal.push(Object.values(lTransition)[0].factory(input.slice(lIndex)));
}
return rVal;
};
return StcgTemplateSlicer;
}());
/**
* Scripted Template Content Generator
*/
var Stcg = /** @class */ (function () {
function Stcg(template, options) {
this.globals = {};
/* Default options */
options = options || {
codeBegin: '[!',
codeEnd: '!]',
outputBegin: '[>',
outputEnd: '<]',
trimAfterCode: true,
debugLen: 6,
};
/* Default markers */
options.codeBegin = options.codeBegin || '[!';
options.codeEnd = options.codeEnd || '!]';
options.outputBegin = options.outputBegin || '[>';
options.outputEnd = options.outputEnd || '<]';
options.trimAfterCode = options.trimAfterCode || true;
options.debugLen = options.debugLen || 5;
/* Debug */
if (options.debugLen < 1) {
throw new StcgError('debugLen option must be >= 1');
}
/* Create the slicer */
var slicer = new StcgTemplateSlicer(options.debugLen, options.codeBegin, options.codeEnd, options.outputBegin, options.outputEnd);
/* Slice based on markers and apply the rules */
var lastCode = false;
var trimLead = options.trimAfterCode;
var splArr = slicer.slice(template).filter(function (cVal) {
/* Mark a code run */
if (cVal instanceof StcgCodeSlice) {
lastCode = true;
}
else if (cVal instanceof StcgTextSlice) {
if (lastCode && trimLead) {
cVal.trimLeading();
}
lastCode = false;
}
else {
lastCode = false;
}
return cVal.isValid();
}, this);
/* Create the script */
var prgScript = splArr.reduce(function (code, data) {
return code + data.toCode();
}, '');
/* Create script */
this.script = new vm2_1.VMScript(prgScript);
}
/**
* Add a global symbol
* @param name Symbol name
* @param data Symbol data
*/
Stcg.prototype.global = function (name, data) {
/* Overrides existing globals */
this.globals[name] = data;
};
/**
* Run the generator
*/
Stcg.prototype.run = function (ldata) {
if (ldata === void 0) { ldata = {}; }
/* Output content */
var output = '';
try {
/* Create the global object */
var vm_1 = new vm2_1.VM();
/* Set data */
Object.entries(__assign(__assign(__assign({}, this.globals), ldata), { "__out__": function (str) { return output += str; } })).forEach(function (_a) {
var k = _a[0], v = _a[1];
vm_1.freeze(v, k);
});
/* And run */
vm_1.run(this.script);
}
catch (err) {
throw new StcgError(err);
}
return output;
};
return Stcg;
}());
exports.Stcg = Stcg;