UNPKG

json-tots

Version:

Template of Templates, a.k.a Template Should Eat Itself

574 lines (565 loc) 24.1 kB
"use strict"; var _Object$keys = require("@babel/runtime-corejs3/core-js-stable/object/keys"); var _Object$getOwnPropertySymbols = require("@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols"); var _filterInstanceProperty = require("@babel/runtime-corejs3/core-js-stable/instance/filter"); var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor"); var _forEachInstanceProperty = require("@babel/runtime-corejs3/core-js-stable/instance/for-each"); var _Object$getOwnPropertyDescriptors = require("@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors"); var _Object$defineProperties = require("@babel/runtime-corejs3/core-js-stable/object/define-properties"); var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/toConsumableArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty")); var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with")); var _trim = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/trim")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); var _repeat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/repeat")); var _parseFloat2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-float")); var _parseInt2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-int")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); var _reduce = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reduce")); function ownKeys(object, enumerableOnly) { var keys = _Object$keys(object); if (_Object$getOwnPropertySymbols) { var symbols = _Object$getOwnPropertySymbols(object); enumerableOnly && (symbols = _filterInstanceProperty(symbols).call(symbols, function (sym) { return _Object$getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var _context5, _context6; var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? _forEachInstanceProperty(_context5 = ownKeys(Object(source), !0)).call(_context5, function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(target, _Object$getOwnPropertyDescriptors(source)) : _forEachInstanceProperty(_context6 = ownKeys(Object(source))).call(_context6, function (key) { _Object$defineProperty(target, key, _Object$getOwnPropertyDescriptor(source, key)); }); } return target; } /* eslint-disable no-param-reassign */ /* eslint-disable curly */ /* eslint-disable no-magic-numbers */ /* eslint-disable no-implicit-coercion */ /* eslint-disable no-useless-escape */ var jp = require('jsonpath'); var F = require('functional-pipelines'); var Fb = require('./times'); var bins = require('./builtins'); var sx = require('./strings'); var sortBy = function sortBy(keyName) { var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref$mapping = _ref.mapping, mapping = _ref$mapping === void 0 ? function (v) { return v; } : _ref$mapping, _ref$asc = _ref.asc, asc = _ref$asc === void 0 ? true : _ref$asc; return function (a, b) { if (!asc) { var _ref2 = [b, a]; a = _ref2[0]; b = _ref2[1]; } return +(mapping(a[keyName]) > mapping(b[keyName])) || +(mapping(a[keyName]) === mapping(b[keyName])) - 1; }; }; var regex = { safeDot: /\.(?![\w\.]+")/, memberOrDescendant: /^[\[\.]/, fnArgsSeparator: /\s*:\s*/, PIPE: /\s*\|\s*/ }; // eslint-disable-next-line no-confusing-arrow var jpify = function jpify(path) { return (0, _startsWith["default"])(path).call(path, '$') ? path : regex.memberOrDescendant.test(path) ? "$".concat(path) : "$.".concat(path); }; var deref = function deref(sources) { return function (ast) { var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref3$meta = _ref3.meta, meta = _ref3$meta === void 0 ? 1 : _ref3$meta, _ref3$source = _ref3.source, source = _ref3$source === void 0 ? 'origin' : _ref3$source; var document = sources[source]; var values; if (F.isNil(document)) { values = []; } else if (!F.isContainer(document)) { meta = 0; values = [document]; // literal value } else { values = jp.query(document, jpify(ast.path)); } return _objectSpread(_objectSpread({}, ast), {}, { '@meta': meta, value: values }); }; }; var query = function query(ast) { var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref4$meta = _ref4.meta, meta = _ref4$meta === void 0 ? 2 : _ref4$meta; var queryOp = function queryOp(values) { return values.pop(); }; if (jp.value(ast, '$.operators.query')) { var ops = { '+': function _(ast) { return function (count) { return function (values) { return bins.take(count)(values); }; }; }, '-': function _(ast) { return function (count) { return function (values) { return count ? bins.skip(count)(values) : values.pop(); }; }; } // semantics of standalone - are not yet defined }; var _ast$operators$query = ast.operators.query, operator = _ast$operators$query.operator, count = _ast$operators$query.count; queryOp = ops[operator](ast)(count); } return _objectSpread(_objectSpread({}, ast), {}, { '@meta': meta, value: queryOp(ast.value) }); }; /** * NOTE: regex for constraint would allow for !abc or ?abc reserved for future use * @param sources * @param config * @returns {function(*=, {meta?: *}=): {"@meta": Number.meta}} */ var constraint = function constraint(_ref5) { var sources = _ref5.sources, config = _ref5.config; return function (ast) { var _ref6 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref6$meta = _ref6.meta, meta = _ref6$meta === void 0 ? 2 : _ref6$meta; var ops = { '?': function _(ast) { return function (isAltLookup) { var defaultSource = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'default'; var defaultValue = arguments.length > 2 ? arguments[2] : undefined; return ast.value !== undefined ? ast : !F.isNil(defaultValue) ? _objectSpread(_objectSpread({}, ast), {}, { value: defaultValue }) : F.compose(query, deref(sources))(ast, { meta: meta, source: defaultSource }); }; }, '!': function _(ast) { return function (isAltLookup, altSource, defaultValue) { var result = ast; result = !F.isEmptyValue(altSource) ? F.compose(query, deref(sources))(ast, { meta: meta, source: altSource }) : _objectSpread(_objectSpread({}, result), {}, { value: F.isNil(ast.value) ? null : ast.value }); result = result.value !== undefined ? result : !F.isNil(defaultValue) ? _objectSpread(_objectSpread({}, result), {}, { value: defaultValue // @TODO: check why it converts to string even if it's standalone }) : _objectSpread(_objectSpread({}, result), {}, { value: null }); return result; }; } }; // eslint-disable-next-line prefer-const var _ast$operators$constr = ast.operators.constraint, operator = _ast$operators$constr.operator, equal = _ast$operators$constr.equal, source = _ast$operators$constr.source, defaultValue = _ast$operators$constr.defaultValue; var result = ops[operator](ast)(equal === '=', source, defaultValue); return _objectSpread(_objectSpread({}, result), {}, { '@meta': meta }); }; }; var constraintOperator = function constraintOperator(_ref7) { var sources = _ref7.sources; return F.composes(constraint({ sources: sources }), bins.has('$.operators.constraint')); }; var symbol = function symbol(_ref8) { var tags = _ref8.tags, context = _ref8.context, sources = _ref8.sources; return function (ast) { var _ref9 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref9$meta = _ref9.meta, meta = _ref9$meta === void 0 ? 2 : _ref9$meta; var ops = { ':': function _(ast) { return function (sources, tag) { sources['@@next'] = sources['@@next'] || []; var job = { type: '@@policy', path: jp.stringify(context.path), tag: tag, source: ast.source, templatePath: '', tagPath: ast.path }; sources['@@next'].push(job); return _objectSpread(_objectSpread({}, ast), {}, { policy: tag }); }; }, '#': function _(ast) { return function (sources, tag) { tag = (0, _trim["default"])(tag).call(tag); var tagHandler = { undefined: ast.path, "null": ast.path, '': ast.path, $: jp.stringify(context.path) }; var path = tagHandler[tag]; if (path === undefined) { path = tag; } tags[path] = ast.value; // sources.tags = tags; return _objectSpread(_objectSpread({}, ast), {}, { tag: path }); }; }, '@': function _(ast) { return function (sources, tag) { var _context, _context2, _context3; var ctx = tags[tag]; // Path rewrite var relativeTagPath = ast.path[0] === '$' ? (0, _slice["default"])(_context = ast.path).call(_context, 1) : ast.path; var tagPath = (0, _concat["default"])(_context2 = (0, _concat["default"])(_context3 = "".concat(tag)).call(_context3, relativeTagPath[0] === '[' ? '' : relativeTagPath[0] ? '.' : '')).call(_context2, relativeTagPath === '$' ? '' : relativeTagPath); // Path rewrite var value; if (F.isEmptyValue(ctx)) { value = ast.source; sources['@@next'] = sources['@@next'] || []; var job = { type: '@@tag', path: jp.stringify(context.path), tag: tag, source: ast.source, templatePath: ast.path, tagPath: tagPath }; sources['@@next'].unshift(job); } else { // value = JSON.stringify({ ctx, path: ast.path, value: jp.value(tags, jpify(ast.path))}, null, 0); value = jp.value(tags, jpify(tagPath)) || ctx; } ast.value = value; return F.reduced(_objectSpread(_objectSpread({}, ast), {}, { from: sources['tags'] })); }; } }; var _ast$operators$symbol = ast.operators.symbol, operator = _ast$operators$symbol.operator, tag = _ast$operators$symbol.tag; var result = ops[operator](ast)(sources, tag); return _objectSpread(_objectSpread({}, result), {}, { '@meta': meta }); }; }; var symbolOperator = function symbolOperator(_ref10) { var tags = _ref10.tags, context = _ref10.context, sources = _ref10.sources, stages = _ref10.stages; return F.composes(symbol({ tags: tags, context: context, sources: sources, stages: stages }), bins.has('$.operators.symbol')); }; var enumerate = function enumerate(ast) { var _ref11 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref11$meta = _ref11.meta, meta = _ref11$meta === void 0 ? 4 : _ref11$meta; var ops = { '*': function _(ast) { return _objectSpread(_objectSpread({}, ast), {}, { value: (0, _toConsumableArray2["default"])(F.iterator(ast.value)) }); }, // no-op on arrays, enumerates object values in Object.keys order '**': function _(ast) { return _objectSpread(_objectSpread({}, ast), {}, { value: (0, _toConsumableArray2["default"])(F.iterator(ast.value, { indexed: true, kv: true })) }); } // TODO: do scenarios of ** python style k/v pairs expansion fit with jsonpath? }; var _ast$operators$enumer = ast.operators.enumeration, operator = _ast$operators$enumer.operator, repeat = (0, _repeat["default"])(_ast$operators$enumer); var result = ops[repeat === 0 ? operator : operator + operator](ast); return _objectSpread(_objectSpread({}, result), {}, { '@meta': meta }); }; var enumerateOperator = F.composes(enumerate, bins.has('$.operators.enumeration')); var parseTextArgs = function parseTextArgs() { var parseNumeric = function parseNumeric(text) { var isIntText = /^\d+$/; var isFloatText = /^\d+\.\d+$/; if (isFloatText.test(text)) { return (0, _parseFloat2["default"])(text, 10); } else if (isIntText.test(text)) { return (0, _parseInt2["default"])(text, 10); } else { return text; } }; var literals = { "true": true, "false": false, "null": null, undefined: undefined, __: F.__ }; var parseText = function parseText(text) { return text in literals ? literals[text] : parseNumeric(text); }; // When regex or parser allows for foo:[1, 2, 3], add: || JSON.parse(text); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return (0, _map["default"])(F).call(F, parseText, args); }; var normalizeArgs = function normalizeArgs(_ref12) { var functions = _ref12.functions, args = _ref12.args; return function (_ref13, data) { var _ref14 = (0, _slicedToArray2["default"])(_ref13, 3), fnPath = _ref14[0], fnKey = _ref14[1], fnName = _ref14[2]; var fnArgs = args[fnPath] || args[fnKey] || args[fnName]; if (fnArgs === undefined) return []; var fnArgList = F.isArray(fnArgs) ? fnArgs : [fnArgs]; var argList = (0, _map["default"])(F).call(F, function (arg) { return arg.path ? jp.value(data, arg.path) : arg.value !== undefined ? arg.value : arg; }, fnArgList); return argList; }; }; var pipe = function pipe(_ref15) { var functions = _ref15.functions, extendedArgs = _ref15.args, sources = _ref15.sources, context = _ref15.context; return function (ast) { var _ref16 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref16$meta = _ref16.meta, meta = _ref16$meta === void 0 ? 5 : _ref16$meta; var pipes = ast.pipes; if (pipes.length === 0) return ast; // ordered [['$1', 'toInt:arg1:arg2'], ['$2', 'isEven:arg1:arg2']] var fnPipeline = (0, _map["default"])(F).call(F, function (_ref17) { var functionName = _ref17["function"], _ref17$type = _ref17.type, type = _ref17$type === void 0 ? 'inline' : _ref17$type, _ref17$args = _ref17.args, args = _ref17$args === void 0 ? [] : _ref17$args; // eslint-disable-next-line prefer-const var enrichedFunctions = _objectSpread(_objectSpread({}, functions), {}, { '*': bins.flatten, '**': bins.doubleFlatten }); if (!(functionName in enrichedFunctions)) { throw new Error("could not resolve function name [".concat(functionName, "]")); // @TODO: Alternatives to throwing inside a mapping!!!! } var fn = enrichedFunctions[functionName]; if (type === 'extended') { var _context4; var fnArgKeys = ["$.".concat(context.path.join('.')), (0, _slice["default"])(_context4 = context.path).call(_context4, -1).pop(), functionName]; var argList = normalizeArgs({ functions: functions, args: extendedArgs })(fnArgKeys, sources['origin']); // sources['origin'] === document return fn.apply(void 0, (0, _toConsumableArray2["default"])(argList)); // {{} | @foo } here foo is a higher order function, (...extendedArgs) => nodeValue => {} } else { /* * A function accepting an argument should return a function of arity one that receives the value rendered * example: take(n)(data), parseInt(base)(data), etc ... */ /** * For functions of arity > 1, the engine supports one slot (only) syntax @TODO: support multiple slots * example: equals:100:__ * */ var phIndex = (0, _indexOf["default"])(args).call(args, '__'); if (phIndex >= 0) { // args[phIndex] = F.__; fn = F.oneslot(fn).apply(void 0, (0, _toConsumableArray2["default"])(parseTextArgs.apply(void 0, (0, _toConsumableArray2["default"])(args)))); // placeholder functions are normal functions, since renderedValue is passed into placeholder position with F.oneslot, which already creates a higher order function return fn; } else if (args.length === 0) { return fn; // no args functions are normal functions that receive the renderedValue } else { var fn2 = fn.apply(void 0, (0, _toConsumableArray2["default"])(parseTextArgs.apply(void 0, (0, _toConsumableArray2["default"])(args)))); return F.isFunction(fn2) ? fn2 : F.lazy(fn2); } } }, pipes); return _objectSpread(_objectSpread({}, ast), {}, { '@meta': meta, value: F.pipe.apply(F, (0, _toConsumableArray2["default"])(fnPipeline))(ast.value) }); // we would love to unleash pipes (short circuit pipe), but current implementation would unreduce value reduced by functions. @TODO revisit later }; }; var pipeOperator = function pipeOperator(_ref18) { var functions = _ref18.functions, args = _ref18.args, sources = _ref18.sources, context = _ref18.context; return F.composes(pipe({ functions: functions, args: args, sources: sources, context: context }), bins.has('$.pipes')); }; /** * op = [ .+ | .N | >+ | >N | %+ | %N ] * .. : lens composition inception * >> : for each child, apply transform with leader node * %% : zip transform, positional template from leader node renders child template at the same position * @param ast * @returns {{operator, repeat: *}} */ var inception = function inception(options) { return function (ast, enumerable) { var _ref19 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, _ref19$meta = _ref19.meta, meta = _ref19$meta === void 0 ? 5 : _ref19$meta; var ops = { /** * Renders node n in current scope, render n+1 using n as scoped-document, effectively recurring into smaller scopes * @param ast * @param enumerable * @param options */ '.': function _(ast, enumerable, options) { var _enumerable = (0, _slicedToArray2["default"])(enumerable, 1), inceptionNode = _enumerable[0]; var _require = require('../transform'), transform = _require.transform; // lazy require to break cyclic dependency var scopedDocument = transform(inceptionNode, options)(options.sources.origin); return [(0, _reduce["default"])(F).call(F, function (rendered, nestedTemplate) { return transform(nestedTemplate, options)(rendered); }, function () { return scopedDocument; }, enumerable)]; }, /** * Renders the leader node, use the rendered value as a scoped-document to render the rest of the enumerable as templates * @param ast * @param enumerable * @param options */ '>': function _(ast, enumerable, options) { var _enumerable2 = (0, _slicedToArray2["default"])(enumerable, 1), inceptionNode = _enumerable2[0]; var _require2 = require('../transform'), transform = _require2.transform; // lazy require to break cyclic dependency var scopedDocument = transform(inceptionNode, options)(options.sources.origin); return (0, _map["default"])(F).call(F, function (item) { return transform(item, options)(scopedDocument); }, enumerable); }, /** * Renders the leader node, which yields an array of documents, zip/render the array of templates aligning document(n) with template(n) * @param ast * @param enumerable * @param options */ '%': function _(ast, enumerable, options) { var _enumerable3 = (0, _slicedToArray2["default"])(enumerable, 1), inceptionNode = _enumerable3[0]; var _require3 = require('../transform'), transform = _require3.transform; // lazy require to break cyclic dependency var scopedDocument = transform(inceptionNode, options)(options.sources.origin); if (!F.isArray(scopedDocument)) throw new Error('Inception Operator [%] should be used for template nodes yielding an array'); var rest = (0, _toConsumableArray2["default"])(enumerable); if (rest.length === 1) { // no zip align, apply the rest-template for-each value in document return (0, _map["default"])(F).call(F, function (documentItem) { return transform(rest[0], options)(documentItem); }, scopedDocument); } else { // zip-align var pairsIter = F.zip(rest, scopedDocument); return (0, _map["default"])(F).call(F, function (_ref20) { var _ref21 = (0, _slicedToArray2["default"])(_ref20, 2), template = _ref21[0], document = _ref21[1]; return transform(template, options)(document); }, pairsIter); } } }; var operator = ast.operator, repeat = (0, _repeat["default"])(ast); var opFn = ops[operator]; var result = opFn(ast, enumerable, options); return result; // enumerable }; }; var inceptionPreprocessor = function inceptionPreprocessor(ast) { // eslint-disable-next-line prefer-const var _ast$operators$incept = ast.operators.inception, operator = _ast$operators$incept.operator, repeat = (0, _repeat["default"])(_ast$operators$incept); repeat = repeat === '*' ? Number.POSITIVE_INFINITY : repeat; return _objectSpread(_objectSpread({}, ast), {}, { operator: operator, $depth: repeat }); }; var applyAll = function applyAll(_ref22) { var meta = _ref22.meta, sources = _ref22.sources, tags = _ref22.tags, functions = _ref22.functions, args = _ref22.args, context = _ref22.context, config = _ref22.config, stages = _ref22.stages; return F.composes(pipeOperator({ functions: functions, args: args, sources: sources, context: context }), enumerateOperator, symbolOperator({ tags: tags, context: context, sources: sources, stages: stages }), constraintOperator({ sources: sources, config: config }), query, deref(sources)); }; module.exports = { normalizeArgs: normalizeArgs, regex: regex, jpify: jpify, deref: deref, query: query, constraint: constraintOperator, symbol: symbolOperator, enumerate: enumerateOperator, inceptionPreprocessor: inceptionPreprocessor, inception: inception, pipe: pipeOperator, applyAll: applyAll, sortBy: sortBy };