From 9dd9d5c328da8ad1d90cd33d4a13efac92104398 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 12 Sep 2023 14:34:20 -0300 Subject: data: more syntax WIP --- src/data/things/composite.js | 65 ++--- src/data/things/thing.js | 591 +++++++++++++++++++++++++++---------------- 2 files changed, 409 insertions(+), 247 deletions(-) diff --git a/src/data/things/composite.js b/src/data/things/composite.js index 26124b5..32a6103 100644 --- a/src/data/things/composite.js +++ b/src/data/things/composite.js @@ -344,7 +344,9 @@ import { const globalCompositeCache = {}; -export function compositeFrom(firstArg, secondArg) { +export function compositeFrom(description) { + const {annotation, steps: composition} = description; + const debug = fn => { if (compositeFrom.debug === true) { const label = @@ -363,13 +365,6 @@ export function compositeFrom(firstArg, secondArg) { } }; - let annotation, composition; - if (typeof firstArg === 'string') { - [annotation, composition] = [firstArg, secondArg]; - } else { - [annotation, composition] = [null, firstArg]; - } - const base = composition.at(-1); const steps = composition.slice(); @@ -974,6 +969,7 @@ export function exposeConstant({ export function withResultOfAvailabilityCheck({ fromUpdateValue, fromDependency, + modeDependency, mode = 'null', into = '#availability', }) { @@ -1026,31 +1022,40 @@ export function withResultOfAvailabilityCheck({ // Exposes a dependency as it is, or continues if it's unavailable. // See withResultOfAvailabilityCheck for {mode} options! -export function exposeDependencyOrContinue({ - dependency, - mode = 'null', -}) { - return compositeFrom(`exposeDependencyOrContinue`, [ - withResultOfAvailabilityCheck({ - fromDependency: dependency, - mode, - }), +export const exposeDependencyOrContinue = + templateCompositeFrom({ + annotation: `exposeDependencyOrContinue`, - { - dependencies: ['#availability'], - compute: ({'#availability': availability}, continuation) => - (availability - ? continuation() - : continuation.raise()), + inputs: { + dependency: input(), + mode: input.default('null'), }, - { - mapDependencies: {dependency}, - compute: ({dependency}, continuation) => - continuation.exit(dependency), - }, - ]); -} + steps: () => [ + withResultOfAvailabilityCheck({ + from: input('dependency'), + mode: input('mode'), + }), + + { + dependencies: ['#availability'], + compute: (continuation, { + ['#availability']: availability, + }) => + (availability + ? continuation() + : continuation.raise()), + }, + + { + dependencies: [input('#dependency')], + compute: (continuation, { + [input('#dependency')]: dependency, + }) => + continuation.exit(dependency), + }, + ], + }); // Exposes the update value of an {update: true} property as it is, // or continues if it's unavailable. See withResultOfAvailabilityCheck diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 19954b1..5cfeaeb 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -203,15 +203,18 @@ export function externalFunction() { // also existing on this object! // export function contributionList() { - return compositeFrom(`contributionList`, [ - withUpdateValueAsDependency(), - withResolvedContribs({from: '#updateValue'}), - exposeDependencyOrContinue({dependency: '#resolvedContribs'}), - exposeConstant({ - value: [], - update: {validate: isContributionList}, - }), - ]); + return compositeFrom({ + annotation: `contributionList`, + + update: {validate: isContributionList}, + + steps: [ + withUpdateValueAsDependency(), + withResolvedContribs({from: '#updateValue'}), + exposeDependencyOrContinue({dependency: '#resolvedContribs'}), + exposeConstant({value: []}), + ], + }); } // Artist commentary! Generally present on tracks and albums. @@ -259,7 +262,7 @@ export function additionalFiles() { export function referenceList({ class: thingClass, data, - find, + find: findFunction, }) { if (!thingClass) { throw new TypeError(`Expected a Thing class`); @@ -270,29 +273,40 @@ export function referenceList({ throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); } - return compositeFrom(`referenceList`, [ - withUpdateValueAsDependency(), + return compositeFrom({ + annotation: `referenceList`, - withResolvedReferenceList({ - data, find, - list: '#updateValue', - notFoundMode: 'filter', - }), + update: { + validate: validateReferenceList(referenceType), + }, - exposeDependency({ - dependency: '#resolvedReferenceList', - update: { - validate: validateReferenceList(referenceType), - }, - }), - ]); + mapDependencies: { + '#composition.data': data, + }, + + constantDependencies: { + '#composition.findFunction': findFunction, + }, + + steps: [ + withUpdateValueAsDependency(), + + withResolvedReferenceList({ + list: '#updateValue', + data: '#composition.data', + find: '#composition.findFunction', + }), + + exposeDependency({dependency: '#resolvedReferenceList'}), + ], + }); } // Corresponding function for a single reference. export function singleReference({ class: thingClass, data, - find, + find: findFunction, }) { if (!thingClass) { throw new TypeError(`Expected a Thing class`); @@ -303,27 +317,56 @@ export function singleReference({ throw new Error(`The passed constructor ${thingClass.name} doesn't define Thing.referenceType!`); } - return compositeFrom(`singleReference`, [ - withUpdateValueAsDependency(), + return compositeFrom({ + annotation: `singleReference`, - withResolvedReference({ref: '#updateValue', data, find}), + update: { + validate: validateReference(referenceType), + }, - exposeDependency({ - dependency: '#resolvedReference', - update: { - validate: validateReference(referenceType), - }, - }), - ]); + mapDependencies: { + '#composition.data': data, + }, + + constantDependencies: { + '#composition.findFunction': findFunction, + }, + + steps: [ + withUpdateValueAsDependency(), + + withResolvedReference({ + ref: '#updateValue', + data: '#composition.data', + find: '#composition.findFunction', + }), + + exposeDependency({dependency: '#resolvedReference'}), + ], + }); } // Nice 'n simple shorthand for an exposed-only flag which is true when any // contributions are present in the specified property. -export function contribsPresent({contribs}) { - return compositeFrom(`contribsPresent`, [ - withResultOfAvailabilityCheck({fromDependency: contribs, mode: 'empty'}), - exposeDependency({dependency: '#availability'}), - ]); +export function contribsPresent({ + contribs, +}) { + return compositeFrom({ + annotation: `contribsPresent`, + + mapDependencies: { + '#composition.contribs': contribs, + }, + + steps: [ + withResultOfAvailabilityCheck({ + fromDependency: '#composition.contribs', + mode: 'empty', + }), + + exposeDependency({dependency: '#availability'}), + ], + }); } // Neat little shortcut for "reversing" the reference lists stored on other @@ -332,10 +375,23 @@ export function contribsPresent({contribs}) { // property. Naturally, the passed ref list property is of the things in the // wiki data provided, not the requesting Thing itself. export function reverseReferenceList({data, list}) { - return compositeFrom(`reverseReferenceList`, [ - withReverseReferenceList({data, list}), - exposeDependency({dependency: '#reverseReferenceList'}), - ]); + return compositeFrom({ + annotation: `reverseReferenceList`, + + mapDependencies: { + '#composition.data': data, + '#composition.list': list, + }, + + steps: [ + withReverseReferenceList({ + data: '#composition.data', + list: '#composition.list', + }), + + exposeDependency({dependency: '#reverseReferenceList'}), + ], + }); } // General purpose wiki data constructor, for properties like artistData, @@ -353,39 +409,51 @@ export function wikiData(thingClass) { // commentary content, and finds the matching artist for each reference. // This is mostly useful for credits and listings on artist pages. export function commentatorArtists() { - return compositeFrom(`commentatorArtists`, [ - exitWithoutDependency({dependency: 'commentary', mode: 'falsy', value: []}), - - { - dependencies: ['commentary'], - compute: ({commentary}, continuation) => - continuation({ - '#artistRefs': - Array.from( - commentary - .replace(/<\/?b>/g, '') - .matchAll(/(?.*?):<\/i>/g)) - .map(({groups: {who}}) => who), - }), + return compositeFrom({ + annotation: `commentatorArtists`, + + constantDependencies: { + '#composition.findFunction': find.artists, }, - withResolvedReferenceList({ - list: '#artistRefs', - data: 'artistData', - into: '#artists', - find: find.artist, - }), + steps: [ + exitWithoutDependency({ + dependency: 'commentary', + mode: 'falsy', + value: [], + }), - { - flags: {expose: true}, + { + dependencies: ['commentary'], + compute: ({commentary}, continuation) => + continuation({ + '#artistRefs': + Array.from( + commentary + .replace(/<\/?b>/g, '') + .matchAll(/(?.*?):<\/i>/g)) + .map(({groups: {who}}) => who), + }), + }, - expose: { - dependencies: ['#artists'], - compute: ({'#artists': artists}) => - unique(artists), + withResolvedReferenceList({ + list: '#artistRefs', + data: 'artistData', + into: '#artists', + find: '#composition.findFunction', + }), + + { + flags: {expose: true}, + + expose: { + dependencies: ['#artists'], + compute: ({'#artists': artists}) => + unique(artists), + }, }, - }, - ]); + ], + }); } // Compositional utilities @@ -398,39 +466,54 @@ export function withResolvedContribs({ from, into = '#resolvedContribs', }) { - return compositeFrom(`withResolvedContribs`, [ - raiseWithoutDependency({ - dependency: from, - mode: 'empty', - map: {into}, - raise: {into: []}, - }), - - withPropertiesFromList({ - list: from, - properties: ['who', 'what'], - prefix: '#contribs', - }), - - withResolvedReferenceList({ - list: '#contribs.who', - data: 'artistData', - into: '#contribs.who', - find: find.artist, - notFoundMode: 'null', - }), - - { - dependencies: ['#contribs.who', '#contribs.what'], - mapContinuation: {into}, - compute({'#contribs.who': who, '#contribs.what': what}, continuation) { - filterMultipleArrays(who, what, (who, _what) => who); - return continuation({ - into: stitchArrays({who, what}), - }); - }, + return compositeFrom({ + annotation: `withResolvedContribs`, + + mapDependencies: { + '#composition.from': from, }, - ]); + + mapContinuation: { + '#composition.into': into, + }, + + constantDependencies: { + '#composition.findFunction': find.artist, + '#composition.notFoundMode': 'null', + }, + + steps: [ + raiseWithoutDependency({ + dependency: '#composition.from', + raise: {'#composition.into': []}, + mode: 'empty', + }), + + withPropertiesFromList({ + list: '#composition.from', + prefix: '#contribs', + properties: ['who', 'what'], + }), + + withResolvedReferenceList({ + list: '#contribs.who', + data: 'artistData', + into: '#contribs.who', + find: '#composition.findFunction', + notFoundMode: '#composition.notFoundMode', + }), + + { + dependencies: ['#contribs.who', '#contribs.what'], + compute({'#contribs.who': who, '#contribs.what': what}, continuation) { + filterMultipleArrays(who, what, (who, _what) => who); + return continuation({ + '#composition.into': stitchArrays({who, what}), + }); + }, + }, + ], + }); } // Shorthand for exiting if the contribution list (usually a property's update @@ -440,14 +523,37 @@ export function exitWithoutContribs({ contribs, value = null, }) { - return compositeFrom(`exitWithoutContribs`, [ - withResolvedContribs({from: contribs}), - exitWithoutDependency({ - dependency: '#resolvedContribs', - mode: 'empty', - value, - }), - ]); + return compositeFrom({ + annotation: `exitWithoutContribs`, + + mapDependencies: { + '#composition.contribs': contribs, + }, + + constantDependencies: { + '#composition.value': value, + }, + + steps: [ + withResolvedContribs({from: '#composition.contribs'}), + + withResultOfAvailabilityCheck({ + fromDependency: '#resolvedContribs', + mode: 'empty', + }), + + { + dependencies: ['#availability', '#composition.value'], + compute: ({ + '#availability': availability, + '#composition.value': value, + }, continuation) => + (availability + ? continuation() + : continuation.exit(value)), + }, + ], + }); } // Resolves a reference by using the provided find function to match it @@ -461,39 +567,54 @@ export function withResolvedReference({ data, find: findFunction, into = '#resolvedReference', - notFoundMode = 'null', + notFoundMode, }) { - if (!['exit', 'null'].includes(notFoundMode)) { - throw new TypeError(`Expected notFoundMode to be exit or null`); - } - - return compositeFrom(`withResolvedReference`, [ - raiseWithoutDependency({ - dependency: ref, - map: {into}, - raise: {into: null}, - }), - - exitWithoutDependency({ - dependency: data, - }), - - { - options: {findFunction, notFoundMode}, - mapDependencies: {ref, data}, - mapContinuation: {match: into}, + return compositeFrom({ + annotation: `withResolvedReference`, + + mapDependencies: { + '#composition.ref': ref, + '#composition.data': data, + '#composition.findFunction': findFunction, + '#composition.notFoundMode': notFoundMode, + }, - compute({ref, data, '#options': {findFunction, notFoundMode}}, continuation) { - const match = findFunction(ref, data, {mode: 'quiet'}); + constantDependencies: { + '#composition.notFoundMode': 'null', + }, - if (match === null && notFoundMode === 'exit') { - return continuation.exit(null); - } + mapContinuation: { + '#composition.into': into, + }, - return continuation.raise({match}); + steps: [ + raiseWithoutDependency({ + dependency: '#composition.ref', + raise: {'#composition.into': null}, + }), + + exitWithoutDependency({ + dependency: '#composition.data', + }), + + { + compute({ + '#composition.ref': ref, + '#composition.data': data, + '#composition.findFunction': findFunction, + '#composition.notFoundMode': notFoundMode, + }, continuation) { + const match = findFunction(ref, data, {mode: 'quiet'}); + + if (match === null && notFoundMode === 'exit') { + return continuation.exit(null); + } + + return continuation.raise({match}); + }, }, - }, - ]); + ], + }); } // Resolves a list of references, with each reference matched with provided @@ -505,79 +626,93 @@ export function withResolvedReferenceList({ list, data, find: findFunction, + notFoundMode, into = '#resolvedReferenceList', - notFoundMode = 'filter', }) { if (!['filter', 'exit', 'null'].includes(notFoundMode)) { throw new TypeError(`Expected notFoundMode to be filter, exit, or null`); } - const composite = compositeFrom(`withResolvedReferenceList`, [ - exitWithoutDependency({ - dependency: data, - value: [], - }), - - raiseWithoutDependency({ - dependency: list, - mode: 'empty', - map: {into}, - raise: {into: []}, - }), - - { - cache: 'aggressive', - annotation: `withResolvedReferenceList.getMatches`, - flags: {expose: true, compose: true}, - - compute: { - mapDependencies: {list, data}, - options: {findFunction}, - - compute: ({list, data, '#options': {findFunction}}, continuation) => - continuation({ - '#matches': list.map(ref => findFunction(ref, data, {mode: 'quiet'})), - }), - }, + return compositeFrom({ + annotation: `withResolvedReferenceList`, + + mapDependencies: { + '#composition.list': list, + '#composition.data': data, + '#composition.findFunction': findFunction, + '#composition.notFoundMode': notFoundMode, }, - { - dependencies: ['#matches'], - mapContinuation: {into}, + constantDependencies: { + '#composition.notFoundMode': 'filter', + }, - compute: ({'#matches': matches}, continuation) => - (matches.every(match => match) - ? continuation.raise({into: matches}) - : continuation()), + mapContinuation: { + '#composition.into': into, }, - { - dependencies: ['#matches'], - options: {notFoundMode}, - mapContinuation: {into}, - - compute({ - '#matches': matches, - '#options': {notFoundMode}, - }, continuation) { - switch (notFoundMode) { - case 'exit': - return continuation.exit([]); - - case 'filter': - matches = matches.filter(match => match); - return continuation.raise({into: matches}); - - case 'null': - matches = matches.map(match => match ?? null); - return continuation.raise({into: matches}); - } + steps: [ + exitWithoutDependency({ + dependency: '#composition.data', + value: [], + }), + + raiseWithoutDependency({ + dependency: '#composition.list', + raise: {'#composition.into': []}, + mode: 'empty', + }), + + { + dependencies: [ + '#composition.list', + '#composition.data', + '#composition.findFunction', + ], + + compute: ({ + '#composition.list': list, + '#composition.data': data, + '#composition.findFunction': findFunction, + }, continuation) => + continuation({ + '#matches': list.map(ref => findFunction(ref, data, {mode: 'quiet'})), + }), + }, + + { + dependencies: ['#matches'], + compute: ({'#matches': matches}, continuation) => + (matches.every(match => match) + ? continuation.raise({'#continuation.into': matches}) + : continuation()), }, - }, - ]); - console.log(composite.expose); - return composite; + { + dependencies: ['#matches', '#composition.notFoundMode'], + compute({ + '#matches': matches, + '#composition.notFoundMode': notFoundMode, + }, continuation) { + switch (notFoundMode) { + case 'exit': + return continuation.exit([]); + + case 'filter': + matches = matches.filter(match => match); + return continuation.raise({'#continuation.into': matches}); + + case 'null': + matches = matches.map(match => match ?? null); + return continuation.raise({'#continuation.into': matches}); + + default: + throw new TypeError(`Expected notFoundMode to be exit, filter, or null`); + } + }, + }, + ], + }); } // Check out the info on reverseReferenceList! @@ -587,22 +722,44 @@ export function withReverseReferenceList({ list: refListProperty, into = '#reverseReferenceList', }) { - return compositeFrom(`withReverseReferenceList`, [ - exitWithoutDependency({ - dependency: data, - value: [], - }), - - { - dependencies: ['this'], - mapDependencies: {data}, - mapContinuation: {into}, - options: {refListProperty}, - - compute: ({this: thisThing, data, '#options': {refListProperty}}, continuation) => - continuation({ - into: data.filter(thing => thing[refListProperty].includes(thisThing)), - }), + return compositeFrom({ + annotation: `withReverseReferenceList`, + + mapDependencies: { + '#composition.data': data, + }, + + constantDependencies: { + '#composition.refListProperty': refListProperty, + }, + + mapContinuation: { + '#composition.into': into, }, - ]); + + steps: [ + exitWithoutDependency({ + dependency: '#composition.data', + value: [], + }), + + { + dependencies: [ + 'this', + '#composition.data', + '#composition.refListProperty', + ], + + compute: ({ + this: thisThing, + '#composition.data': data, + '#composition.refListProperty': refListProperty, + }, continuation) => + continuation({ + '#composition.into': + data.filter(thing => thing[refListProperty].includes(thisThing)), + }), + }, + ], + }); } -- cgit 1.3.0-6-gf8a5