UNPKG

@adobe/htlengine

Version:

Javascript Based HTL (Sightly) parser

279 lines (249 loc) 7.36 kB
/* * Copyright 2018 Adobe. All rights reserved. * This file is licensed to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ const format = require('./format'); const formatUri = require('./format_uri'); const formatXss = require('./format_xss'); const HtmlDOMFactory = require('./HtmlDOMFactory'); const HDOMFactory = require('./HDOMFactory'); const VDOMFactory = require('./VDOMFactory'); module.exports = class Runtime { constructor() { this._globals = {}; this._templates = {}; this._resourceLoader = null; this._includeHandler = null; this._dom = new HtmlDOMFactory(); } get globals() { return this._globals; } get templates() { return this._templates; } get dom() { return this._dom; } // eslint-disable-next-line class-methods-use-this get col() { function limit(col, { begin, end, step } = {}) { if (!begin && !end && !step) { return col; } /* eslint-disable no-param-reassign */ if (!Array.isArray(col)) { col = Object.keys(col); } begin = begin ? Number.parseInt(begin, 10) : 0; end = end ? Number.parseInt(end, 10) : col.length - 1; step = step ? Number.parseInt(step, 10) : 1; const ret = []; for (let i = begin; i <= end; i += step) { ret.push(col[i]); } return ret; /* eslint-enable no-param-reassign */ } function coerce(c) { if (!c) { return []; } if (typeof c[Symbol.iterator] === 'function') { return Array.from(c); } return c; } function rel(key, value) { if (typeof value === 'object') { return Array.isArray(value) ? value.indexOf(key) >= 0 : (key in value); } return value.toString().indexOf(key) >= 0; } return { init: (c, opts) => limit(coerce(c), opts), len: (c) => (Array.isArray(c) ? c.length : Object.keys(c).length), keys: (c) => Object.keys(c), get: (c, k) => (Array.isArray(c) ? c[k] : k), // eslint-disable-next-line no-nested-ternary empty: (c) => (Array.isArray(c) ? c.length === 0 : (typeof c === 'number' ? !`${c}` : !c)), rel, }; } // eslint-disable-next-line class-methods-use-this async run(fn) { const gen = fn(); let ret = {}; for (;;) { // eslint-disable-next-line no-await-in-loop ret = gen.next(await ret.value); if (ret.done) { return ret.value; } } } withDomFactory(domFactory) { if (typeof domFactory === 'function') { this._dom = domFactory(this); } else { this._dom = domFactory; } return this; } withResourceLoader(value) { this._resourceLoader = value; return this; } withIncludeHandler(value) { this._includeHandler = value; return this; } setGlobal(name, obj) { if (obj === undefined) { Object.keys(name).forEach((k) => { this._globals[k] = name[k]; }); } else { this._globals[name] = obj; } return this; } // eslint-disable-next-line class-methods-use-this listInfo(idx, size) { const nIdx = Number(idx); const nSize = Number(size); const count = nIdx + 1; return { index: nIdx, count, first: nIdx === 0, middle: nIdx > 0 && count < nSize, last: count === nSize, odd: nIdx % 2 === 0, even: nIdx % 2 === 1, }; } async use(Mod, options) { const mod = new Mod(); Object.keys(options).forEach((k) => { mod[k] = options[k]; }); /** * For every property of the given object this function will * add a camel cased getter property * * For example: * const demo = enhanceWithGetters({ foo: "bar" }) * console.log(demo.getFoo); // 'bar' */ function enhanceWithGetters(baseObject) { // Use a object without getters and setters for // browser which have no support for Proxy as Proxy // can not be polyfilled e.g. Internet Explorer 11 if (typeof Proxy === 'undefined') { return baseObject; } return new Proxy(baseObject, { has(target, key) { if (!(key in target)) { // eslint-disable-next-line no-param-reassign key = `get${key.charAt(0).toUpperCase()}${key.substring(1)}`; return key in target; } return true; }, get(target, prop, receiver) { if (prop === 'then') { return Reflect.get(target, prop, receiver); } if (!(prop in target)) { if (!prop.startsWith('get')) { // eslint-disable-next-line no-param-reassign prop = `get${prop.charAt(0).toUpperCase()}${prop.substring(1)}`; } } if (prop in target) { let value = target[prop]; if (typeof value === 'function') { value = value(); } return value; } return undefined; }, }); } return enhanceWithGetters(await mod.use(this._globals)); } resource(uri, options) { if (!this._resourceLoader) { return ''; } return this._resourceLoader(this, uri, options); } include(uri, options) { if (!uri) { // eslint-disable-next-line no-param-reassign uri = options.file; } if (!uri) { throw new Error('Include call needs file argument or option.'); } if (!this._includeHandler) { return ''; } return this._includeHandler(this, uri, options); } // eslint-disable-next-line class-methods-use-this xss(value, context, hint) { return formatXss(value, context, hint); } async call(fn, args) { if (!fn) { throw new Error('Template call that has not been registered.'); } const callable = fn.bind(this, args); return this.run(callable); } /** * Registers a template. * @param {string} id The id of the use group * @param {string} name The name of the template function * @param {Function} fn Template function * @returns {object} the use group. */ template(id, name, fn) { let group = this._templates[id]; if (!group) { group = {}; this._templates[id] = group; } if (name) { group[name] = fn; } return group; } // eslint-disable-next-line class-methods-use-this exec(name, value, arg0) { if (name === 'join') { return Array.isArray(value) ? value.join(arg0 || ', ') : value; } if (name === 'format') { return format(value, arg0); } if (name === 'uriManipulation') { return formatUri(value, arg0); } throw new Error(`Unknown runtime call: ${name}`); } }; module.exports.VDOMFactory = VDOMFactory; module.exports.HDOMFactory = HDOMFactory; module.exports.HtmlDOMFactory = HtmlDOMFactory;