json-tots
Version:
Template of Templates, a.k.a Template Should Eat Itself
574 lines (565 loc) • 24.1 kB
JavaScript
;
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
};