UNPKG

monic

Version:
499 lines (416 loc) 11.3 kB
/*! * Monic v2.6.1 * https://github.com/MonicBuilder/Monic * * Released under the MIT license * https://github.com/MonicBuilder/Monic/blob/master/LICENSE * * Date: Mon, 05 Jul 2021 05:34:25 GMT */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileStructure = void 0; var _parser = _interopRequireDefault(require("./parser")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const $C = require('collection.js/compiled'); const uuid = require('uuid').v4, path = require('path'); /** * File structure class */ class FileStructure { /** * @param {string} file - file path * @param {!Object} globals - map of the global Monic flags */ constructor({ file, globals }) { this.root = { type: 'root', content: [], labels: {} }; this.file = file; this.uuid = uuid(); this.currentBlock = this.root; this.included = {}; this.globals = globals; } /** * Returns a file path relative to the base folder * * @param {string} file - file path * @returns {string} */ getRelativePathOf(file) { return _parser.default.normalizePath(path.resolve(path.dirname(this.file), file)); } /** * Adds a custom code (text) to the structure * * @param {string} code - some code * @param {Object=} [opt_info] - information object for a source map * @returns {!FileStructure} */ addCode(code, opt_info) { this.currentBlock.content.push({ type: 'code', included: false, info: opt_info, code }); return this; } /** * Adds a file to the structure * * @param {!FileStructure} fileStructure - structure of the adding file * @param {!Object} labels - map of Monic labels * @returns {!FileStructure} */ addInclude(fileStructure, labels) { this.currentBlock.content.push({ type: 'include', fileStructure, labels }); return this; } /** * Adds expulsion to the structure * * @param {!FileStructure} fileStructure - structure of the expulsion file * @param {!Object} labels - map of Monic labels * @returns {!FileStructure} */ addWithout(fileStructure, labels) { this.currentBlock.content.push({ type: 'without', fileStructure, labels }); return this; } /** * Sets a new flag * * @param {string} flag - flag name * @param {?=} [opt_value] - flag value * @returns {!FileStructure} */ addSet(flag, opt_value = true) { if (this.currentBlock.type === 'root') { this.globals[flag] = opt_value; } this.currentBlock.content.push({ type: 'set', varName: flag, value: opt_value }); return this; } /** * Cancels (removes) the specified flag * * @param {string} flag - flag name * @returns {!FileStructure} */ addUnset(flag) { if (this.currentBlock.type === 'root') { delete this.globals[flag]; } this.currentBlock.content.push({ type: 'set', varName: flag, value: false }); return this; } /** * Sets a new condition * * @param {string} flag - condition * @param {string} type - condition type * @param {?=} [opt_value] - condition value * @param {boolean=} [opt_unless] - unless mode * @returns {!FileStructure} */ beginIf(flag, type, opt_value = true, opt_unless = false) { const aliases = { '=': 'eq', '!=': 'ne', '>': 'gt', '>=': 'gte', '<': 'lt', '<=': 'lte' }; const ifBlock = { parent: this.currentBlock, type: aliases[type] || type, content: [], varName: flag, value: opt_value, unless: opt_unless }; this.currentBlock.content.push(ifBlock); this.currentBlock = ifBlock; return this; } /** * Ends a condition * @returns {!FileStructure} */ endIf() { const validIf = { is: true, eq: true, ne: true, gt: true, gte: true, lt: true, lte: true, has: true, call: true, callRight: true, like: true, instanceof: true, typeof: true }; if (!validIf[this.currentBlock.type]) { throw new SyntaxError(`Attempt to close the unopened block "#${this.currentBlock.unless ? 'unless' : 'if'}"`); } this.currentBlock = this.currentBlock.parent; return this; } /** * Sets a new label * * @param {string} label - label name * @returns {!FileStructure} */ beginLabel(label) { const labelBlock = { parent: this.currentBlock, type: 'label', content: [], label }; this.currentBlock.content.push(labelBlock); this.currentBlock = labelBlock; return this; } /** * Ends a label * @returns {!FileStructure} */ endLabel() { if (this.currentBlock.type !== 'label') { throw new SyntaxError('Attempt to close the unopened block "#label"'); } this.currentBlock = this.currentBlock.parent; return this; } /** * Compiles the structure * * @param {Array=} [opt_labels] - map of Monic labels * @param {Object=} [opt_flags] - map of Monic flags * @param {SourceMapGenerator=} [opt_sourceMap] - source map object * @returns {string} */ compile(opt_labels, opt_flags, opt_sourceMap) { $C(opt_labels).forEach((el, key) => this.root.labels[key] = true); return this._compileBlock(this.root, this.root.labels, opt_flags || {}, opt_sourceMap); } /** * Compiles expulsion of the file * * @param {Array=} [opt_labels] - a map of Monic labels * @param {Object=} [opt_flags] - map of Monic flags * @param {SourceMapGenerator=} [opt_sourceMap] - source map object * @returns {!FileStructure} */ without(opt_labels, opt_flags, opt_sourceMap) { this._compileBlock(this.root, opt_labels || {}, opt_flags || {}, opt_sourceMap); return this; } /** * Compiles the specified file structure * * @private * @param {!Object} block - structure object * @param {!Object} labels - map of Monic labels * @param {!Object} flags - map of Monic flags * @param {SourceMapGenerator=} [opt_sourceMap] - source map object * @returns {string} */ _compileBlock(block, labels, flags, opt_sourceMap) { switch (block.type) { case 'code': if (!block.included) { block.included = true; return block.code; } break; case 'include': const cacheKey = `${block.fileStructure.file}@${Object.keys(block.labels).sort()}@${Object.keys(flags).sort()}`; $C(labels).forEach((el, key) => { block.labels[key] = true; }); if (!this.included[cacheKey]) { this.included[cacheKey] = true; return block.fileStructure.compile(block.labels, flags, opt_sourceMap); } break; case 'without': block.fileStructure.without(block.labels, flags); break; case 'set': { const store = $C(flags), path = block.varName, val = block.value; if (path.slice(-1) === '.') { const ctx = store.get(path.slice(0, -1)); if (Array.isArray(ctx)) { ctx.push(val); } else { store.set(val, path.slice(-1)); } } else { store.set(val, path); } break; } default: if (FileStructure.isValidContentBlock(block, labels, flags)) { return $C(block.content).map(block => { if (!_parser.default.current || this.uuid !== _parser.default.current) { _parser.default.current = this.uuid; } const { info } = block, compiledBlock = this._compileBlock(block, labels, flags, opt_sourceMap); if (opt_sourceMap && info && compiledBlock) { if (!info.ignore) { const test = {}, selfMap = info.source; $C(selfMap ? [info] : info).forEach(info => { if (selfMap) { info.generated.line = _parser.default.cursor; } else { info.generated.line += _parser.default.cursor - info.generated.line; } opt_sourceMap.addMapping(info); if (!test[info.source]) { test[info.source] = true; opt_sourceMap.setSourceContent(info.source, info.sourcesContent); } }); } _parser.default.cursor++; } return compiledBlock; }).join(''); } } return ''; } /** * Returns true if the specified object is a valid file structure * * @param {!Object} block - structure object * @param {!Object} labels - map of Monic labels * @param {!Object} flags - map of Monic flags * @returns {boolean} */ static isValidContentBlock(block, labels, flags) { const flag = block.varName, flagVal = $C(flags).get(flag), blockVal = block.value; let res; switch (block.type) { case 'root': return true; case 'label': return Boolean(!Object.keys(labels).length || labels[block.label]); case 'is': res = typeof flagVal === 'function' ? flagVal({ flag, flags, labels }) : flagVal; break; case 'eq': res = flagVal === blockVal; break; case 'ne': res = flagVal !== blockVal; break; case 'gt': res = flagVal > blockVal; break; case 'gte': res = flagVal >= blockVal; break; case 'lt': res = flagVal < blockVal; break; case 'lte': res = flagVal <= blockVal; break; case 'has': if (Array.isArray(flagVal)) { res = flagVal.includes(blockVal); } else if (flagVal instanceof RegExp) { res = flagVal.test(blockVal); } else if (flagVal) { if (typeof flagVal.has === 'function') { res = flagVal.has(blockVal); } else { res = flagVal[blockVal]; } } break; case 'like': res = new RegExp(flagVal).test(blockVal); break; case 'call': if (typeof flagVal === 'function') { res = flagVal({ flag, value: blockVal, flags, labels }); } break; case 'callRight': if (typeof blockVal === 'function') { res = blockVal({ flag, value: flagVal, flags, labels }); } break; case 'typeof': res = typeof flagVal === String(blockVal); break; case 'instanceof': res = flagVal instanceof blockVal; break; } if (block.unless) { res = !res; } return Boolean(res || false); } } exports.FileStructure = FileStructure;