UNPKG

stcg

Version:

Scripted Template Content Generator

334 lines (333 loc) 11.7 kB
"use strict"; 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;