UNPKG

tpmkms

Version:

Reusable libraries for Entodicton, a natural language to json converter

441 lines (394 loc) 10.2 kB
const pluralize = require('pluralize') const { flatten } = require('./runtime').theprogrammablemind // check if member function of a class have been overriden by an subclass class OverrideCheck { constructor(base, checks) { this.base = base this.checks = checks } check(obj) { for (const check of this.checks) { if (obj[check] == this.base.prototype[check]) { throw new Error(`For ${obj.constructor.name} you need to override ${check}`) } } } } function unshiftL(list, element, max) { if (list.length >= max) { list.pop() } list.unshift(element) } function pushL(list, element, max) { if (list.length >= max) { list.shift() } list.push(element) } // X pm today or tomorrow function millisecondsUntilHourOfDay(newDate, hour) { const now = newDate() const target = newDate(now) function addHours(date, h) { date.setTime(date.getTime() + (h*60*60*1000)); } const hours = target.getHours() if (hours == hour) { return 0 } if (hours > hour) { addHours(target, 24) } target.setHours(hour, 0, 0, 0) var diff = Math.abs(target - now); return diff; } function indent(string, indent) { return string.replace(/^/gm, ' '.repeat(indent)); } function getCount(context) { if (context.quantity) { return context.quantity.value } if (context.determined) { return 1 } } function words(word, additional = {}) { return [{ word: pluralize.singular(word), number: 'one', ...additional }, { word: pluralize.plural(word), number: 'many', ...additional }] } function isMany(context) { if (!context) { return } // if (((context || {}).value || {}).marker == 'list' && (((context || {}).value || {}).value || []).length > 1) { const isList = context.marker == 'list' || context.value?.marker == 'list' if (isList) { if (context.value?.length > 1) { return true } if (context.value?.value?.length > 1) { return true } } let number = context.number if (context.quantity) { if (context.quantity.number) { number = context.quantity.number } else if (context.quantity.value !== 1) { number = 'many' } } if (number == 'many') { return true } if (number == 'one') { return false } if (context.word && pluralize.isPlural(context.word)) { return true } return false } function requiredArgument(value, name) { if (!value) { throw new Error(`${name} is a required argument`) } } function chooseNumber(context, one, many) { if (isMany(context)) { return many; } else { return one; } } function zip(...arrays) { if (arrays == []) { return [] } const zipped = [] for(let i = 0; i < arrays[0].length; i++){ const tuple = [] for (const array of arrays) { tuple.push(array[i]) } zipped.push(tuple) } return zipped } function focus(context) { function helper(context) { if (!context || !context.focusable) { return null } for (const property of context.focusable) { let focus = helper(context[property]) if (!focus) { const flat = flatten(['list'], context[property])[0] for (const element of flat) { if (element.focus) { focus = context[property] break } } } return focus } return null } return helper(context) || context } // if property is a list make array of elements of the list, if not return an array with the property value // fromList function propertyToArray(value) { if (Array.isArray(value)) { return value; } if (value?.listable || value?.marker == 'list') { return value.value.flatMap(item => propertyToArray(item)); } // Single value → wrap in array return [value]; } // values is marker: 'list' or some context function concats(values) { combined = [] for (const value of values) { if (value.marker == 'list') { combined = combined.concat(value.value) } else { combined.push(value) } } return { marker: 'list', listable: true, value: combined } } function wordNumber(word, toPlural) { if (toPlural) { return pluralize.plural(word) } else { return pluralize.singular(word) } } function toEValue(context) { while( context.evalue ) { context = context.evalue } return context; } function toFinalValue(context) { while( context.evalue || context.value ) { if (context.evalue) { context = context.evalue } else { context = context.value } } return context; } function defaultObjectCheck(extra = []) { return { objects: [ { extra, match: ({objects}) => true, apply: () => extra }, ], } } function defaultContextCheckProperties(extra = []) { return ['marker', 'text', 'verbatim', 'value', 'evalue', 'isResponse', { properties: 'modifiers' }, { properties: 'postModifiers' }, ...extra] } function defaultContextCheck({marker, extra = [], exported = false} = {}) { let match if (marker) { match = ({context}) => context.marker == marker } else { match = () => true } return { marker, match, exported, apply: () => ['marker', 'text', 'verbatim', 'value', 'evalue', 'isResponse', { properties: 'modifiers' }, { properties: 'postModifiers' }, ...extra], } } function isA(hierarchy) { return (child, parent, { strict=false } = {}) => { if (!child || !parent) { return false } if (strict) { if (child.marker) { child = child.marker } if (parent.marker) { parent = parent.marker } return hierarchy.isA(child, parent) } else { if (hierarchy.isA(child.marker || child, parent.marker || parent)) { return true } for (const childT of child.types || [child]) { for (const parentT of parent.types || [parent]) { if (hierarchy.isA(childT, parentT)) { return true } } } return false } } } function getValue(propertyPath, object) { if (!propertyPath) { return } let path = propertyPath if (typeof path == 'string') { path = propertyPath.split('.') } let value = object for (const name of path) { if (!value) { break } value = value[name] } return value } function setValue(propertyPath, object, newValue) { if (!propertyPath) { return; } let path = propertyPath; if (typeof path === 'string') { path = propertyPath.split('.'); } let current = object; for (let i = 0; i < path.length; i++) { const name = path[i]; if (i === path.length - 1) { // Set the value at the final step current[name] = newValue; break; } if (current[name] === undefined || current[name] === null) { current[name] = {}; // Create a plain object for nesting } current = current[name]; } } async function processTemplateString(template, evaluate) { async function resolveWithCallback(strings, ...keys) { // const resolvedValues = await Promise.all(keys.map(key => lookupVariable(key))); const resolvedValues = await Promise.all(keys.map(async (key) => { return await evaluate(key) })) let result = strings[0]; for (let i = 0; i < resolvedValues.length; i++) { result += resolvedValues[i] + strings[(i + 1)*2]; } return result; } async function processTemplateString(template) { // Split the template into strings and keys const parts = template.split(/(\${[^}]+})/g); const strings = []; const keys = []; for (const part of parts) { if (part.startsWith("${") && part.endsWith("}")) { keys.push(part.slice(2, -1)); // Extract key (e.g., "name" from "${name}") strings.push(""); // Placeholder for interpolation } else { strings.push(part); } } // Ensure the strings array has one more element than keys if (strings.length === keys.length) { strings.push(""); } // Pass to the tagged template function return resolveWithCallback(strings, ...keys); } return await processTemplateString(template) } // removeProp.js function removeProp(obj, testFn, { maxDepth = Infinity, seen = new WeakSet() } = {}) { if (!obj || typeof obj !== 'object' || maxDepth <= 0) return obj; if (seen.has(obj)) return obj; seen.add(obj); if (Array.isArray(obj)) { // ---- ARRAY: process each element (but don't remove elements unless testFn says so) let writeIdx = 0; for (let i = 0; i < obj.length; i++) { const element = obj[i]; const shouldRemoveElement = testFn(element, i, obj); if (shouldRemoveElement) { // Remove the whole array element if (element && typeof element === 'object') { // Still walk inside it in case testFn wants side effects removeProp(element, testFn, { maxDepth: maxDepth - 1, seen }); } // Skip writing it back } else { // Keep element, but walk into it to remove inner props removeProp(element, testFn, { maxDepth: maxDepth - 1, seen }); if (writeIdx !== i) { obj[writeIdx] = element; } writeIdx++; } } obj.length = writeIdx; return obj; } // ---- OBJECT: iterate over own keys const keys = Reflect.ownKeys(obj); for (const key of keys) { const val = obj[key]; const shouldRemove = testFn(val, key, obj); if (shouldRemove) { delete obj[key]; if (val && typeof val === 'object') { removeProp(val, testFn, { maxDepth: maxDepth - 1, seen }); } } else { removeProp(val, testFn, { maxDepth: maxDepth - 1, seen }); } } return obj; } module.exports = { processTemplateString, unshiftL, pushL, getValue, setValue, defaultContextCheck, defaultContextCheckProperties, defaultObjectCheck, toEValue, toFinalValue, millisecondsUntilHourOfDay, indent, isMany, getCount, chooseNumber, zip, focus, words, propertyToArray, wordNumber, requiredArgument, isA, removeProp, concats, OverrideCheck, }