diff options
Diffstat (limited to 'src/data/composite')
82 files changed, 2467 insertions, 1063 deletions
diff --git a/src/data/composite/control-flow/flipFilter.js b/src/data/composite/control-flow/flipFilter.js new file mode 100644 index 00000000..995bacad --- /dev/null +++ b/src/data/composite/control-flow/flipFilter.js @@ -0,0 +1,36 @@ +// Flips a filter, so that each true item becomes false, and vice versa. +// Overwrites the provided dependency. +// +// See also: +// - withAvailabilityFilter + +import {input, templateCompositeFrom} from '#composite'; + +export default templateCompositeFrom({ + annotation: `flipFilter`, + + inputs: { + filter: input({type: 'array'}), + }, + + outputs: ({ + [input.staticDependency('filter')]: filterDependency, + }) => [filterDependency ?? '#flippedFilter'], + + steps: () => [ + { + dependencies: [ + input('filter'), + input.staticDependency('filter'), + ], + + compute: (continuation, { + [input('filter')]: filter, + [input.staticDependency('filter')]: filterDependency, + }) => continuation({ + [filterDependency ?? '#flippedFilter']: + filter.map(item => !item), + }), + }, + ], +}); diff --git a/src/data/composite/control-flow/index.js b/src/data/composite/control-flow/index.js index 7e137a14..778dc66b 100644 --- a/src/data/composite/control-flow/index.js +++ b/src/data/composite/control-flow/index.js @@ -10,6 +10,7 @@ export {default as exposeDependency} from './exposeDependency.js'; export {default as exposeDependencyOrContinue} from './exposeDependencyOrContinue.js'; export {default as exposeUpdateValueOrContinue} from './exposeUpdateValueOrContinue.js'; export {default as exposeWhetherDependencyAvailable} from './exposeWhetherDependencyAvailable.js'; +export {default as flipFilter} from './flipFilter.js'; export {default as raiseOutputWithoutDependency} from './raiseOutputWithoutDependency.js'; export {default as raiseOutputWithoutUpdateValue} from './raiseOutputWithoutUpdateValue.js'; export {default as withAvailabilityFilter} from './withAvailabilityFilter.js'; diff --git a/src/data/composite/control-flow/withAvailabilityFilter.js b/src/data/composite/control-flow/withAvailabilityFilter.js index cfea998e..fd93af71 100644 --- a/src/data/composite/control-flow/withAvailabilityFilter.js +++ b/src/data/composite/control-flow/withAvailabilityFilter.js @@ -4,6 +4,7 @@ // Accepts the same mode options as withResultOfAvailabilityCheck. // // See also: +// - flipFilter // - withFilteredList // - withResultOfAvailabilityCheck // diff --git a/src/data/composite/data/index.js b/src/data/composite/data/index.js index 46a3dc81..05b59445 100644 --- a/src/data/composite/data/index.js +++ b/src/data/composite/data/index.js @@ -20,6 +20,7 @@ export {default as withMappedList} from './withMappedList.js'; export {default as withSortedList} from './withSortedList.js'; export {default as withStretchedList} from './withStretchedList.js'; +export {default as withLengthOfList} from './withLengthOfList.js'; export {default as withPropertyFromList} from './withPropertyFromList.js'; export {default as withPropertiesFromList} from './withPropertiesFromList.js'; diff --git a/src/data/composite/data/withFilteredList.js b/src/data/composite/data/withFilteredList.js index 44c1661d..15ee3373 100644 --- a/src/data/composite/data/withFilteredList.js +++ b/src/data/composite/data/withFilteredList.js @@ -2,9 +2,6 @@ // corresponding items in a list. Items which correspond to a truthy value // are kept, and the rest are excluded from the output list. // -// If the flip option is set, only items corresponding with a *falsy* value in -// the filter are kept. -// // TODO: There should be two outputs - one for the items included according to // the filter, and one for the items excluded. // @@ -22,28 +19,19 @@ export default templateCompositeFrom({ inputs: { list: input({type: 'array'}), filter: input({type: 'array'}), - - flip: input({ - type: 'boolean', - defaultValue: false, - }), }, outputs: ['#filteredList'], steps: () => [ { - dependencies: [input('list'), input('filter'), input('flip')], + dependencies: [input('list'), input('filter')], compute: (continuation, { [input('list')]: list, [input('filter')]: filter, - [input('flip')]: flip, }) => continuation({ '#filteredList': - list.filter((_item, index) => - (flip - ? !filter[index] - : filter[index])), + list.filter((_item, index) => filter[index]), }), }, ], diff --git a/src/data/composite/data/withLengthOfList.js b/src/data/composite/data/withLengthOfList.js new file mode 100644 index 00000000..e67aa887 --- /dev/null +++ b/src/data/composite/data/withLengthOfList.js @@ -0,0 +1,54 @@ +import {input, templateCompositeFrom} from '#composite'; + +function getOutputName({ + [input.staticDependency('list')]: list, +}) { + if (list && list.startsWith('#')) { + return `${list}.length`; + } else if (list) { + return `#${list}.length`; + } else { + return '#length'; + } +} + +export default templateCompositeFrom({ + annotation: `withMappedList`, + + inputs: { + list: input({type: 'array'}), + }, + + outputs: inputs => [getOutputName(inputs)], + + steps: () => [ + { + dependencies: [input.staticDependency('list')], + compute: (continuation, inputs) => + continuation({'#output': getOutputName(inputs)}), + }, + + { + dependencies: [input('list')], + compute: (continuation, { + [input('list')]: list, + }) => continuation({ + ['#value']: + (list === null + ? null + : list.length), + }), + }, + + { + dependencies: ['#output', '#value'], + + compute: (continuation, { + ['#output']: output, + ['#value']: value, + }) => continuation({ + [output]: value, + }), + }, + ], +}); diff --git a/src/data/composite/data/withNearbyItemFromList.js b/src/data/composite/data/withNearbyItemFromList.js index 83a8cc21..5e165219 100644 --- a/src/data/composite/data/withNearbyItemFromList.js +++ b/src/data/composite/data/withNearbyItemFromList.js @@ -9,6 +9,10 @@ // - If the 'valuePastEdge' input is provided, that value will be output // instead of null. // +// - If the 'filter' input is provided, corresponding items will be skipped, +// and only (repeating `offset`) the next included in the filter will be +// returned. +// // Both the list and item must be provided. // // See also: @@ -16,7 +20,6 @@ // import {input, templateCompositeFrom} from '#composite'; -import {atOffset} from '#sugar'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; @@ -28,9 +31,12 @@ export default templateCompositeFrom({ inputs: { list: input({acceptsNull: false, type: 'array'}), item: input({acceptsNull: false}), - offset: input({type: 'number'}), + wrap: input({type: 'boolean', defaultValue: false}), + valuePastEdge: input({defaultValue: null}), + + filter: input({defaultValue: null, type: 'array'}), }, outputs: ['#nearbyItem'], @@ -45,29 +51,55 @@ export default templateCompositeFrom({ dependency: '#index', mode: input.value('index'), - output: input.value({ - ['#nearbyItem']: - null, - }), + output: input.value({'#nearbyItem': null}), }), { dependencies: [ input('list'), input('offset'), + input('wrap'), + input('valuePastEdge'), + + input('filter'), + '#index', ], compute: (continuation, { [input('list')]: list, [input('offset')]: offset, + [input('wrap')]: wrap, + [input('valuePastEdge')]: valuePastEdge, + + [input('filter')]: filter, + ['#index']: index, - }) => continuation({ - ['#nearbyItem']: - atOffset(list, index, offset, {wrap}), - }), + }) => { + const startIndex = index; + + do { + index += offset; + + if (wrap) { + index = index % list.length; + } else if (index < 0) { + return continuation({'#nearbyItem': valuePastEdge}); + } else if (index >= list.length) { + return continuation({'#nearbyItem': valuePastEdge}); + } + + if (filter && !filter[index]) { + continue; + } + + return continuation({'#nearbyItem': list[index]}); + } while (index !== startIndex); + + return continuation({'#nearbyItem': null}); + }, }, ], }); diff --git a/src/data/composite/data/withPropertyFromList.js b/src/data/composite/data/withPropertyFromList.js index 65ebf77b..760095c2 100644 --- a/src/data/composite/data/withPropertyFromList.js +++ b/src/data/composite/data/withPropertyFromList.js @@ -5,11 +5,15 @@ // original list are kept null here. Objects which don't have the specified // property are retained in-place as null. // +// If the `internal` input is true, this reads the CacheableObject update value +// of each object rather than its exposed value. +// // See also: // - withPropertiesFromList // - withPropertyFromObject // +import CacheableObject from '#cacheable-object'; import {input, templateCompositeFrom} from '#composite'; function getOutputName({list, property, prefix}) { @@ -26,6 +30,7 @@ export default templateCompositeFrom({ list: input({type: 'array'}), property: input({type: 'string'}), prefix: input.staticValue({type: 'string', defaultValue: null}), + internal: input({type: 'boolean', defaultValue: false}), }, outputs: ({ @@ -37,13 +42,26 @@ export default templateCompositeFrom({ steps: () => [ { - dependencies: [input('list'), input('property')], + dependencies: [ + input('list'), + input('property'), + input('internal'), + ], + compute: (continuation, { [input('list')]: list, [input('property')]: property, + [input('internal')]: internal, }) => continuation({ ['#values']: - list.map(item => item[property] ?? null), + list.map(item => + (item === null + ? null + : internal + ? CacheableObject.getUpdateValue(item, property) + ?? null + : item[property] + ?? null)), }), }, diff --git a/src/data/composite/data/withPropertyFromObject.js b/src/data/composite/data/withPropertyFromObject.js index 4f240506..7b452b99 100644 --- a/src/data/composite/data/withPropertyFromObject.js +++ b/src/data/composite/data/withPropertyFromObject.js @@ -13,6 +13,21 @@ import CacheableObject from '#cacheable-object'; import {input, templateCompositeFrom} from '#composite'; +function getOutputName({ + [input.staticDependency('object')]: object, + [input.staticValue('property')]: property, +}) { + if (object && property) { + if (object.startsWith('#')) { + return `${object}.${property}`; + } else { + return `#${object}.${property}`; + } + } else { + return '#value'; + } +} + export default templateCompositeFrom({ annotation: `withPropertyFromObject`, @@ -22,15 +37,7 @@ export default templateCompositeFrom({ internal: input({type: 'boolean', defaultValue: false}), }, - outputs: ({ - [input.staticDependency('object')]: object, - [input.staticValue('property')]: property, - }) => - (object && property - ? (object.startsWith('#') - ? [`${object}.${property}`] - : [`#${object}.${property}`]) - : ['#value']), + outputs: inputs => [getOutputName(inputs)], steps: () => [ { @@ -39,17 +46,8 @@ export default templateCompositeFrom({ input.staticValue('property'), ], - compute: (continuation, { - [input.staticDependency('object')]: object, - [input.staticValue('property')]: property, - }) => continuation({ - '#output': - (object && property - ? (object.startsWith('#') - ? `${object}.${property}` - : `#${object}.${property}`) - : '#value'), - }), + compute: (continuation, inputs) => + continuation({'#output': getOutputName(inputs)}), }, { diff --git a/src/data/composite/things/album/index.js b/src/data/composite/things/album/index.js index 8b5098f0..de1d37c3 100644 --- a/src/data/composite/things/album/index.js +++ b/src/data/composite/things/album/index.js @@ -1 +1,2 @@ +export {default as withCoverArtDate} from './withCoverArtDate.js'; export {default as withTracks} from './withTracks.js'; diff --git a/src/data/composite/things/album/withCoverArtDate.js b/src/data/composite/things/album/withCoverArtDate.js new file mode 100644 index 00000000..978f566a --- /dev/null +++ b/src/data/composite/things/album/withCoverArtDate.js @@ -0,0 +1,50 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isDate} from '#validators'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withHasArtwork} from '#composite/wiki-data'; + +export default templateCompositeFrom({ + annotation: `withCoverArtDate`, + + inputs: { + from: input({ + validate: isDate, + defaultDependency: 'coverArtDate', + acceptsNull: true, + }), + }, + + outputs: ['#coverArtDate'], + + steps: () => [ + withHasArtwork({ + contribs: 'coverArtistContribs', + artworks: 'coverArtworks', + }), + + raiseOutputWithoutDependency({ + dependency: '#hasArtwork', + mode: input.value('falsy'), + output: input.value({'#coverArtDate': null}), + }), + + { + dependencies: [input('from')], + compute: (continuation, { + [input('from')]: from, + }) => + (from + ? continuation.raiseOutput({'#coverArtDate': from}) + : continuation()), + }, + + { + dependencies: ['date'], + compute: (continuation, {date}) => + (date + ? continuation({'#coverArtDate': date}) + : continuation({'#coverArtDate': null})), + }, + ], +}); diff --git a/src/data/composite/things/artwork/index.js b/src/data/composite/things/artwork/index.js new file mode 100644 index 00000000..b5e5e167 --- /dev/null +++ b/src/data/composite/things/artwork/index.js @@ -0,0 +1,7 @@ +export {default as withArtTags} from './withArtTags.js'; +export {default as withAttachedArtwork} from './withAttachedArtwork.js'; +export {default as withContainingArtworkList} from './withContainingArtworkList.js'; +export {default as withContentWarningArtTags} from './withContentWarningArtTags.js'; +export {default as withContribsFromAttachedArtwork} from './withContribsFromAttachedArtwork.js'; +export {default as withDate} from './withDate.js'; +export {default as withPropertyFromAttachedArtwork} from './withPropertyFromAttachedArtwork.js'; diff --git a/src/data/composite/things/artwork/withArtTags.js b/src/data/composite/things/artwork/withArtTags.js new file mode 100644 index 00000000..1fed3c31 --- /dev/null +++ b/src/data/composite/things/artwork/withArtTags.js @@ -0,0 +1,99 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck} + from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; +import {withResolvedReferenceList} from '#composite/wiki-data'; +import {soupyFind} from '#composite/wiki-properties'; + +import withPropertyFromAttachedArtwork + from './withPropertyFromAttachedArtwork.js'; + +export default templateCompositeFrom({ + annotation: `withArtTags`, + + inputs: { + from: input({ + type: 'array', + acceptsNull: true, + defaultDependency: 'artTags', + }), + }, + + outputs: ['#artTags'], + + steps: () => [ + withResolvedReferenceList({ + list: input('from'), + find: soupyFind.input('artTag'), + }), + + withResultOfAvailabilityCheck({ + from: '#resolvedReferenceList', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', '#resolvedReferenceList'], + compute: (continuation, { + ['#availability']: availability, + ['#resolvedReferenceList']: resolvedReferenceList, + }) => + (availability + ? continuation.raiseOutput({ + '#artTags': resolvedReferenceList, + }) + : continuation()), + }, + + withPropertyFromAttachedArtwork({ + property: input.value('artTags'), + }), + + withResultOfAvailabilityCheck({ + from: '#attachedArtwork.artTags', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', '#attachedArtwork.artTags'], + compute: (continuation, { + ['#availability']: availability, + ['#attachedArtwork.artTags']: attachedArtworkArtTags, + }) => + (availability + ? continuation.raiseOutput({ + '#artTags': attachedArtworkArtTags, + }) + : continuation()), + }, + + raiseOutputWithoutDependency({ + dependency: 'artTagsFromThingProperty', + output: input.value({'#artTags': []}), + }), + + withPropertyFromObject({ + object: 'thing', + property: 'artTagsFromThingProperty', + }).outputs({ + ['#value']: '#thing.artTags', + }), + + withResultOfAvailabilityCheck({ + from: '#thing.artTags', + mode: input.value('empty'), + }), + + { + dependencies: ['#availability', '#thing.artTags'], + compute: (continuation, { + ['#availability']: availability, + ['#thing.artTags']: thingArtTags, + }) => + (availability + ? continuation({'#artTags': thingArtTags}) + : continuation({'#artTags': []})), + }, + ], +}); diff --git a/src/data/composite/things/artwork/withAttachedArtwork.js b/src/data/composite/things/artwork/withAttachedArtwork.js new file mode 100644 index 00000000..d7c0d87b --- /dev/null +++ b/src/data/composite/things/artwork/withAttachedArtwork.js @@ -0,0 +1,43 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {flipFilter, raiseOutputWithoutDependency} + from '#composite/control-flow'; +import {withNearbyItemFromList, withPropertyFromList} from '#composite/data'; + +import withContainingArtworkList from './withContainingArtworkList.js'; + +export default templateCompositeFrom({ + annotaion: `withContribsFromMainArtwork`, + + outputs: ['#attachedArtwork'], + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: 'attachAbove', + mode: input.value('falsy'), + output: input.value({'#attachedArtwork': null}), + }), + + withContainingArtworkList(), + + withPropertyFromList({ + list: '#containingArtworkList', + property: input.value('attachAbove'), + }), + + flipFilter({ + filter: '#containingArtworkList.attachAbove', + }).outputs({ + '#containingArtworkList.attachAbove': '#filterNotAttached', + }), + + withNearbyItemFromList({ + list: '#containingArtworkList', + item: input.myself(), + offset: input.value(-1), + filter: '#filterNotAttached', + }).outputs({ + '#nearbyItem': '#attachedArtwork', + }), + ], +}); diff --git a/src/data/composite/things/artwork/withContainingArtworkList.js b/src/data/composite/things/artwork/withContainingArtworkList.js new file mode 100644 index 00000000..9c928ffd --- /dev/null +++ b/src/data/composite/things/artwork/withContainingArtworkList.js @@ -0,0 +1,46 @@ +// Gets the list of artworks which contains this one, which is functionally +// equivalent to `this.thing[this.thingProperty]`. If the exposed value is not +// a list at all (i.e. the property holds a single artwork), this composition +// outputs null. + +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +export default templateCompositeFrom({ + annotation: `withContainingArtworkList`, + + outputs: ['#containingArtworkList'], + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: 'thing', + output: input.value({'#containingArtworkList': null}), + }), + + raiseOutputWithoutDependency({ + dependency: 'thingProperty', + output: input.value({'#containingArtworkList': null}), + }), + + withPropertyFromObject({ + object: 'thing', + property: 'thingProperty', + }).outputs({ + '#value': '#containingValue', + }), + + { + dependencies: ['#containingValue'], + compute: (continuation, { + ['#containingValue']: containingValue, + }) => continuation({ + ['#containingArtworkList']: + (Array.isArray(containingValue) + ? containingValue + : null), + }), + }, + ], +}); diff --git a/src/data/composite/things/artwork/withContentWarningArtTags.js b/src/data/composite/things/artwork/withContentWarningArtTags.js new file mode 100644 index 00000000..4c07e837 --- /dev/null +++ b/src/data/composite/things/artwork/withContentWarningArtTags.js @@ -0,0 +1,27 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {withFilteredList, withPropertyFromList} from '#composite/data'; + +import withArtTags from './withArtTags.js'; + +export default templateCompositeFrom({ + annotation: `withContentWarningArtTags`, + + outputs: ['#contentWarningArtTags'], + + steps: () => [ + withArtTags(), + + withPropertyFromList({ + list: '#artTags', + property: input.value('isContentWarning'), + }), + + withFilteredList({ + list: '#artTags', + filter: '#artTags.isContentWarning', + }).outputs({ + '#filteredList': '#contentWarningArtTags', + }), + ], +}); diff --git a/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js b/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js new file mode 100644 index 00000000..e9425c95 --- /dev/null +++ b/src/data/composite/things/artwork/withContribsFromAttachedArtwork.js @@ -0,0 +1,27 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withRecontextualizedContributionList} from '#composite/wiki-data'; + +import withPropertyFromAttachedArtwork from './withPropertyFromAttachedArtwork.js'; + +export default templateCompositeFrom({ + annotaion: `withContribsFromAttachedArtwork`, + + outputs: ['#attachedArtwork.artistContribs'], + + steps: () => [ + withPropertyFromAttachedArtwork({ + property: input.value('artistContribs'), + }), + + raiseOutputWithoutDependency({ + dependency: '#attachedArtwork.artistContribs', + output: input.value({'#attachedArtwork.artistContribs': null}), + }), + + withRecontextualizedContributionList({ + list: '#attachedArtwork.artistContribs', + }), + ], +}); diff --git a/src/data/composite/things/artwork/withDate.js b/src/data/composite/things/artwork/withDate.js new file mode 100644 index 00000000..5e05b814 --- /dev/null +++ b/src/data/composite/things/artwork/withDate.js @@ -0,0 +1,41 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +export default templateCompositeFrom({ + annotation: `withDate`, + + inputs: { + from: input({ + defaultDependency: 'date', + acceptsNull: true, + }), + }, + + outputs: ['#date'], + + steps: () => [ + { + dependencies: [input('from')], + compute: (continuation, { + [input('from')]: date, + }) => + (date + ? continuation.raiseOutput({'#date': date}) + : continuation()), + }, + + raiseOutputWithoutDependency({ + dependency: 'dateFromThingProperty', + output: input.value({'#date': null}), + }), + + withPropertyFromObject({ + object: 'thing', + property: 'dateFromThingProperty', + }).outputs({ + ['#value']: '#date', + }), + ], +}) diff --git a/src/data/composite/things/artwork/withPropertyFromAttachedArtwork.js b/src/data/composite/things/artwork/withPropertyFromAttachedArtwork.js new file mode 100644 index 00000000..a2f954b9 --- /dev/null +++ b/src/data/composite/things/artwork/withPropertyFromAttachedArtwork.js @@ -0,0 +1,65 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {withResultOfAvailabilityCheck} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +import withAttachedArtwork from './withAttachedArtwork.js'; + +function getOutputName({ + [input.staticValue('property')]: property, +}) { + if (property) { + return `#attachedArtwork.${property}`; + } else { + return '#value'; + } +} + +export default templateCompositeFrom({ + annotation: `withPropertyFromAttachedArtwork`, + + inputs: { + property: input({type: 'string'}), + }, + + outputs: inputs => [getOutputName(inputs)], + + steps: () => [ + { + dependencies: [input.staticValue('property')], + compute: (continuation, inputs) => + continuation({'#output': getOutputName(inputs)}), + }, + + withAttachedArtwork(), + + withResultOfAvailabilityCheck({ + from: '#attachedArtwork', + }), + + { + dependencies: ['#availability', '#output'], + compute: (continuation, { + ['#availability']: availability, + ['#output']: output, + }) => + (availability + ? continuation() + : continuation.raiseOutput({[output]: null})), + }, + + withPropertyFromObject({ + object: '#attachedArtwork', + property: input('property'), + }), + + { + dependencies: ['#value', '#output'], + compute: (continuation, { + ['#value']: value, + ['#output']: output, + }) => + continuation.raiseOutput({[output]: value}), + }, + ], +}); diff --git a/src/data/composite/things/content/contentArtists.js b/src/data/composite/things/content/contentArtists.js new file mode 100644 index 00000000..8d5db5a5 --- /dev/null +++ b/src/data/composite/things/content/contentArtists.js @@ -0,0 +1,40 @@ +import {input, templateCompositeFrom} from '#composite'; +import {validateReferenceList} from '#validators'; + +import {exitWithoutDependency, exposeDependency} + from '#composite/control-flow'; +import {withResolvedReferenceList} from '#composite/wiki-data'; +import {soupyFind} from '#composite/wiki-properties'; + +import withExpressedOrImplicitArtistReferences + from './helpers/withExpressedOrImplicitArtistReferences.js'; + +export default templateCompositeFrom({ + annotation: `contentArtists`, + + compose: false, + + update: { + validate: validateReferenceList('artist'), + }, + + steps: () => [ + withExpressedOrImplicitArtistReferences({ + from: input.updateValue(), + }), + + exitWithoutDependency({ + dependency: '#artistReferences', + value: input.value([]), + }), + + withResolvedReferenceList({ + list: '#artistReferences', + find: soupyFind.input('artist'), + }), + + exposeDependency({ + dependency: '#resolvedReferenceList', + }), + ], +}); diff --git a/src/data/composite/things/content/hasAnnotationPart.js b/src/data/composite/things/content/hasAnnotationPart.js new file mode 100644 index 00000000..83d175e3 --- /dev/null +++ b/src/data/composite/things/content/hasAnnotationPart.js @@ -0,0 +1,25 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {exposeDependency} from '#composite/control-flow'; + +import withHasAnnotationPart from './withHasAnnotationPart.js'; + +export default templateCompositeFrom({ + annotation: `hasAnnotationPart`, + + compose: false, + + inputs: { + part: input({type: 'string'}), + }, + + steps: () => [ + withHasAnnotationPart({ + part: input('part'), + }), + + exposeDependency({ + dependency: '#hasAnnotationPart', + }), + ], +}); diff --git a/src/data/composite/things/content/helpers/withExpressedOrImplicitArtistReferences.js b/src/data/composite/things/content/helpers/withExpressedOrImplicitArtistReferences.js new file mode 100644 index 00000000..69da8c75 --- /dev/null +++ b/src/data/composite/things/content/helpers/withExpressedOrImplicitArtistReferences.js @@ -0,0 +1,61 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withFilteredList, withMappedList} from '#composite/data'; +import {withContentNodes} from '#composite/wiki-data'; + +export default templateCompositeFrom({ + annotation: `withExpressedOrImplicitArtistReferences`, + + inputs: { + from: input({type: 'array', acceptsNull: true}), + }, + + outputs: ['#artistReferences'], + + steps: () => [ + { + dependencies: [input('from')], + compute: (continuation, { + [input('from')]: expressedArtistReferences, + }) => + (expressedArtistReferences + ? continuation.raiseOutput({'#artistReferences': expressedArtistReferences}) + : continuation()), + }, + + raiseOutputWithoutDependency({ + dependency: 'artistText', + output: input.value({'#artistReferences': null}), + }), + + withContentNodes({ + from: 'artistText', + }), + + withMappedList({ + list: '#contentNodes', + map: input.value(node => + node.type === 'tag' && + node.data.replacerKey?.data === 'artist'), + }).outputs({ + '#mappedList': '#artistTagFilter', + }), + + withFilteredList({ + list: '#contentNodes', + filter: '#artistTagFilter', + }).outputs({ + '#filteredList': '#artistTags', + }), + + withMappedList({ + list: '#artistTags', + map: input.value(node => + 'artist:' + + node.data.replacerValue[0].data), + }).outputs({ + '#mappedList': '#artistReferences', + }), + ], +}); diff --git a/src/data/composite/things/content/index.js b/src/data/composite/things/content/index.js new file mode 100644 index 00000000..4176337d --- /dev/null +++ b/src/data/composite/things/content/index.js @@ -0,0 +1,7 @@ +export {default as contentArtists} from './contentArtists.js'; +export {default as hasAnnotationPart} from './hasAnnotationPart.js'; +export {default as withAnnotationParts} from './withAnnotationParts.js'; +export {default as withHasAnnotationPart} from './withHasAnnotationPart.js'; +export {default as withSourceText} from './withSourceText.js'; +export {default as withSourceURLs} from './withSourceURLs.js'; +export {default as withWebArchiveDate} from './withWebArchiveDate.js'; diff --git a/src/data/composite/things/content/withAnnotationParts.js b/src/data/composite/things/content/withAnnotationParts.js new file mode 100644 index 00000000..0c5a0294 --- /dev/null +++ b/src/data/composite/things/content/withAnnotationParts.js @@ -0,0 +1,103 @@ +import {input, templateCompositeFrom} from '#composite'; +import {empty, transposeArrays} from '#sugar'; +import {is} from '#validators'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromList} from '#composite/data'; +import {splitContentNodesAround, withContentNodes} from '#composite/wiki-data'; + +export default templateCompositeFrom({ + annotation: `withAnnotationParts`, + + inputs: { + mode: input({ + validate: is('strings', 'nodes'), + }), + }, + + outputs: ['#annotationParts'], + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: 'annotation', + output: input.value({'#annotationParts': []}), + }), + + withContentNodes({ + from: 'annotation', + }), + + splitContentNodesAround({ + nodes: '#contentNodes', + around: input.value(/, */g), + }), + + { + dependencies: ['#contentNodeLists'], + compute: (continuation, { + ['#contentNodeLists']: nodeLists, + }) => continuation({ + ['#contentNodeLists']: + nodeLists.filter(list => !empty(list)), + }), + }, + + { + dependencies: ['#contentNodeLists', input('mode')], + compute: (continuation, { + ['#contentNodeLists']: nodeLists, + [input('mode')]: mode, + }) => + (mode === 'nodes' + ? continuation.raiseOutput({'#annotationParts': nodeLists}) + : continuation()), + }, + + { + dependencies: ['#contentNodeLists'], + + compute: (continuation, { + ['#contentNodeLists']: nodeLists, + }) => continuation({ + ['#firstNodes']: + nodeLists.map(list => list.at(0)), + + ['#lastNodes']: + nodeLists.map(list => list.at(-1)), + }), + }, + + withPropertyFromList({ + list: '#firstNodes', + property: input.value('i'), + }).outputs({ + '#firstNodes.i': '#startIndices', + }), + + withPropertyFromList({ + list: '#lastNodes', + property: input.value('iEnd'), + }).outputs({ + '#lastNodes.iEnd': '#endIndices', + }), + + { + dependencies: [ + 'annotation', + '#startIndices', + '#endIndices', + ], + + compute: (continuation, { + ['annotation']: annotation, + ['#startIndices']: startIndices, + ['#endIndices']: endIndices, + }) => continuation({ + ['#annotationParts']: + transposeArrays([startIndices, endIndices]) + .map(([start, end]) => + annotation.slice(start, end)), + }), + }, + ], +}); diff --git a/src/data/composite/things/content/withHasAnnotationPart.js b/src/data/composite/things/content/withHasAnnotationPart.js new file mode 100644 index 00000000..4af554f3 --- /dev/null +++ b/src/data/composite/things/content/withHasAnnotationPart.js @@ -0,0 +1,43 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withAnnotationParts from './withAnnotationParts.js'; + +export default templateCompositeFrom({ + annotation: `withHasAnnotationPart`, + + inputs: { + part: input({type: 'string'}), + }, + + outputs: ['#hasAnnotationPart'], + + steps: () => [ + withAnnotationParts({ + mode: input.value('strings'), + }), + + raiseOutputWithoutDependency({ + dependency: '#annotationParts', + output: input.value({'#hasAnnotationPart': false}), + }), + + { + dependencies: [ + input('part'), + '#annotationParts', + ], + + compute: (continuation, { + [input('part')]: search, + ['#annotationParts']: parts, + }) => continuation({ + ['#hasAnnotationPart']: + parts.some(part => + part.toLowerCase() === + search.toLowerCase()), + }), + }, + ], +}); diff --git a/src/data/composite/things/content/withSourceText.js b/src/data/composite/things/content/withSourceText.js new file mode 100644 index 00000000..292306b7 --- /dev/null +++ b/src/data/composite/things/content/withSourceText.js @@ -0,0 +1,53 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withAnnotationParts from './withAnnotationParts.js'; + +export default templateCompositeFrom({ + annotation: `withSourceText`, + + outputs: ['#sourceText'], + + steps: () => [ + withAnnotationParts({ + mode: input.value('nodes'), + }), + + raiseOutputWithoutDependency({ + dependency: '#annotationParts', + output: input.value({'#sourceText': null}), + }), + + { + dependencies: ['#annotationParts'], + compute: (continuation, { + ['#annotationParts']: annotationParts, + }) => continuation({ + ['#firstPartWithExternalLink']: + annotationParts + .find(nodes => nodes + .some(node => node.type === 'external-link')) ?? + null, + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#firstPartWithExternalLink', + output: input.value({'#sourceText': null}), + }), + + { + dependencies: ['annotation', '#firstPartWithExternalLink'], + compute: (continuation, { + ['annotation']: annotation, + ['#firstPartWithExternalLink']: nodes, + }) => continuation({ + ['#sourceText']: + annotation.slice( + nodes.at(0).i, + nodes.at(-1).iEnd), + }), + }, + ], +}); diff --git a/src/data/composite/things/content/withSourceURLs.js b/src/data/composite/things/content/withSourceURLs.js new file mode 100644 index 00000000..f85ff9ea --- /dev/null +++ b/src/data/composite/things/content/withSourceURLs.js @@ -0,0 +1,62 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withFilteredList, withMappedList} from '#composite/data'; + +import withAnnotationParts from './withAnnotationParts.js'; + +export default templateCompositeFrom({ + annotation: `withSourceURLs`, + + outputs: ['#sourceURLs'], + + steps: () => [ + withAnnotationParts({ + mode: input.value('nodes'), + }), + + raiseOutputWithoutDependency({ + dependency: '#annotationParts', + output: input.value({'#sourceURLs': []}), + }), + + { + dependencies: ['#annotationParts'], + compute: (continuation, { + ['#annotationParts']: annotationParts, + }) => continuation({ + ['#firstPartWithExternalLink']: + annotationParts + .find(nodes => nodes + .some(node => node.type === 'external-link')) ?? + null, + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#firstPartWithExternalLink', + output: input.value({'#sourceURLs': []}), + }), + + withMappedList({ + list: '#firstPartWithExternalLink', + map: input.value(node => node.type === 'external-link'), + }).outputs({ + '#mappedList': '#externalLinkFilter', + }), + + withFilteredList({ + list: '#firstPartWithExternalLink', + filter: '#externalLinkFilter', + }).outputs({ + '#filteredList': '#externalLinks', + }), + + withMappedList({ + list: '#externalLinks', + map: input.value(node => node.data.href), + }).outputs({ + '#mappedList': '#sourceURLs', + }), + ], +}); diff --git a/src/data/composite/things/content/withWebArchiveDate.js b/src/data/composite/things/content/withWebArchiveDate.js new file mode 100644 index 00000000..3aaa4f64 --- /dev/null +++ b/src/data/composite/things/content/withWebArchiveDate.js @@ -0,0 +1,41 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +export default templateCompositeFrom({ + annotation: `withWebArchiveDate`, + + outputs: ['#webArchiveDate'], + + steps: () => [ + { + dependencies: ['annotation'], + + compute: (continuation, {annotation}) => + continuation({ + ['#dateText']: + annotation + ?.match(/https?:\/\/web.archive.org\/web\/([0-9]{8,8})[0-9]*\//) + ?.[1] ?? + null, + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#dateText', + output: input.value({['#webArchiveDate']: null}), + }), + + { + dependencies: ['#dateText'], + compute: (continuation, {['#dateText']: dateText}) => + continuation({ + ['#webArchiveDate']: + new Date( + dateText.slice(0, 4) + '/' + + dateText.slice(4, 6) + '/' + + dateText.slice(6, 8)), + }), + }, + ], +}); diff --git a/src/data/composite/things/contribution/index.js b/src/data/composite/things/contribution/index.js index 9b22be2e..31d86b8b 100644 --- a/src/data/composite/things/contribution/index.js +++ b/src/data/composite/things/contribution/index.js @@ -1,6 +1,4 @@ export {default as inheritFromContributionPresets} from './inheritFromContributionPresets.js'; -export {default as thingPropertyMatches} from './thingPropertyMatches.js'; -export {default as thingReferenceTypeMatches} from './thingReferenceTypeMatches.js'; export {default as withContainingReverseContributionList} from './withContainingReverseContributionList.js'; export {default as withContributionArtist} from './withContributionArtist.js'; export {default as withContributionContext} from './withContributionContext.js'; diff --git a/src/data/composite/things/contribution/thingPropertyMatches.js b/src/data/composite/things/contribution/thingPropertyMatches.js deleted file mode 100644 index 4a37f2cf..00000000 --- a/src/data/composite/things/contribution/thingPropertyMatches.js +++ /dev/null @@ -1,33 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; - -import {exitWithoutDependency} from '#composite/control-flow'; - -export default templateCompositeFrom({ - annotation: `thingPropertyMatches`, - - compose: false, - - inputs: { - value: input({type: 'string'}), - }, - - steps: () => [ - exitWithoutDependency({ - dependency: 'thingProperty', - value: input.value(false), - }), - - { - dependencies: [ - 'thingProperty', - input('value'), - ], - - compute: ({ - ['thingProperty']: thingProperty, - [input('value')]: value, - }) => - thingProperty === value, - }, - ], -}); diff --git a/src/data/composite/things/contribution/thingReferenceTypeMatches.js b/src/data/composite/things/contribution/thingReferenceTypeMatches.js deleted file mode 100644 index 2ee811af..00000000 --- a/src/data/composite/things/contribution/thingReferenceTypeMatches.js +++ /dev/null @@ -1,39 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; - -import {exitWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - -export default templateCompositeFrom({ - annotation: `thingReferenceTypeMatches`, - - compose: false, - - inputs: { - value: input({type: 'string'}), - }, - - steps: () => [ - exitWithoutDependency({ - dependency: 'thing', - value: input.value(false), - }), - - withPropertyFromObject({ - object: 'thing', - property: input.value('constructor'), - }), - - { - dependencies: [ - '#thing.constructor', - input('value'), - ], - - compute: ({ - ['#thing.constructor']: constructor, - [input('value')]: value, - }) => - constructor[Symbol.for('Thing.referenceType')] === value, - }, - ], -}); diff --git a/src/data/composite/things/language/index.js b/src/data/composite/things/language/index.js new file mode 100644 index 00000000..f22cdaf6 --- /dev/null +++ b/src/data/composite/things/language/index.js @@ -0,0 +1 @@ +export {default as withStrings} from './withStrings.js'; diff --git a/src/data/composite/things/language/withStrings.js b/src/data/composite/things/language/withStrings.js new file mode 100644 index 00000000..3b8d46b3 --- /dev/null +++ b/src/data/composite/things/language/withStrings.js @@ -0,0 +1,111 @@ +import {logWarn} from '#cli'; +import {input, templateCompositeFrom} from '#composite'; +import {empty, withEntries} from '#sugar'; +import {languageOptionRegex} from '#wiki-data'; + +import {withResultOfAvailabilityCheck} from '#composite/control-flow'; + +export default templateCompositeFrom({ + annotation: `withStrings`, + + inputs: { + from: input({defaultDependency: 'strings'}), + }, + + outputs: ['#strings'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: input('from'), + }).outputs({ + '#availability': '#stringsAvailability', + }), + + withResultOfAvailabilityCheck({ + from: 'inheritedStrings', + }).outputs({ + '#availability': '#inheritedStringsAvailability', + }), + + { + dependencies: [ + '#stringsAvailability', + '#inheritedStringsAvailability', + ], + + compute: (continuation, { + ['#stringsAvailability']: stringsAvailability, + ['#inheritedStringsAvailability']: inheritedStringsAvailability, + }) => + (stringsAvailability || inheritedStringsAvailability + ? continuation() + : continuation.raiseOutput({'#strings': null})), + }, + + { + dependencies: [input('from'), '#inheritedStringsAvailability'], + compute: (continuation, { + [input('from')]: strings, + ['#inheritedStringsAvailability']: inheritedStringsAvailability, + }) => + (inheritedStringsAvailability + ? continuation() + : continuation.raiseOutput({'#strings': strings})), + }, + + { + dependencies: ['inheritedStrings', '#stringsAvailability'], + compute: (continuation, { + ['inheritedStrings']: inheritedStrings, + ['#stringsAvailability']: stringsAvailability, + }) => + (stringsAvailability + ? continuation() + : continuation.raiseOutput({'#strings': inheritedStrings})), + }, + + { + dependencies: [input('from'), 'inheritedStrings', 'code'], + compute(continuation, { + [input('from')]: strings, + ['inheritedStrings']: inheritedStrings, + ['code']: code, + }) { + const validStrings = { + ...inheritedStrings, + ...strings, + }; + + const optionsFromTemplate = template => + Array.from(template.matchAll(languageOptionRegex)) + .map(({groups}) => groups.name); + + for (const [key, providedTemplate] of Object.entries(strings)) { + const inheritedTemplate = inheritedStrings[key]; + if (!inheritedTemplate) continue; + + const providedOptions = optionsFromTemplate(providedTemplate); + const inheritedOptions = optionsFromTemplate(inheritedTemplate); + + const missingOptionNames = + inheritedOptions.filter(name => !providedOptions.includes(name)); + + const misplacedOptionNames = + providedOptions.filter(name => !inheritedOptions.includes(name)); + + if (!empty(missingOptionNames) || !empty(misplacedOptionNames)) { + logWarn`Not using ${code ?? '(no code)'} string ${key}:`; + if (!empty(missingOptionNames)) + logWarn`- Missing options: ${missingOptionNames.join(', ')}`; + if (!empty(misplacedOptionNames)) + logWarn`- Unexpected options: ${misplacedOptionNames.join(', ')}`; + + validStrings[key] = inheritedStrings[key]; + } + } + + return continuation({'#strings': validStrings}); + }, + }, + ], +}); diff --git a/src/data/composite/things/track-section/index.js b/src/data/composite/things/track-section/index.js index 3202ed49..f11a2ab5 100644 --- a/src/data/composite/things/track-section/index.js +++ b/src/data/composite/things/track-section/index.js @@ -1 +1,3 @@ export {default as withAlbum} from './withAlbum.js'; +export {default as withContinueCountingFrom} from './withContinueCountingFrom.js'; +export {default as withStartCountingFrom} from './withStartCountingFrom.js'; diff --git a/src/data/composite/things/track-section/withContinueCountingFrom.js b/src/data/composite/things/track-section/withContinueCountingFrom.js new file mode 100644 index 00000000..0ca52b6c --- /dev/null +++ b/src/data/composite/things/track-section/withContinueCountingFrom.js @@ -0,0 +1,25 @@ +import {templateCompositeFrom} from '#composite'; + +import withStartCountingFrom from './withStartCountingFrom.js'; + +export default templateCompositeFrom({ + annotation: `withContinueCountingFrom`, + + outputs: ['#continueCountingFrom'], + + steps: () => [ + withStartCountingFrom(), + + { + dependencies: ['#startCountingFrom', 'tracks'], + compute: (continuation, { + ['#startCountingFrom']: startCountingFrom, + ['tracks']: tracks, + }) => continuation({ + ['#continueCountingFrom']: + startCountingFrom + + tracks.length, + }), + }, + ], +}); diff --git a/src/data/composite/things/track-section/withStartCountingFrom.js b/src/data/composite/things/track-section/withStartCountingFrom.js new file mode 100644 index 00000000..ef345327 --- /dev/null +++ b/src/data/composite/things/track-section/withStartCountingFrom.js @@ -0,0 +1,64 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withNearbyItemFromList, withPropertyFromObject} from '#composite/data'; + +import withAlbum from './withAlbum.js'; + +export default templateCompositeFrom({ + annotation: `withStartCountingFrom`, + + inputs: { + from: input({ + type: 'number', + defaultDependency: 'startCountingFrom', + acceptsNull: true, + }), + }, + + outputs: ['#startCountingFrom'], + + steps: () => [ + { + dependencies: [input('from')], + compute: (continuation, { + [input('from')]: from, + }) => + (from === null + ? continuation() + : continuation.raiseOutput({'#startCountingFrom': from})), + }, + + withAlbum(), + + raiseOutputWithoutDependency({ + dependency: '#album', + output: input.value({'#startCountingFrom': 1}), + }), + + withPropertyFromObject({ + object: '#album', + property: input.value('trackSections'), + }), + + withNearbyItemFromList({ + list: '#album.trackSections', + item: input.myself(), + offset: input.value(-1), + }).outputs({ + '#nearbyItem': '#previousTrackSection', + }), + + raiseOutputWithoutDependency({ + dependency: '#previousTrackSection', + output: input.value({'#startCountingFrom': 1}), + }), + + withPropertyFromObject({ + object: '#previousTrackSection', + property: input.value('continueCountingFrom'), + }).outputs({ + '#previousTrackSection.continueCountingFrom': '#startCountingFrom', + }), + ], +}); diff --git a/src/data/composite/things/track/alwaysReferenceByDirectory.js b/src/data/composite/things/track/alwaysReferenceByDirectory.js new file mode 100644 index 00000000..a342d38b --- /dev/null +++ b/src/data/composite/things/track/alwaysReferenceByDirectory.js @@ -0,0 +1,69 @@ +// Controls how find.track works - it'll never be matched by a reference +// just to the track's name, which means you don't have to always reference +// some *other* (much more commonly referenced) track by directory instead +// of more naturally by name. + +import {input, templateCompositeFrom} from '#composite'; +import {isBoolean} from '#validators'; +import {getKebabCase} from '#wiki-data'; + +import {withPropertyFromObject} from '#composite/data'; + +import { + exitWithoutDependency, + exposeDependencyOrContinue, + exposeUpdateValueOrContinue, +} from '#composite/control-flow'; + +import withMainReleaseTrack from './withMainReleaseTrack.js'; +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; + +export default templateCompositeFrom({ + annotation: `alwaysReferenceByDirectory`, + + compose: false, + + steps: () => [ + exposeUpdateValueOrContinue({ + validate: input.value(isBoolean), + }), + + withPropertyFromAlbum({ + property: input.value('alwaysReferenceTracksByDirectory'), + }), + + // Falsy mode means this exposes true if the album's property is true, + // but continues if the property is false (which is also the default). + exposeDependencyOrContinue({ + dependency: '#album.alwaysReferenceTracksByDirectory', + mode: input.value('falsy'), + }), + + exitWithoutDependency({ + dependency: 'mainRelease', + value: input.value(false), + }), + + withMainReleaseTrack(), + + exitWithoutDependency({ + dependency: '#mainReleaseTrack', + value: input.value(false), + }), + + withPropertyFromObject({ + object: '#mainReleaseTrack', + property: input.value('name'), + }), + + { + dependencies: ['name', '#mainReleaseTrack.name'], + compute: ({ + ['name']: name, + ['#mainReleaseTrack.name']: mainReleaseName, + }) => + getKebabCase(name) === + getKebabCase(mainReleaseName), + }, + ], +}); diff --git a/src/data/composite/things/track/index.js b/src/data/composite/things/track/index.js index beb8c6ab..1c203cd9 100644 --- a/src/data/composite/things/track/index.js +++ b/src/data/composite/things/track/index.js @@ -1,16 +1,18 @@ +export {default as alwaysReferenceByDirectory} from './alwaysReferenceByDirectory.js'; export {default as exitWithoutUniqueCoverArt} from './exitWithoutUniqueCoverArt.js'; export {default as inheritContributionListFromMainRelease} from './inheritContributionListFromMainRelease.js'; export {default as inheritFromMainRelease} from './inheritFromMainRelease.js'; -export {default as withAlbum} from './withAlbum.js'; export {default as withAllReleases} from './withAllReleases.js'; -export {default as withAlwaysReferenceByDirectory} from './withAlwaysReferenceByDirectory.js'; export {default as withContainingTrackSection} from './withContainingTrackSection.js'; +export {default as withCoverArtistContribs} from './withCoverArtistContribs.js'; export {default as withDate} from './withDate.js'; export {default as withDirectorySuffix} from './withDirectorySuffix.js'; export {default as withHasUniqueCoverArt} from './withHasUniqueCoverArt.js'; export {default as withMainRelease} from './withMainRelease.js'; +export {default as withMainReleaseTrack} from './withMainReleaseTrack.js'; export {default as withOtherReleases} from './withOtherReleases.js'; export {default as withPropertyFromAlbum} from './withPropertyFromAlbum.js'; export {default as withPropertyFromMainRelease} from './withPropertyFromMainRelease.js'; export {default as withSuffixDirectoryFromAlbum} from './withSuffixDirectoryFromAlbum.js'; export {default as withTrackArtDate} from './withTrackArtDate.js'; +export {default as withTrackNumber} from './withTrackNumber.js'; diff --git a/src/data/composite/things/track/trackAdditionalNameList.js b/src/data/composite/things/track/trackAdditionalNameList.js deleted file mode 100644 index 65a2263d..00000000 --- a/src/data/composite/things/track/trackAdditionalNameList.js +++ /dev/null @@ -1,38 +0,0 @@ -// Compiles additional names from various sources. - -import {input, templateCompositeFrom} from '#composite'; -import {isAdditionalNameList} from '#validators'; - -import withInferredAdditionalNames from './withInferredAdditionalNames.js'; -import withSharedAdditionalNames from './withSharedAdditionalNames.js'; - -export default templateCompositeFrom({ - annotation: `trackAdditionalNameList`, - - compose: false, - - update: {validate: isAdditionalNameList}, - - steps: () => [ - withInferredAdditionalNames(), - withSharedAdditionalNames(), - - { - dependencies: [ - '#inferredAdditionalNames', - '#sharedAdditionalNames', - input.updateValue(), - ], - - compute: ({ - ['#inferredAdditionalNames']: inferredAdditionalNames, - ['#sharedAdditionalNames']: sharedAdditionalNames, - [input.updateValue()]: providedAdditionalNames, - }) => [ - ...providedAdditionalNames ?? [], - ...sharedAdditionalNames, - ...inferredAdditionalNames, - ], - }, - ], -}); diff --git a/src/data/composite/things/track/withAlbum.js b/src/data/composite/things/track/withAlbum.js deleted file mode 100644 index 4c55e1f4..00000000 --- a/src/data/composite/things/track/withAlbum.js +++ /dev/null @@ -1,22 +0,0 @@ -// Gets the track's album. This will early exit if albumData is missing. -// If there's no album whose list of tracks includes this track, the output -// dependency will be null. - -import {templateCompositeFrom} from '#composite'; - -import {withUniqueReferencingThing} from '#composite/wiki-data'; -import {soupyReverse} from '#composite/wiki-properties'; - -export default templateCompositeFrom({ - annotation: `withAlbum`, - - outputs: ['#album'], - - steps: () => [ - withUniqueReferencingThing({ - reverse: soupyReverse.input('albumsWhoseTracksInclude'), - }).outputs({ - ['#uniqueReferencingThing']: '#album', - }), - ], -}); diff --git a/src/data/composite/things/track/withAllReleases.js b/src/data/composite/things/track/withAllReleases.js index b93bf753..bd54384f 100644 --- a/src/data/composite/things/track/withAllReleases.js +++ b/src/data/composite/things/track/withAllReleases.js @@ -8,10 +8,9 @@ import {input, templateCompositeFrom} from '#composite'; import {sortByDate} from '#sort'; -import {exitWithoutDependency} from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import withMainRelease from './withMainRelease.js'; +import withMainReleaseTrack from './withMainReleaseTrack.js'; export default templateCompositeFrom({ annotation: `withAllReleases`, @@ -19,7 +18,7 @@ export default templateCompositeFrom({ outputs: ['#allReleases'], steps: () => [ - withMainRelease({ + withMainReleaseTrack({ selfIfMain: input.value(true), notFoundValue: input.value([]), }), @@ -29,18 +28,22 @@ export default templateCompositeFrom({ // `this.secondaryReleases` from within a data composition. // Oooooooooooooooooooooooooooooooooooooooooooooooo withPropertyFromObject({ - object: '#mainRelease', + object: '#mainReleaseTrack', property: input.value('secondaryReleases'), }), { - dependencies: ['#mainRelease', '#mainRelease.secondaryReleases'], + dependencies: [ + '#mainReleaseTrack', + '#mainReleaseTrack.secondaryReleases', + ], + compute: (continuation, { - ['#mainRelease']: mainRelease, - ['#mainRelease.secondaryReleases']: secondaryReleases, + ['#mainReleaseTrack']: mainReleaseTrack, + ['#mainReleaseTrack.secondaryReleases']: secondaryReleases, }) => continuation({ ['#allReleases']: - sortByDate([mainRelease, ...secondaryReleases]), + sortByDate([mainReleaseTrack, ...secondaryReleases]), }), }, ], diff --git a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js b/src/data/composite/things/track/withAlwaysReferenceByDirectory.js deleted file mode 100644 index aebcf793..00000000 --- a/src/data/composite/things/track/withAlwaysReferenceByDirectory.js +++ /dev/null @@ -1,106 +0,0 @@ -// Controls how find.track works - it'll never be matched by a reference -// just to the track's name, which means you don't have to always reference -// some *other* (much more commonly referenced) track by directory instead -// of more naturally by name. - -import {input, templateCompositeFrom} from '#composite'; -import find from '#find'; -import {isBoolean} from '#validators'; - -import {withPropertyFromObject} from '#composite/data'; -import {withResolvedReference} from '#composite/wiki-data'; -import {soupyFind} from '#composite/wiki-properties'; - -import { - exitWithoutDependency, - exposeDependencyOrContinue, - exposeUpdateValueOrContinue, -} from '#composite/control-flow'; - -export default templateCompositeFrom({ - annotation: `withAlwaysReferenceByDirectory`, - - outputs: ['#alwaysReferenceByDirectory'], - - steps: () => [ - exposeUpdateValueOrContinue({ - validate: input.value(isBoolean), - }), - - // withAlwaysReferenceByDirectory is sort of a fragile area - we can't - // find the track's album the normal way because albums' track lists - // recurse back into alwaysReferenceByDirectory! - withResolvedReference({ - ref: 'dataSourceAlbum', - find: soupyFind.input('album'), - }).outputs({ - '#resolvedReference': '#album', - }), - - withPropertyFromObject({ - object: '#album', - property: input.value('alwaysReferenceTracksByDirectory'), - }), - - // Falsy mode means this exposes true if the album's property is true, - // but continues if the property is false (which is also the default). - exposeDependencyOrContinue({ - dependency: '#album.alwaysReferenceTracksByDirectory', - mode: input.value('falsy'), - }), - - // Remaining code is for defaulting to true if this track is a rerelease of - // another with the same name, so everything further depends on access to - // trackData as well as mainReleaseTrack. - - exitWithoutDependency({ - dependency: 'trackData', - mode: input.value('empty'), - value: input.value(false), - }), - - exitWithoutDependency({ - dependency: 'mainReleaseTrack', - value: input.value(false), - }), - - // It's necessary to use the custom trackMainReleasesOnly find function - // here, so as to avoid recursion issues - the find.track() function depends - // on accessing each track's alwaysReferenceByDirectory, which means it'll - // hit *this track* - and thus this step - and end up recursing infinitely. - // By definition, find.trackMainReleasesOnly excludes tracks which have - // an mainReleaseTrack update value set, which means even though it does - // still access each of tracks' `alwaysReferenceByDirectory` property, it - // won't access that of *this* track - it will never proceed past the - // `exitWithoutDependency` step directly above, so there's no opportunity - // for recursion. - withResolvedReference({ - ref: 'mainReleaseTrack', - data: 'trackData', - find: input.value(find.trackMainReleasesOnly), - }).outputs({ - '#resolvedReference': '#mainRelease', - }), - - exitWithoutDependency({ - dependency: '#mainRelease', - value: input.value(false), - }), - - withPropertyFromObject({ - object: '#mainRelease', - property: input.value('name'), - }), - - { - dependencies: ['name', '#mainRelease.name'], - compute: (continuation, { - name, - ['#mainRelease.name']: mainReleaseName, - }) => continuation({ - ['#alwaysReferenceByDirectory']: - name === mainReleaseName, - }), - }, - ], -}); diff --git a/src/data/composite/things/track/withCoverArtistContribs.js b/src/data/composite/things/track/withCoverArtistContribs.js new file mode 100644 index 00000000..9057cfeb --- /dev/null +++ b/src/data/composite/things/track/withCoverArtistContribs.js @@ -0,0 +1,73 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isContributionList} from '#validators'; + +import {exposeDependencyOrContinue} from '#composite/control-flow'; + +import { + withRecontextualizedContributionList, + withRedatedContributionList, + withResolvedContribs, +} from '#composite/wiki-data'; + +import exitWithoutUniqueCoverArt from './exitWithoutUniqueCoverArt.js'; +import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withTrackArtDate from './withTrackArtDate.js'; + +export default templateCompositeFrom({ + annotation: `withCoverArtistContribs`, + + inputs: { + from: input({ + defaultDependency: 'coverArtistContribs', + validate: isContributionList, + acceptsNull: true, + }), + }, + + outputs: ['#coverArtistContribs'], + + steps: () => [ + exitWithoutUniqueCoverArt({ + value: input.value([]), + }), + + withTrackArtDate(), + + withResolvedContribs({ + from: input('from'), + thingProperty: input.value('coverArtistContribs'), + artistProperty: input.value('trackCoverArtistContributions'), + date: '#trackArtDate', + }).outputs({ + '#resolvedContribs': '#coverArtistContribs', + }), + + exposeDependencyOrContinue({ + dependency: '#coverArtistContribs', + mode: input.value('empty'), + }), + + withPropertyFromAlbum({ + property: input.value('trackCoverArtistContribs'), + }), + + withRecontextualizedContributionList({ + list: '#album.trackCoverArtistContribs', + artistProperty: input.value('trackCoverArtistContributions'), + }), + + withRedatedContributionList({ + list: '#album.trackCoverArtistContribs', + date: '#trackArtDate', + }), + + { + dependencies: ['#album.trackCoverArtistContribs'], + compute: (continuation, { + ['#album.trackCoverArtistContribs']: coverArtistContribs, + }) => continuation({ + ['#coverArtistContribs']: coverArtistContribs, + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withDate.js b/src/data/composite/things/track/withDate.js index b5a770e9..1851c0d2 100644 --- a/src/data/composite/things/track/withDate.js +++ b/src/data/composite/things/track/withDate.js @@ -12,6 +12,14 @@ export default templateCompositeFrom({ steps: () => [ { + dependencies: ['disableDate'], + compute: (continuation, {disableDate}) => + (disableDate + ? continuation.raiseOutput({'#date': null}) + : continuation()), + }, + + { dependencies: ['dateFirstReleased'], compute: (continuation, {dateFirstReleased}) => (dateFirstReleased diff --git a/src/data/composite/things/track/withDirectorySuffix.js b/src/data/composite/things/track/withDirectorySuffix.js index c063e158..c3651491 100644 --- a/src/data/composite/things/track/withDirectorySuffix.js +++ b/src/data/composite/things/track/withDirectorySuffix.js @@ -1,8 +1,9 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; -import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withContainingTrackSection from './withContainingTrackSection.js'; import withSuffixDirectoryFromAlbum from './withSuffixDirectoryFromAlbum.js'; export default templateCompositeFrom({ @@ -16,21 +17,16 @@ export default templateCompositeFrom({ raiseOutputWithoutDependency({ dependency: '#suffixDirectoryFromAlbum', mode: input.value('falsy'), - output: input.value({['#directorySuffix']: null}), + output: input.value({'#directorySuffix': null}), }), - withPropertyFromAlbum({ + withContainingTrackSection(), + + withPropertyFromObject({ + object: '#trackSection', property: input.value('directorySuffix'), + }).outputs({ + '#trackSection.directorySuffix': '#directorySuffix', }), - - { - dependencies: ['#album.directorySuffix'], - compute: (continuation, { - ['#album.directorySuffix']: directorySuffix, - }) => continuation({ - ['#directorySuffix']: - directorySuffix, - }), - }, ], }); diff --git a/src/data/composite/things/track/withHasUniqueCoverArt.js b/src/data/composite/things/track/withHasUniqueCoverArt.js index f7e65f25..85d3b92a 100644 --- a/src/data/composite/things/track/withHasUniqueCoverArt.js +++ b/src/data/composite/things/track/withHasUniqueCoverArt.js @@ -5,11 +5,18 @@ // or a placeholder. (This property is named hasUniqueCoverArt instead of // the usual hasCoverArt to emphasize that it does not inherit from the // album.) +// +// withHasUniqueCoverArt is based only around the presence of *specified* +// cover artist contributions, not whether the references to artists on those +// contributions actually resolve to anything. It completely evades interacting +// with find/replace. import {input, templateCompositeFrom} from '#composite'; -import {empty} from '#sugar'; -import {withResolvedContribs} from '#composite/wiki-data'; +import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck} + from '#composite/control-flow'; +import {fillMissingListItems, withFlattenedList, withPropertyFromList} + from '#composite/data'; import withPropertyFromAlbum from './withPropertyFromAlbum.js'; @@ -29,36 +36,73 @@ export default templateCompositeFrom({ : continuation()), }, - withResolvedContribs({ + withResultOfAvailabilityCheck({ from: 'coverArtistContribs', - date: input.value(null), + mode: input.value('empty'), }), { - dependencies: ['#resolvedContribs'], + dependencies: ['#availability'], compute: (continuation, { - ['#resolvedContribs']: contribsFromTrack, + ['#availability']: availability, }) => - (empty(contribsFromTrack) - ? continuation() - : continuation.raiseOutput({ + (availability + ? continuation.raiseOutput({ ['#hasUniqueCoverArt']: true, - })), + }) + : continuation()), }, withPropertyFromAlbum({ property: input.value('trackCoverArtistContribs'), + internal: input.value(true), + }), + + withResultOfAvailabilityCheck({ + from: '#album.trackCoverArtistContribs', + mode: input.value('empty'), }), { - dependencies: ['#album.trackCoverArtistContribs'], + dependencies: ['#availability'], compute: (continuation, { - ['#album.trackCoverArtistContribs']: contribsFromAlbum, + ['#availability']: availability, }) => - continuation.raiseOutput({ - ['#hasUniqueCoverArt']: - !empty(contribsFromAlbum), - }), + (availability + ? continuation.raiseOutput({ + ['#hasUniqueCoverArt']: true, + }) + : continuation()), }, + + raiseOutputWithoutDependency({ + dependency: 'trackArtworks', + mode: input.value('empty'), + output: input.value({'#hasUniqueCoverArt': false}), + }), + + withPropertyFromList({ + list: 'trackArtworks', + property: input.value('artistContribs'), + internal: input.value(true), + }), + + // Since we're getting the update value for each artwork's artistContribs, + // it may not be set at all, and in that case won't be exposing as []. + fillMissingListItems({ + list: '#trackArtworks.artistContribs', + fill: input.value([]), + }), + + withFlattenedList({ + list: '#trackArtworks.artistContribs', + }), + + withResultOfAvailabilityCheck({ + from: '#flattenedList', + mode: input.value('empty'), + }).outputs({ + '#availability': '#hasUniqueCoverArt', + }), ], }); diff --git a/src/data/composite/things/track/withMainRelease.js b/src/data/composite/things/track/withMainRelease.js index 3a91edae..67a312ae 100644 --- a/src/data/composite/things/track/withMainRelease.js +++ b/src/data/composite/things/track/withMainRelease.js @@ -1,13 +1,15 @@ -// Just includes the main release of this track as a dependency. -// If this track isn't a secondary release, then it'll provide null, unless -// the {selfIfMain} option is set, in which case it'll provide this track -// itself. This will early exit (with notFoundValue) if the main release -// is specified by reference and that reference doesn't resolve to anything. +// Resolves this track's `mainRelease` reference, using weird-ass atypical +// machinery that operates on soupyFind and does not operate on findMixed, +// let alone a prim and proper standalone find spec. +// +// Raises null only if there is no `mainRelease` reference provided at all. +// This will early exit (with notFoundValue) if the reference doesn't resolve. +// import {input, templateCompositeFrom} from '#composite'; -import {exitWithoutDependency, withResultOfAvailabilityCheck} - from '#composite/control-flow'; +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; import {withResolvedReference} from '#composite/wiki-data'; import {soupyFind} from '#composite/wiki-properties'; @@ -15,56 +17,121 @@ export default templateCompositeFrom({ annotation: `withMainRelease`, inputs: { - selfIfMain: input({type: 'boolean', defaultValue: false}), + from: input({ + defaultDependency: 'mainRelease', + acceptsNull: true, + }), + notFoundValue: input({defaultValue: null}), }, outputs: ['#mainRelease'], steps: () => [ - withResultOfAvailabilityCheck({ - from: 'mainReleaseTrack', + raiseOutputWithoutDependency({ + dependency: input('from'), + output: input.value({'#mainRelease': null}), }), { + dependencies: [input('from'), 'name'], + compute: (continuation, { + [input('from')]: ref, + ['name']: ownName, + }) => + (ref === 'same name single' + ? continuation({ + ['#albumOrTrackReference']: null, + ['#sameNameSingleReference']: ownName, + }) + : continuation({ + ['#albumOrTrackReference']: ref, + ['#sameNameSingleReference']: null, + })), + }, + + withResolvedReference({ + ref: '#albumOrTrackReference', + find: soupyFind.input('trackMainReleasesOnly'), + }).outputs({ + '#resolvedReference': '#matchingTrack', + }), + + withResolvedReference({ + ref: '#albumOrTrackReference', + find: soupyFind.input('album'), + }).outputs({ + '#resolvedReference': '#matchingAlbum', + }), + + withResolvedReference({ + ref: '#sameNameSingleReference', + find: soupyFind.input('albumSinglesOnly'), + findOptions: input.value({ + fuzz: { + capitalization: true, + kebab: true, + }, + }), + }).outputs({ + '#resolvedReference': '#sameNameSingle', + }), + + { + dependencies: ['#sameNameSingle'], + compute: (continuation, { + ['#sameNameSingle']: sameNameSingle, + }) => + (sameNameSingle + ? continuation.raiseOutput({ + ['#mainRelease']: + sameNameSingle, + }) + : continuation()), + }, + + { dependencies: [ - input.myself(), - input('selfIfMain'), - '#availability', + '#matchingTrack', + '#matchingAlbum', + input('notFoundValue'), ], compute: (continuation, { - [input.myself()]: track, - [input('selfIfMain')]: selfIfMain, - '#availability': availability, + ['#matchingTrack']: matchingTrack, + ['#matchingAlbum']: matchingAlbum, + [input('notFoundValue')]: notFoundValue, }) => - (availability + (matchingTrack && matchingAlbum ? continuation() - : continuation.raiseOutput({ + : matchingTrack ?? matchingAlbum + ? continuation.raiseOutput({ ['#mainRelease']: - (selfIfMain ? track : null), - })), + matchingTrack ?? matchingAlbum, + }) + : continuation.exit(notFoundValue)), }, - withResolvedReference({ - ref: 'mainReleaseTrack', - find: soupyFind.input('track'), - }), - - exitWithoutDependency({ - dependency: '#resolvedReference', - value: input('notFoundValue'), + withPropertyFromObject({ + object: '#matchingAlbum', + property: input.value('tracks'), }), { - dependencies: ['#resolvedReference'], + dependencies: [ + '#matchingAlbum.tracks', + '#matchingTrack', + input('notFoundValue'), + ], compute: (continuation, { - ['#resolvedReference']: resolvedReference, + ['#matchingAlbum.tracks']: matchingAlbumTracks, + ['#matchingTrack']: matchingTrack, + [input('notFoundValue')]: notFoundValue, }) => - continuation({ - ['#mainRelease']: resolvedReference, - }), + (matchingAlbumTracks.includes(matchingTrack) + ? continuation.raiseOutput({'#mainRelease': matchingTrack}) + : continuation.exit(notFoundValue)), }, ], }); diff --git a/src/data/composite/things/track/withMainReleaseTrack.js b/src/data/composite/things/track/withMainReleaseTrack.js new file mode 100644 index 00000000..6371e895 --- /dev/null +++ b/src/data/composite/things/track/withMainReleaseTrack.js @@ -0,0 +1,248 @@ +// Just provides the main release of this track as a dependency. +// If this track isn't a secondary release, then it'll provide null, unless +// the {selfIfMain} option is set, in which case it'll provide this track +// itself. This will early exit (with notFoundValue) if the main release +// is specified by reference and that reference doesn't resolve to anything. + +import {input, templateCompositeFrom} from '#composite'; +import {onlyItem} from '#sugar'; +import {getKebabCase} from '#wiki-data'; + +import { + exitWithoutDependency, + withAvailabilityFilter, + withResultOfAvailabilityCheck, +} from '#composite/control-flow'; + +import { + withFilteredList, + withMappedList, + withPropertyFromList, + withPropertyFromObject, +} from '#composite/data'; + +import withMainRelease from './withMainRelease.js'; + +export default templateCompositeFrom({ + annotation: `withMainReleaseTrack`, + + inputs: { + selfIfMain: input({type: 'boolean', defaultValue: false}), + notFoundValue: input({defaultValue: null}), + }, + + outputs: ['#mainReleaseTrack'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: 'mainRelease', + }), + + { + dependencies: [ + input.myself(), + input('selfIfMain'), + '#availability', + ], + + compute: (continuation, { + [input.myself()]: track, + [input('selfIfMain')]: selfIfMain, + '#availability': availability, + }) => + (availability + ? continuation() + : continuation.raiseOutput({ + ['#mainReleaseTrack']: + (selfIfMain ? track : null), + })), + }, + + withMainRelease(), + + exitWithoutDependency({ + dependency: '#mainRelease', + value: input('notFoundValue'), + }), + + withPropertyFromObject({ + object: '#mainRelease', + property: input.value('isTrack'), + }), + + { + dependencies: ['#mainRelease', '#mainRelease.isTrack'], + + compute: (continuation, { + ['#mainRelease']: mainRelease, + ['#mainRelease.isTrack']: mainReleaseIsTrack, + }) => + (mainReleaseIsTrack + ? continuation.raiseOutput({ + ['#mainReleaseTrack']: mainRelease, + }) + : continuation()), + }, + + { + dependencies: ['name', 'directory'], + compute: (continuation, { + ['name']: ownName, + ['directory']: ownDirectory, + }) => { + const ownNameKebabed = getKebabCase(ownName); + + return continuation({ + ['#mapItsNameLikeName']: + name => getKebabCase(name) === ownNameKebabed, + + ['#mapItsDirectoryLikeDirectory']: + (ownDirectory + ? directory => directory === ownDirectory + : () => false), + + ['#mapItsNameLikeDirectory']: + (ownDirectory + ? name => getKebabCase(name) === ownDirectory + : () => false), + + ['#mapItsDirectoryLikeName']: + directory => directory === ownNameKebabed, + }); + }, + }, + + withPropertyFromObject({ + object: '#mainRelease', + property: input.value('tracks'), + }), + + withPropertyFromList({ + list: '#mainRelease.tracks', + property: input.value('mainRelease'), + internal: input.value(true), + }), + + withAvailabilityFilter({ + from: '#mainRelease.tracks.mainRelease', + }), + + withMappedList({ + list: '#availabilityFilter', + map: input.value(item => !item), + }).outputs({ + '#mappedList': '#availabilityFilter', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#availabilityFilter', + }).outputs({ + '#filteredList': '#mainRelease.tracks', + }), + + withPropertyFromList({ + list: '#mainRelease.tracks', + property: input.value('name'), + }), + + withPropertyFromList({ + list: '#mainRelease.tracks', + property: input.value('directory'), + internal: input.value(true), + }), + + withMappedList({ + list: '#mainRelease.tracks.name', + map: '#mapItsNameLikeName', + }).outputs({ + '#mappedList': '#filterItsNameLikeName', + }), + + withMappedList({ + list: '#mainRelease.tracks.directory', + map: '#mapItsDirectoryLikeDirectory', + }).outputs({ + '#mappedList': '#filterItsDirectoryLikeDirectory', + }), + + withMappedList({ + list: '#mainRelease.tracks.name', + map: '#mapItsNameLikeDirectory', + }).outputs({ + '#mappedList': '#filterItsNameLikeDirectory', + }), + + withMappedList({ + list: '#mainRelease.tracks.directory', + map: '#mapItsDirectoryLikeName', + }).outputs({ + '#mappedList': '#filterItsDirectoryLikeName', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsNameLikeName', + }).outputs({ + '#filteredList': '#matchingItsNameLikeName', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsDirectoryLikeDirectory', + }).outputs({ + '#filteredList': '#matchingItsDirectoryLikeDirectory', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsNameLikeDirectory', + }).outputs({ + '#filteredList': '#matchingItsNameLikeDirectory', + }), + + withFilteredList({ + list: '#mainRelease.tracks', + filter: '#filterItsDirectoryLikeName', + }).outputs({ + '#filteredList': '#matchingItsDirectoryLikeName', + }), + + { + dependencies: [ + '#matchingItsNameLikeName', + '#matchingItsDirectoryLikeDirectory', + '#matchingItsNameLikeDirectory', + '#matchingItsDirectoryLikeName', + ], + + compute: (continuation, { + ['#matchingItsNameLikeName']: NLN, + ['#matchingItsDirectoryLikeDirectory']: DLD, + ['#matchingItsNameLikeDirectory']: NLD, + ['#matchingItsDirectoryLikeName']: DLN, + }) => continuation({ + ['#mainReleaseTrack']: + onlyItem(DLD) ?? + onlyItem(NLN) ?? + onlyItem(DLN) ?? + onlyItem(NLD) ?? + null, + }), + }, + + { + dependencies: ['#mainReleaseTrack', input.myself()], + + compute: (continuation, { + ['#mainReleaseTrack']: mainReleaseTrack, + [input.myself()]: thisTrack, + }) => continuation({ + ['#mainReleaseTrack']: + (mainReleaseTrack === thisTrack + ? null + : mainReleaseTrack), + }), + }, + ], +}); diff --git a/src/data/composite/things/track/withOtherReleases.js b/src/data/composite/things/track/withOtherReleases.js index 0639742f..bb3e8983 100644 --- a/src/data/composite/things/track/withOtherReleases.js +++ b/src/data/composite/things/track/withOtherReleases.js @@ -3,9 +3,6 @@ import {input, templateCompositeFrom} from '#composite'; -import {exitWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; - import withAllReleases from './withAllReleases.js'; export default templateCompositeFrom({ diff --git a/src/data/composite/things/track/withPropertyFromAlbum.js b/src/data/composite/things/track/withPropertyFromAlbum.js index e9c5b56e..a203c2e7 100644 --- a/src/data/composite/things/track/withPropertyFromAlbum.js +++ b/src/data/composite/things/track/withPropertyFromAlbum.js @@ -5,13 +5,12 @@ import {input, templateCompositeFrom} from '#composite'; import {withPropertyFromObject} from '#composite/data'; -import withAlbum from './withAlbum.js'; - export default templateCompositeFrom({ annotation: `withPropertyFromAlbum`, inputs: { property: input.staticValue({type: 'string'}), + internal: input({type: 'boolean', defaultValue: false}), }, outputs: ({ @@ -19,11 +18,21 @@ export default templateCompositeFrom({ }) => ['#album.' + property], steps: () => [ - withAlbum(), + // XXX: This is a ridiculous hack considering `defaultValue` above. + // If we were certain what was up, we'd just get around to fixing it LOL + { + dependencies: [input('internal')], + compute: (continuation, { + [input('internal')]: internal, + }) => continuation({ + ['#internal']: internal ?? false, + }), + }, withPropertyFromObject({ - object: '#album', + object: 'album', property: input('property'), + internal: '#internal', }), { diff --git a/src/data/composite/things/track/withPropertyFromMainRelease.js b/src/data/composite/things/track/withPropertyFromMainRelease.js index 393a4c63..c6f65653 100644 --- a/src/data/composite/things/track/withPropertyFromMainRelease.js +++ b/src/data/composite/things/track/withPropertyFromMainRelease.js @@ -10,10 +10,10 @@ import {input, templateCompositeFrom} from '#composite'; import {withResultOfAvailabilityCheck} from '#composite/control-flow'; import {withPropertyFromObject} from '#composite/data'; -import withMainRelease from './withMainRelease.js'; +import withMainReleaseTrack from './withMainReleaseTrack.js'; export default templateCompositeFrom({ - annotation: `inheritFromMainRelease`, + annotation: `withPropertyFromMainRelease`, inputs: { property: input({type: 'string'}), @@ -32,12 +32,12 @@ export default templateCompositeFrom({ : ['#mainReleaseValue'])), steps: () => [ - withMainRelease({ + withMainReleaseTrack({ notFoundValue: input('notFoundValue'), }), withResultOfAvailabilityCheck({ - from: '#mainRelease', + from: '#mainReleaseTrack', }), { @@ -61,7 +61,7 @@ export default templateCompositeFrom({ }, withPropertyFromObject({ - object: '#mainRelease', + object: '#mainReleaseTrack', property: input('property'), }), diff --git a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js index 7159a3f4..30c777b6 100644 --- a/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js +++ b/src/data/composite/things/track/withSuffixDirectoryFromAlbum.js @@ -1,8 +1,9 @@ import {input, templateCompositeFrom} from '#composite'; import {withResultOfAvailabilityCheck} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; -import withPropertyFromAlbum from './withPropertyFromAlbum.js'; +import withContainingTrackSection from './withContainingTrackSection.js'; export default templateCompositeFrom({ annotation: `withSuffixDirectoryFromAlbum`, @@ -36,18 +37,13 @@ export default templateCompositeFrom({ : continuation()), }, - withPropertyFromAlbum({ + withContainingTrackSection(), + + withPropertyFromObject({ + object: '#trackSection', property: input.value('suffixTrackDirectories'), + }).outputs({ + '#trackSection.suffixTrackDirectories': '#suffixDirectoryFromAlbum', }), - - { - dependencies: ['#album.suffixTrackDirectories'], - compute: (continuation, { - ['#album.suffixTrackDirectories']: suffixTrackDirectories, - }) => continuation({ - ['#suffixDirectoryFromAlbum']: - suffixTrackDirectories, - }), - }, ], }); diff --git a/src/data/composite/things/track/withTrackArtDate.js b/src/data/composite/things/track/withTrackArtDate.js index e2c4d8bc..9b7b61c7 100644 --- a/src/data/composite/things/track/withTrackArtDate.js +++ b/src/data/composite/things/track/withTrackArtDate.js @@ -1,11 +1,3 @@ -// Gets the date of cover art release. This represents only the track's own -// unique cover artwork, if any. -// -// If the 'fallback' option is false (the default), this will only output -// the track's own coverArtDate or its album's trackArtDate. If 'fallback' -// is set, and neither of these is available, it'll output the track's own -// date instead. - import {input, templateCompositeFrom} from '#composite'; import {isDate} from '#validators'; @@ -24,11 +16,6 @@ export default templateCompositeFrom({ defaultDependency: 'coverArtDate', acceptsNull: true, }), - - fallback: input({ - type: 'boolean', - defaultValue: false, - }), }, outputs: ['#trackArtDate'], @@ -57,20 +44,13 @@ export default templateCompositeFrom({ }), { - dependencies: [ - '#album.trackArtDate', - input('fallback'), - ], - + dependencies: ['#album.trackArtDate'], compute: (continuation, { ['#album.trackArtDate']: albumTrackArtDate, - [input('fallback')]: fallback, }) => (albumTrackArtDate ? continuation.raiseOutput({'#trackArtDate': albumTrackArtDate}) - : fallback - ? continuation() - : continuation.raiseOutput({'#trackArtDate': null})), + : continuation()), }, withDate().outputs({ diff --git a/src/data/composite/things/track/withTrackNumber.js b/src/data/composite/things/track/withTrackNumber.js new file mode 100644 index 00000000..61428e8c --- /dev/null +++ b/src/data/composite/things/track/withTrackNumber.js @@ -0,0 +1,50 @@ +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withIndexInList, withPropertiesFromObject} from '#composite/data'; + +import withContainingTrackSection from './withContainingTrackSection.js'; + +export default templateCompositeFrom({ + annotation: `withTrackNumber`, + + outputs: ['#trackNumber'], + + steps: () => [ + withContainingTrackSection(), + + // Zero is the fallback, not one, but in most albums the first track + // (and its intended output by this composition) will be one. + raiseOutputWithoutDependency({ + dependency: '#trackSection', + output: input.value({'#trackNumber': 0}), + }), + + withPropertiesFromObject({ + object: '#trackSection', + properties: input.value(['tracks', 'startCountingFrom']), + }), + + withIndexInList({ + list: '#trackSection.tracks', + item: input.myself(), + }), + + raiseOutputWithoutDependency({ + dependency: '#index', + output: input.value({'#trackNumber': 0}), + }), + + { + dependencies: ['#trackSection.startCountingFrom', '#index'], + compute: (continuation, { + ['#trackSection.startCountingFrom']: startCountingFrom, + ['#index']: index, + }) => continuation({ + ['#trackNumber']: + startCountingFrom + + index, + }), + }, + ], +}); diff --git a/src/data/composite/wiki-data/exitWithoutArtwork.js b/src/data/composite/wiki-data/exitWithoutArtwork.js new file mode 100644 index 00000000..8e799fda --- /dev/null +++ b/src/data/composite/wiki-data/exitWithoutArtwork.js @@ -0,0 +1,45 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isContributionList, isThing, strictArrayOf} from '#validators'; + +import {exitWithoutDependency} from '#composite/control-flow'; + +import withHasArtwork from './withHasArtwork.js'; + +export default templateCompositeFrom({ + annotation: `exitWithoutArtwork`, + + inputs: { + contribs: input({ + validate: isContributionList, + defaultValue: null, + }), + + artwork: input({ + validate: isThing, + defaultValue: null, + }), + + artworks: input({ + validate: strictArrayOf(isThing), + defaultValue: null, + }), + + value: input({ + defaultValue: null, + }), + }, + + steps: () => [ + withHasArtwork({ + contribs: input('contribs'), + artwork: input('artwork'), + artworks: input('artworks'), + }), + + exitWithoutDependency({ + dependency: '#hasArtwork', + mode: input.value('falsy'), + value: input('value'), + }), + ], +}); diff --git a/src/data/composite/wiki-data/index.js b/src/data/composite/wiki-data/index.js index be83e4c9..d70d7c56 100644 --- a/src/data/composite/wiki-data/index.js +++ b/src/data/composite/wiki-data/index.js @@ -5,24 +5,27 @@ // export {default as exitWithoutContribs} from './exitWithoutContribs.js'; +export {default as exitWithoutArtwork} from './exitWithoutArtwork.js'; export {default as gobbleSoupyFind} from './gobbleSoupyFind.js'; export {default as gobbleSoupyReverse} from './gobbleSoupyReverse.js'; +export {default as inputFindOptions} from './inputFindOptions.js'; export {default as inputNotFoundMode} from './inputNotFoundMode.js'; export {default as inputSoupyFind} from './inputSoupyFind.js'; export {default as inputSoupyReverse} from './inputSoupyReverse.js'; export {default as inputWikiData} from './inputWikiData.js'; +export {default as splitContentNodesAround} from './splitContentNodesAround.js'; export {default as withClonedThings} from './withClonedThings.js'; +export {default as withConstitutedArtwork} from './withConstitutedArtwork.js'; +export {default as withContentNodes} from './withContentNodes.js'; export {default as withContributionListSums} from './withContributionListSums.js'; -export {default as withCoverArtDate} from './withCoverArtDate.js'; export {default as withDirectory} from './withDirectory.js'; -export {default as withParsedCommentaryEntries} from './withParsedCommentaryEntries.js'; +export {default as withHasArtwork} from './withHasArtwork.js'; export {default as withRecontextualizedContributionList} from './withRecontextualizedContributionList.js'; export {default as withRedatedContributionList} from './withRedatedContributionList.js'; export {default as withResolvedAnnotatedReferenceList} from './withResolvedAnnotatedReferenceList.js'; export {default as withResolvedContribs} from './withResolvedContribs.js'; export {default as withResolvedReference} from './withResolvedReference.js'; export {default as withResolvedReferenceList} from './withResolvedReferenceList.js'; -export {default as withResolvedSeriesList} from './withResolvedSeriesList.js'; export {default as withReverseReferenceList} from './withReverseReferenceList.js'; export {default as withThingsSortedAlphabetically} from './withThingsSortedAlphabetically.js'; export {default as withUniqueReferencingThing} from './withUniqueReferencingThing.js'; diff --git a/src/data/composite/wiki-data/inputFindOptions.js b/src/data/composite/wiki-data/inputFindOptions.js new file mode 100644 index 00000000..07ed4bce --- /dev/null +++ b/src/data/composite/wiki-data/inputFindOptions.js @@ -0,0 +1,5 @@ +import {input} from '#composite'; + +export default function inputFindOptions() { + return input({type: 'object', defaultValue: null}); +} diff --git a/src/data/composite/wiki-data/splitContentNodesAround.js b/src/data/composite/wiki-data/splitContentNodesAround.js new file mode 100644 index 00000000..6648d8e1 --- /dev/null +++ b/src/data/composite/wiki-data/splitContentNodesAround.js @@ -0,0 +1,87 @@ +import {input, templateCompositeFrom} from '#composite'; +import {splitContentNodesAround} from '#replacer'; +import {anyOf, isFunction, validateInstanceOf} from '#validators'; + +import {withFilteredList, withMappedList, withUnflattenedList} + from '#composite/data'; + +export default templateCompositeFrom({ + annotation: `splitContentNodesAround`, + + inputs: { + nodes: input({type: 'array'}), + + around: input({ + validate: + anyOf(isFunction, validateInstanceOf(RegExp)), + }), + }, + + outputs: ['#contentNodeLists'], + + steps: () => [ + { + dependencies: [input('nodes'), input('around')], + + compute: (continuation, { + [input('nodes')]: nodes, + [input('around')]: splitter, + }) => continuation({ + ['#nodes']: + Array.from(splitContentNodesAround(nodes, splitter)), + }), + }, + + withMappedList({ + list: '#nodes', + map: input.value(node => node.type === 'separator'), + }).outputs({ + '#mappedList': '#separatorFilter', + }), + + withMappedList({ + list: '#separatorFilter', + filter: '#separatorFilter', + map: input.value((_node, index) => index), + }), + + withFilteredList({ + list: '#mappedList', + filter: '#separatorFilter', + }).outputs({ + '#filteredList': '#separatorIndices', + }), + + { + dependencies: ['#nodes', '#separatorFilter'], + + compute: (continuation, { + ['#nodes']: nodes, + ['#separatorFilter']: separatorFilter, + }) => continuation({ + ['#nodes']: + nodes.map((node, index) => + (separatorFilter[index] + ? null + : node)), + }), + }, + + { + dependencies: ['#separatorIndices'], + compute: (continuation, { + ['#separatorIndices']: separatorIndices, + }) => continuation({ + ['#unflattenIndices']: + [0, ...separatorIndices], + }), + }, + + withUnflattenedList({ + list: '#nodes', + indices: '#unflattenIndices', + }).outputs({ + '#unflattenedList': '#contentNodeLists', + }), + ], +}); diff --git a/src/data/composite/wiki-data/withConstitutedArtwork.js b/src/data/composite/wiki-data/withConstitutedArtwork.js new file mode 100644 index 00000000..28d719e2 --- /dev/null +++ b/src/data/composite/wiki-data/withConstitutedArtwork.js @@ -0,0 +1,60 @@ +import {input, templateCompositeFrom} from '#composite'; +import thingConstructors from '#things'; + +export default templateCompositeFrom({ + annotation: `withConstitutedArtwork`, + + inputs: { + thingProperty: input({type: 'string', acceptsNull: true}), + dimensionsFromThingProperty: input({type: 'string', acceptsNull: true}), + fileExtensionFromThingProperty: input({type: 'string', acceptsNull: true}), + dateFromThingProperty: input({type: 'string', acceptsNull: true}), + artistContribsFromThingProperty: input({type: 'string', acceptsNull: true}), + artistContribsArtistProperty: input({type: 'string', acceptsNull: true}), + artTagsFromThingProperty: input({type: 'string', acceptsNull: true}), + referencedArtworksFromThingProperty: input({type: 'string', acceptsNull: true}), + }, + + outputs: ['#constitutedArtwork'], + + steps: () => [ + { + dependencies: [ + input.myself(), + input('thingProperty'), + input('dimensionsFromThingProperty'), + input('fileExtensionFromThingProperty'), + input('dateFromThingProperty'), + input('artistContribsFromThingProperty'), + input('artistContribsArtistProperty'), + input('artTagsFromThingProperty'), + input('referencedArtworksFromThingProperty'), + ], + + compute: (continuation, { + [input.myself()]: myself, + [input('thingProperty')]: thingProperty, + [input('dimensionsFromThingProperty')]: dimensionsFromThingProperty, + [input('fileExtensionFromThingProperty')]: fileExtensionFromThingProperty, + [input('dateFromThingProperty')]: dateFromThingProperty, + [input('artistContribsFromThingProperty')]: artistContribsFromThingProperty, + [input('artistContribsArtistProperty')]: artistContribsArtistProperty, + [input('artTagsFromThingProperty')]: artTagsFromThingProperty, + [input('referencedArtworksFromThingProperty')]: referencedArtworksFromThingProperty, + }) => continuation({ + ['#constitutedArtwork']: + Object.assign(new thingConstructors.Artwork, { + thing: myself, + thingProperty, + dimensionsFromThingProperty, + fileExtensionFromThingProperty, + artistContribsFromThingProperty, + artistContribsArtistProperty, + artTagsFromThingProperty, + dateFromThingProperty, + referencedArtworksFromThingProperty, + }), + }), + }, + ], +}); diff --git a/src/data/composite/wiki-data/withContentNodes.js b/src/data/composite/wiki-data/withContentNodes.js new file mode 100644 index 00000000..d014d43b --- /dev/null +++ b/src/data/composite/wiki-data/withContentNodes.js @@ -0,0 +1,25 @@ +import {input, templateCompositeFrom} from '#composite'; +import {parseContentNodes} from '#replacer'; + +export default templateCompositeFrom({ + annotation: `withContentNodes`, + + inputs: { + from: input({type: 'string', acceptsNull: false}), + }, + + outputs: ['#contentNodes'], + + steps: () => [ + { + dependencies: [input('from')], + + compute: (continuation, { + [input('from')]: string, + }) => continuation({ + ['#contentNodes']: + parseContentNodes(string), + }), + }, + ], +}); diff --git a/src/data/composite/wiki-data/withCoverArtDate.js b/src/data/composite/wiki-data/withCoverArtDate.js deleted file mode 100644 index 0c644c77..00000000 --- a/src/data/composite/wiki-data/withCoverArtDate.js +++ /dev/null @@ -1,70 +0,0 @@ -// Gets the current thing's coverArtDate, or, if the 'fallback' option is set, -// the thing's date. This is always null if the thing doesn't actually have -// any coverArtistContribs. - -import {input, templateCompositeFrom} from '#composite'; -import {isDate} from '#validators'; - -import {raiseOutputWithoutDependency} from '#composite/control-flow'; - -import withResolvedContribs from './withResolvedContribs.js'; - -export default templateCompositeFrom({ - annotation: `withCoverArtDate`, - - inputs: { - from: input({ - validate: isDate, - defaultDependency: 'coverArtDate', - acceptsNull: true, - }), - - fallback: input({ - type: 'boolean', - defaultValue: false, - }), - }, - - outputs: ['#coverArtDate'], - - steps: () => [ - withResolvedContribs({ - from: 'coverArtistContribs', - date: input.value(null), - }), - - raiseOutputWithoutDependency({ - dependency: '#resolvedContribs', - mode: input.value('empty'), - output: input.value({'#coverArtDate': null}), - }), - - { - dependencies: [input('from')], - compute: (continuation, { - [input('from')]: from, - }) => - (from - ? continuation.raiseOutput({'#coverArtDate': from}) - : continuation()), - }, - - { - dependencies: [input('fallback')], - compute: (continuation, { - [input('fallback')]: fallback, - }) => - (fallback - ? continuation() - : continuation.raiseOutput({'#coverArtDate': null})), - }, - - { - dependencies: ['date'], - compute: (continuation, {date}) => - (date - ? continuation.raiseOutput({'#coverArtDate': date}) - : continuation.raiseOutput({'#coverArtDate': null})), - }, - ], -}); diff --git a/src/data/composite/wiki-data/withHasArtwork.js b/src/data/composite/wiki-data/withHasArtwork.js new file mode 100644 index 00000000..9c22f439 --- /dev/null +++ b/src/data/composite/wiki-data/withHasArtwork.js @@ -0,0 +1,97 @@ +import {input, templateCompositeFrom} from '#composite'; +import {isContributionList, isThing, strictArrayOf} from '#validators'; + +import {raiseOutputWithoutDependency, withResultOfAvailabilityCheck} + from '#composite/control-flow'; +import {fillMissingListItems, withFlattenedList, withPropertyFromList} + from '#composite/data'; + +export default templateCompositeFrom({ + annotation: 'withHasArtwork', + + inputs: { + contribs: input({ + validate: isContributionList, + defaultValue: null, + }), + + artwork: input({ + validate: isThing, + defaultValue: null, + }), + + artworks: input({ + validate: strictArrayOf(isThing), + defaultValue: null, + }), + }, + + outputs: ['#hasArtwork'], + + steps: () => [ + withResultOfAvailabilityCheck({ + from: input('contribs'), + mode: input.value('empty'), + }), + + { + dependencies: ['#availability'], + compute: (continuation, { + ['#availability']: availability, + }) => + (availability + ? continuation.raiseOutput({ + ['#hasArtwork']: true, + }) + : continuation()), + }, + + { + dependencies: [input('artwork'), input('artworks')], + compute: (continuation, { + [input('artwork')]: artwork, + [input('artworks')]: artworks, + }) => + continuation({ + ['#artworks']: + (artwork && artworks + ? [artwork, ...artworks] + : artwork + ? [artwork] + : artworks + ? artworks + : []), + }), + }, + + raiseOutputWithoutDependency({ + dependency: '#artworks', + mode: input.value('empty'), + output: input.value({'#hasArtwork': false}), + }), + + withPropertyFromList({ + list: '#artworks', + property: input.value('artistContribs'), + internal: input.value(true), + }), + + // Since we're getting the update value for each artwork's artistContribs, + // it may not be set at all, and in that case won't be exposing as []. + fillMissingListItems({ + list: '#artworks.artistContribs', + fill: input.value([]), + }), + + withFlattenedList({ + list: '#artworks.artistContribs', + }), + + withResultOfAvailabilityCheck({ + from: '#flattenedList', + mode: input.value('empty'), + }).outputs({ + '#availability': '#hasArtwork', + }), + ], +}); diff --git a/src/data/composite/wiki-data/withParsedCommentaryEntries.js b/src/data/composite/wiki-data/withParsedCommentaryEntries.js deleted file mode 100644 index 9bf4278c..00000000 --- a/src/data/composite/wiki-data/withParsedCommentaryEntries.js +++ /dev/null @@ -1,260 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isCommentary} from '#validators'; -import {commentaryRegexCaseSensitive} from '#wiki-data'; - -import { - fillMissingListItems, - withFlattenedList, - withPropertiesFromList, - withUnflattenedList, -} from '#composite/data'; - -import inputSoupyFind from './inputSoupyFind.js'; -import withResolvedReferenceList from './withResolvedReferenceList.js'; - -export default templateCompositeFrom({ - annotation: `withParsedCommentaryEntries`, - - inputs: { - from: input({validate: isCommentary}), - }, - - outputs: ['#parsedCommentaryEntries'], - - steps: () => [ - { - dependencies: [input('from')], - - compute: (continuation, { - [input('from')]: commentaryText, - }) => continuation({ - ['#rawMatches']: - Array.from(commentaryText.matchAll(commentaryRegexCaseSensitive)), - }), - }, - - withPropertiesFromList({ - list: '#rawMatches', - properties: input.value([ - '0', // The entire match as a string. - 'groups', - 'index', - ]), - }).outputs({ - '#rawMatches.0': '#rawMatches.text', - '#rawMatches.groups': '#rawMatches.groups', - '#rawMatches.index': '#rawMatches.startIndex', - }), - - { - dependencies: [ - '#rawMatches.text', - '#rawMatches.startIndex', - ], - - compute: (continuation, { - ['#rawMatches.text']: text, - ['#rawMatches.startIndex']: startIndex, - }) => continuation({ - ['#rawMatches.endIndex']: - stitchArrays({text, startIndex}) - .map(({text, startIndex}) => startIndex + text.length), - }), - }, - - { - dependencies: [ - input('from'), - '#rawMatches.startIndex', - '#rawMatches.endIndex', - ], - - compute: (continuation, { - [input('from')]: commentaryText, - ['#rawMatches.startIndex']: startIndex, - ['#rawMatches.endIndex']: endIndex, - }) => continuation({ - ['#entries.body']: - stitchArrays({startIndex, endIndex}) - .map(({endIndex}, index, stitched) => - (index === stitched.length - 1 - ? commentaryText.slice(endIndex) - : commentaryText.slice( - endIndex, - stitched[index + 1].startIndex))) - .map(body => body.trim()), - }), - }, - - withPropertiesFromList({ - list: '#rawMatches.groups', - prefix: input.value('#entries'), - properties: input.value([ - 'artistReferences', - 'artistDisplayText', - 'annotation', - 'date', - 'secondDate', - 'dateKind', - 'accessDate', - 'accessKind', - ]), - }), - - // The artistReferences group will always have a value, since it's required - // for the line to match in the first place. - - { - dependencies: ['#entries.artistReferences'], - compute: (continuation, { - ['#entries.artistReferences']: artistReferenceTexts, - }) => continuation({ - ['#entries.artistReferences']: - artistReferenceTexts - .map(text => text.split(',').map(ref => ref.trim())), - }), - }, - - withFlattenedList({ - list: '#entries.artistReferences', - }), - - withResolvedReferenceList({ - list: '#flattenedList', - find: inputSoupyFind.input('artist'), - notFoundMode: input.value('null'), - }), - - withUnflattenedList({ - list: '#resolvedReferenceList', - }).outputs({ - '#unflattenedList': '#entries.artists', - }), - - fillMissingListItems({ - list: '#entries.artistDisplayText', - fill: input.value(null), - }), - - fillMissingListItems({ - list: '#entries.annotation', - fill: input.value(null), - }), - - { - dependencies: ['#entries.annotation'], - compute: (continuation, { - ['#entries.annotation']: annotation, - }) => continuation({ - ['#entries.webArchiveDate']: - annotation - .map(text => text?.match(/https?:\/\/web.archive.org\/web\/([0-9]{8,8})[0-9]*\//)) - .map(match => match?.[1]) - .map(dateText => - (dateText - ? dateText.slice(0, 4) + '/' + - dateText.slice(4, 6) + '/' + - dateText.slice(6, 8) - : null)), - }), - }, - - { - dependencies: ['#entries.date'], - compute: (continuation, { - ['#entries.date']: date, - }) => continuation({ - ['#entries.date']: - date - .map(date => date ? new Date(date) : null), - }), - }, - - { - dependencies: ['#entries.secondDate'], - compute: (continuation, { - ['#entries.secondDate']: secondDate, - }) => continuation({ - ['#entries.secondDate']: - secondDate - .map(date => date ? new Date(date) : null), - }), - }, - - fillMissingListItems({ - list: '#entries.dateKind', - fill: input.value(null), - }), - - { - dependencies: ['#entries.accessDate', '#entries.webArchiveDate'], - compute: (continuation, { - ['#entries.accessDate']: accessDate, - ['#entries.webArchiveDate']: webArchiveDate, - }) => continuation({ - ['#entries.accessDate']: - stitchArrays({accessDate, webArchiveDate}) - .map(({accessDate, webArchiveDate}) => - accessDate ?? - webArchiveDate ?? - null) - .map(date => date ? new Date(date) : date), - }), - }, - - { - dependencies: ['#entries.accessKind', '#entries.webArchiveDate'], - compute: (continuation, { - ['#entries.accessKind']: accessKind, - ['#entries.webArchiveDate']: webArchiveDate, - }) => continuation({ - ['#entries.accessKind']: - stitchArrays({accessKind, webArchiveDate}) - .map(({accessKind, webArchiveDate}) => - accessKind ?? - (webArchiveDate && 'captured') ?? - null), - }), - }, - - { - dependencies: [ - '#entries.artists', - '#entries.artistDisplayText', - '#entries.annotation', - '#entries.date', - '#entries.secondDate', - '#entries.dateKind', - '#entries.accessDate', - '#entries.accessKind', - '#entries.body', - ], - - compute: (continuation, { - ['#entries.artists']: artists, - ['#entries.artistDisplayText']: artistDisplayText, - ['#entries.annotation']: annotation, - ['#entries.date']: date, - ['#entries.secondDate']: secondDate, - ['#entries.dateKind']: dateKind, - ['#entries.accessDate']: accessDate, - ['#entries.accessKind']: accessKind, - ['#entries.body']: body, - }) => continuation({ - ['#parsedCommentaryEntries']: - stitchArrays({ - artists, - artistDisplayText, - annotation, - date, - secondDate, - dateKind, - accessDate, - accessKind, - body, - }), - }), - }, - ], -}); diff --git a/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js b/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js index c9a7c058..670dc422 100644 --- a/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js +++ b/src/data/composite/wiki-data/withResolvedAnnotatedReferenceList.js @@ -1,12 +1,13 @@ import {input, templateCompositeFrom} from '#composite'; import {stitchArrays} from '#sugar'; -import {isDate, isObject, validateArrayItems} from '#validators'; +import {isObject, validateArrayItems} from '#validators'; import {withPropertyFromList} from '#composite/data'; import {raiseOutputWithoutDependency, withAvailabilityFilter} from '#composite/control-flow'; +import inputFindOptions from './inputFindOptions.js'; import inputSoupyFind from './inputSoupyFind.js'; import inputNotFoundMode from './inputNotFoundMode.js'; import inputWikiData from './inputWikiData.js'; @@ -22,17 +23,13 @@ export default templateCompositeFrom({ acceptsNull: true, }), - date: input({ - validate: isDate, - acceptsNull: true, - }), - reference: input({type: 'string', defaultValue: 'reference'}), annotation: input({type: 'string', defaultValue: 'annotation'}), thing: input({type: 'string', defaultValue: 'thing'}), data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), notFoundMode: inputNotFoundMode(), }, @@ -66,6 +63,7 @@ export default templateCompositeFrom({ list: '#references', data: input('data'), find: input('find'), + findOptions: input('findOptions'), notFoundMode: input.value('null'), }), @@ -91,17 +89,6 @@ export default templateCompositeFrom({ }), }, - { - dependencies: ['#matches', input('date')], - compute: (continuation, { - ['#matches']: matches, - [input('date')]: date, - }) => continuation({ - ['#matches']: - matches.map(match => ({...match, date})), - }), - }, - withAvailabilityFilter({ from: '#resolvedReferenceList', }), diff --git a/src/data/composite/wiki-data/withResolvedReference.js b/src/data/composite/wiki-data/withResolvedReference.js index 6f422194..d9a05367 100644 --- a/src/data/composite/wiki-data/withResolvedReference.js +++ b/src/data/composite/wiki-data/withResolvedReference.js @@ -8,6 +8,7 @@ import {input, templateCompositeFrom} from '#composite'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import gobbleSoupyFind from './gobbleSoupyFind.js'; +import inputFindOptions from './inputFindOptions.js'; import inputSoupyFind from './inputSoupyFind.js'; import inputWikiData from './inputWikiData.js'; @@ -17,8 +18,9 @@ export default templateCompositeFrom({ inputs: { ref: input({type: 'string', acceptsNull: true}), - data: inputWikiData({allowMixedTypes: false}), + data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), }, outputs: ['#resolvedReference'], @@ -36,21 +38,35 @@ export default templateCompositeFrom({ }), { + dependencies: [input('findOptions')], + compute: (continuation, { + [input('findOptions')]: findOptions, + }) => continuation({ + ['#findOptions']: + (findOptions + ? {...findOptions, mode: 'quiet'} + : {mode: 'quiet'}), + }), + }, + + { dependencies: [ input('ref'), input('data'), '#find', + '#findOptions', ], compute: (continuation, { [input('ref')]: ref, [input('data')]: data, ['#find']: findFunction, + ['#findOptions']: findOptions, }) => continuation({ ['#resolvedReference']: (data - ? findFunction(ref, data, {mode: 'quiet'}) ?? null - : findFunction(ref, {mode: 'quiet'}) ?? null), + ? findFunction(ref, data, findOptions) ?? null + : findFunction(ref, findOptions) ?? null), }), }, ], diff --git a/src/data/composite/wiki-data/withResolvedReferenceList.js b/src/data/composite/wiki-data/withResolvedReferenceList.js index 9dc960dd..14ce6919 100644 --- a/src/data/composite/wiki-data/withResolvedReferenceList.js +++ b/src/data/composite/wiki-data/withResolvedReferenceList.js @@ -11,6 +11,7 @@ import {raiseOutputWithoutDependency, withAvailabilityFilter} import {withMappedList} from '#composite/data'; import gobbleSoupyFind from './gobbleSoupyFind.js'; +import inputFindOptions from './inputFindOptions.js'; import inputNotFoundMode from './inputNotFoundMode.js'; import inputSoupyFind from './inputSoupyFind.js'; import inputWikiData from './inputWikiData.js'; @@ -27,6 +28,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), notFoundMode: inputNotFoundMode(), }, @@ -47,15 +49,28 @@ export default templateCompositeFrom({ }), { - dependencies: [input('data'), '#find'], + dependencies: [input('findOptions')], + compute: (continuation, { + [input('findOptions')]: findOptions, + }) => continuation({ + ['#findOptions']: + (findOptions + ? {...findOptions, mode: 'quiet'} + : {mode: 'quiet'}), + }), + }, + + { + dependencies: [input('data'), '#find', '#findOptions'], compute: (continuation, { [input('data')]: data, ['#find']: findFunction, + ['#findOptions']: findOptions, }) => continuation({ ['#map']: (data - ? ref => findFunction(ref, data, {mode: 'quiet'}) - : ref => findFunction(ref, {mode: 'quiet'})), + ? ref => findFunction(ref, data, findOptions) + : ref => findFunction(ref, findOptions)), }), }, diff --git a/src/data/composite/wiki-data/withResolvedSeriesList.js b/src/data/composite/wiki-data/withResolvedSeriesList.js deleted file mode 100644 index deaab466..00000000 --- a/src/data/composite/wiki-data/withResolvedSeriesList.js +++ /dev/null @@ -1,130 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {stitchArrays} from '#sugar'; -import {isSeriesList, validateThing} from '#validators'; - -import {raiseOutputWithoutDependency} from '#composite/control-flow'; - -import { - fillMissingListItems, - withFlattenedList, - withUnflattenedList, - withPropertiesFromList, -} from '#composite/data'; - -import inputSoupyFind from './inputSoupyFind.js'; -import withResolvedReferenceList from './withResolvedReferenceList.js'; - -export default templateCompositeFrom({ - annotation: `withResolvedSeriesList`, - - inputs: { - group: input({ - validate: validateThing({referenceType: 'group'}), - }), - - list: input({ - validate: isSeriesList, - acceptsNull: true, - }), - }, - - outputs: ['#resolvedSeriesList'], - - steps: () => [ - raiseOutputWithoutDependency({ - dependency: input('list'), - mode: input.value('empty'), - output: input.value({ - ['#resolvedSeriesList']: [], - }), - }), - - withPropertiesFromList({ - list: input('list'), - prefix: input.value('#serieses'), - properties: input.value([ - 'name', - 'description', - 'albums', - - 'showAlbumArtists', - ]), - }), - - fillMissingListItems({ - list: '#serieses.albums', - fill: input.value([]), - }), - - withFlattenedList({ - list: '#serieses.albums', - }), - - withResolvedReferenceList({ - list: '#flattenedList', - find: inputSoupyFind.input('album'), - notFoundMode: input.value('null'), - }), - - withUnflattenedList({ - list: '#resolvedReferenceList', - }).outputs({ - '#unflattenedList': '#serieses.albums', - }), - - fillMissingListItems({ - list: '#serieses.description', - fill: input.value(null), - }), - - fillMissingListItems({ - list: '#serieses.showAlbumArtists', - fill: input.value(null), - }), - - { - dependencies: [ - '#serieses.name', - '#serieses.description', - '#serieses.albums', - - '#serieses.showAlbumArtists', - ], - - compute: (continuation, { - ['#serieses.name']: name, - ['#serieses.description']: description, - ['#serieses.albums']: albums, - - ['#serieses.showAlbumArtists']: showAlbumArtists, - }) => continuation({ - ['#seriesProperties']: - stitchArrays({ - name, - description, - albums, - - showAlbumArtists, - }).map(properties => ({ - ...properties, - group: input - })) - }), - }, - - { - dependencies: ['#seriesProperties', input('group')], - compute: (continuation, { - ['#seriesProperties']: seriesProperties, - [input('group')]: group, - }) => continuation({ - ['#resolvedSeriesList']: - seriesProperties - .map(properties => ({ - ...properties, - group, - })), - }), - }, - ], -}); diff --git a/src/data/composite/wiki-properties/additionalFiles.js b/src/data/composite/wiki-properties/additionalFiles.js deleted file mode 100644 index 6760527a..00000000 --- a/src/data/composite/wiki-properties/additionalFiles.js +++ /dev/null @@ -1,30 +0,0 @@ -// This is a somewhat more involved data structure - it's for additional -// or "bonus" files associated with albums or tracks (or anything else). -// It's got this form: -// -// [ -// {title: 'Booklet', files: ['Booklet.pdf']}, -// { -// title: 'Wallpaper', -// description: 'Cool Wallpaper!', -// files: ['1440x900.png', '1920x1080.png'] -// }, -// {title: 'Alternate Covers', description: null, files: [...]}, -// ... -// ] -// - -import {isAdditionalFileList} from '#validators'; - -// TODO: Not templateCompositeFrom. - -export default function() { - return { - flags: {update: true, expose: true}, - update: {validate: isAdditionalFileList}, - expose: { - transform: (additionalFiles) => - additionalFiles ?? [], - }, - }; -} diff --git a/src/data/composite/wiki-properties/additionalNameList.js b/src/data/composite/wiki-properties/additionalNameList.js deleted file mode 100644 index c5971d4a..00000000 --- a/src/data/composite/wiki-properties/additionalNameList.js +++ /dev/null @@ -1,14 +0,0 @@ -// A list of additional names! These can be used for a variety of purposes, -// e.g. providing extra searchable titles, localizations, romanizations or -// original titles, and so on. Each item has a name and, optionally, a -// descriptive annotation. - -import {isAdditionalNameList} from '#validators'; - -export default function() { - return { - flags: {update: true, expose: true}, - update: {validate: isAdditionalNameList}, - expose: {transform: value => value ?? []}, - }; -} diff --git a/src/data/composite/wiki-properties/annotatedReferenceList.js b/src/data/composite/wiki-properties/annotatedReferenceList.js index bb6875f1..aea0f22c 100644 --- a/src/data/composite/wiki-properties/annotatedReferenceList.js +++ b/src/data/composite/wiki-properties/annotatedReferenceList.js @@ -2,7 +2,6 @@ import {input, templateCompositeFrom} from '#composite'; import { isContentString, - isDate, optional, validateArrayItems, validateProperties, @@ -10,8 +9,13 @@ import { } from '#validators'; import {exposeDependency} from '#composite/control-flow'; -import {inputSoupyFind, inputWikiData, withResolvedAnnotatedReferenceList} - from '#composite/wiki-data'; + +import { + inputFindOptions, + inputSoupyFind, + inputWikiData, + withResolvedAnnotatedReferenceList, +} from '#composite/wiki-data'; import {referenceListInputDescriptions, referenceListUpdateDescription} from './helpers/reference-list-helpers.js'; @@ -26,11 +30,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), - - date: input({ - validate: isDate, - acceptsNull: true, - }), + findOptions: inputFindOptions(), reference: input.staticValue({type: 'string', defaultValue: 'reference'}), annotation: input.staticValue({type: 'string', defaultValue: 'annotation'}), @@ -57,14 +57,13 @@ export default templateCompositeFrom({ withResolvedAnnotatedReferenceList({ list: input.updateValue(), - date: input('date'), - reference: input('reference'), annotation: input('annotation'), thing: input('thing'), data: input('data'), find: input('find'), + findOptions: input('findOptions'), }), exposeDependency({dependency: '#resolvedAnnotatedReferenceList'}), diff --git a/src/data/composite/wiki-properties/canonicalBase.js b/src/data/composite/wiki-properties/canonicalBase.js new file mode 100644 index 00000000..81740d6c --- /dev/null +++ b/src/data/composite/wiki-properties/canonicalBase.js @@ -0,0 +1,16 @@ +import {isURL} from '#validators'; + +export default function() { + return { + flags: {update: true, expose: true}, + update: {validate: isURL}, + expose: { + transform: (value) => + (value === null + ? null + : value.endsWith('/') + ? value + : value + '/'), + }, + }; +} diff --git a/src/data/composite/wiki-properties/commentary.js b/src/data/composite/wiki-properties/commentary.js deleted file mode 100644 index 9625278d..00000000 --- a/src/data/composite/wiki-properties/commentary.js +++ /dev/null @@ -1,30 +0,0 @@ -// Artist commentary! Generally present on tracks and albums. - -import {input, templateCompositeFrom} from '#composite'; -import {isCommentary} from '#validators'; - -import {exitWithoutDependency, exposeDependency} - from '#composite/control-flow'; -import {withParsedCommentaryEntries} from '#composite/wiki-data'; - -export default templateCompositeFrom({ - annotation: `commentary`, - - compose: false, - - steps: () => [ - exitWithoutDependency({ - dependency: input.updateValue({validate: isCommentary}), - mode: input.value('falsy'), - value: input.value([]), - }), - - withParsedCommentaryEntries({ - from: input.updateValue(), - }), - - exposeDependency({ - dependency: '#parsedCommentaryEntries', - }), - ], -}); diff --git a/src/data/composite/wiki-properties/commentatorArtists.js b/src/data/composite/wiki-properties/commentatorArtists.js index c5c14769..54d3e1a5 100644 --- a/src/data/composite/wiki-properties/commentatorArtists.js +++ b/src/data/composite/wiki-properties/commentatorArtists.js @@ -7,7 +7,6 @@ import {exitWithoutDependency, exposeDependency} from '#composite/control-flow'; import {withFlattenedList, withPropertyFromList, withUniqueItemsOnly} from '#composite/data'; -import {withParsedCommentaryEntries} from '#composite/wiki-data'; export default templateCompositeFrom({ annotation: `commentatorArtists`, @@ -21,19 +20,13 @@ export default templateCompositeFrom({ value: input.value([]), }), - withParsedCommentaryEntries({ - from: 'commentary', - }), - withPropertyFromList({ - list: '#parsedCommentaryEntries', + list: 'commentary', property: input.value('artists'), - }).outputs({ - '#parsedCommentaryEntries.artists': '#artistLists', }), withFlattenedList({ - list: '#artistLists', + list: '#commentary.artists', }).outputs({ '#flattenedList': '#artists', }), diff --git a/src/data/composite/wiki-properties/constitutibleArtwork.js b/src/data/composite/wiki-properties/constitutibleArtwork.js new file mode 100644 index 00000000..48f4211a --- /dev/null +++ b/src/data/composite/wiki-properties/constitutibleArtwork.js @@ -0,0 +1,70 @@ +// This composition does not actually inspect the values of any properties +// specified, so it's not responsible for determining whether a constituted +// artwork should exist at all. + +import {input, templateCompositeFrom} from '#composite'; +import {withEntries} from '#sugar'; +import Thing from '#thing'; +import {validateThing} from '#validators'; + +import {exposeDependency, exposeUpdateValueOrContinue} + from '#composite/control-flow'; +import {withConstitutedArtwork} from '#composite/wiki-data'; + +const template = templateCompositeFrom({ + annotation: `constitutibleArtwork`, + + compose: false, + + inputs: { + thingProperty: input({type: 'string', acceptsNull: true}), + dimensionsFromThingProperty: input({type: 'string', acceptsNull: true}), + fileExtensionFromThingProperty: input({type: 'string', acceptsNull: true}), + dateFromThingProperty: input({type: 'string', acceptsNull: true}), + artistContribsFromThingProperty: input({type: 'string', acceptsNull: true}), + artistContribsArtistProperty: input({type: 'string', acceptsNull: true}), + artTagsFromThingProperty: input({type: 'string', acceptsNull: true}), + referencedArtworksFromThingProperty: input({type: 'string', acceptsNull: true}), + }, + + steps: () => [ + exposeUpdateValueOrContinue({ + validate: input.value( + validateThing({ + referenceType: 'artwork', + })), + }), + + withConstitutedArtwork({ + thingProperty: input('thingProperty'), + dimensionsFromThingProperty: input('dimensionsFromThingProperty'), + fileExtensionFromThingProperty: input('fileExtensionFromThingProperty'), + dateFromThingProperty: input('dateFromThingProperty'), + artistContribsFromThingProperty: input('artistContribsFromThingProperty'), + artistContribsArtistProperty: input('artistContribsArtistProperty'), + artTagsFromThingProperty: input('artTagsFromThingProperty'), + referencedArtworksFromThingProperty: input('referencedArtworksFromThingProperty'), + }), + + exposeDependency({ + dependency: '#constitutedArtwork', + }), + ], +}); + +template.fromYAMLFieldSpec = function(field) { + const {[Thing.yamlDocumentSpec]: documentSpec} = this; + + const {provide} = documentSpec.fields[field].transform; + + const inputs = + withEntries(provide, entries => + entries.map(([property, value]) => [ + property, + input.value(value), + ])); + + return template(inputs); +}; + +export default template; diff --git a/src/data/composite/wiki-properties/constitutibleArtworkList.js b/src/data/composite/wiki-properties/constitutibleArtworkList.js new file mode 100644 index 00000000..dad3a957 --- /dev/null +++ b/src/data/composite/wiki-properties/constitutibleArtworkList.js @@ -0,0 +1,72 @@ +// This composition does not actually inspect the values of any properties +// specified, so it's not responsible for determining whether a constituted +// artwork should exist at all. + +import {input, templateCompositeFrom} from '#composite'; +import {withEntries} from '#sugar'; +import Thing from '#thing'; +import {validateWikiData} from '#validators'; + +import {exposeUpdateValueOrContinue} from '#composite/control-flow'; +import {withConstitutedArtwork} from '#composite/wiki-data'; + +const template = templateCompositeFrom({ + annotation: `constitutibleArtworkList`, + + compose: false, + + inputs: { + thingProperty: input({type: 'string', acceptsNull: true}), + dimensionsFromThingProperty: input({type: 'string', acceptsNull: true}), + fileExtensionFromThingProperty: input({type: 'string', acceptsNull: true}), + dateFromThingProperty: input({type: 'string', acceptsNull: true}), + artistContribsFromThingProperty: input({type: 'string', acceptsNull: true}), + artistContribsArtistProperty: input({type: 'string', acceptsNull: true}), + artTagsFromThingProperty: input({type: 'string', acceptsNull: true}), + referencedArtworksFromThingProperty: input({type: 'string', acceptsNull: true}), + }, + + steps: () => [ + exposeUpdateValueOrContinue({ + validate: input.value( + validateWikiData({ + referenceType: 'artwork', + })), + }), + + withConstitutedArtwork({ + thingProperty: input('thingProperty'), + dimensionsFromThingProperty: input('dimensionsFromThingProperty'), + fileExtensionFromThingProperty: input('fileExtensionFromThingProperty'), + dateFromThingProperty: input('dateFromThingProperty'), + artistContribsFromThingProperty: input('artistContribsFromThingProperty'), + artistContribsArtistProperty: input('artistContribsArtistProperty'), + artTagsFromThingProperty: input('artTagsFromThingProperty'), + referencedArtworksFromThingProperty: input('referencedArtworksFromThingProperty'), + }), + + { + dependencies: ['#constitutedArtwork'], + compute: ({ + ['#constitutedArtwork']: constitutedArtwork, + }) => [constitutedArtwork], + }, + ], +}); + +template.fromYAMLFieldSpec = function(field) { + const {[Thing.yamlDocumentSpec]: documentSpec} = this; + + const {provide} = documentSpec.fields[field].transform; + + const inputs = + withEntries(provide, entries => + entries.map(([property, value]) => [ + property, + input.value(value), + ])); + + return template(inputs); +}; + +export default template; diff --git a/src/data/composite/wiki-properties/directory.js b/src/data/composite/wiki-properties/directory.js index 9ca2a204..1756a8e5 100644 --- a/src/data/composite/wiki-properties/directory.js +++ b/src/data/composite/wiki-properties/directory.js @@ -18,6 +18,7 @@ export default templateCompositeFrom({ name: input({ validate: isName, defaultDependency: 'name', + acceptsNull: true, }), suffix: input({ diff --git a/src/data/composite/wiki-properties/index.js b/src/data/composite/wiki-properties/index.js index 4aaaeb72..57a2b8f2 100644 --- a/src/data/composite/wiki-properties/index.js +++ b/src/data/composite/wiki-properties/index.js @@ -3,12 +3,12 @@ // Entries here may depend on entries in #composite/control-flow, // #composite/data, and #composite/wiki-data. -export {default as additionalFiles} from './additionalFiles.js'; -export {default as additionalNameList} from './additionalNameList.js'; export {default as annotatedReferenceList} from './annotatedReferenceList.js'; +export {default as canonicalBase} from './canonicalBase.js'; export {default as color} from './color.js'; -export {default as commentary} from './commentary.js'; export {default as commentatorArtists} from './commentatorArtists.js'; +export {default as constitutibleArtwork} from './constitutibleArtwork.js'; +export {default as constitutibleArtworkList} from './constitutibleArtworkList.js'; export {default as contentString} from './contentString.js'; export {default as contribsPresent} from './contribsPresent.js'; export {default as contributionList} from './contributionList.js'; @@ -22,7 +22,6 @@ export {default as name} from './name.js'; export {default as referenceList} from './referenceList.js'; export {default as referencedArtworkList} from './referencedArtworkList.js'; export {default as reverseReferenceList} from './reverseReferenceList.js'; -export {default as seriesList} from './seriesList.js'; export {default as simpleDate} from './simpleDate.js'; export {default as simpleString} from './simpleString.js'; export {default as singleReference} from './singleReference.js'; diff --git a/src/data/composite/wiki-properties/referenceList.js b/src/data/composite/wiki-properties/referenceList.js index 4f8207b5..663349ee 100644 --- a/src/data/composite/wiki-properties/referenceList.js +++ b/src/data/composite/wiki-properties/referenceList.js @@ -11,8 +11,13 @@ import {input, templateCompositeFrom} from '#composite'; import {validateReferenceList} from '#validators'; import {exposeDependency} from '#composite/control-flow'; -import {inputSoupyFind, inputWikiData, withResolvedReferenceList} - from '#composite/wiki-data'; + +import { + inputFindOptions, + inputSoupyFind, + inputWikiData, + withResolvedReferenceList, +} from '#composite/wiki-data'; import {referenceListInputDescriptions, referenceListUpdateDescription} from './helpers/reference-list-helpers.js'; @@ -27,6 +32,7 @@ export default templateCompositeFrom({ data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), + findOptions: inputFindOptions(), }, update: @@ -39,6 +45,7 @@ export default templateCompositeFrom({ list: input.updateValue(), data: input('data'), find: input('find'), + findOptions: input('findOptions'), }), exposeDependency({dependency: '#resolvedReferenceList'}), diff --git a/src/data/composite/wiki-properties/referencedArtworkList.js b/src/data/composite/wiki-properties/referencedArtworkList.js index 819b2f43..4f243493 100644 --- a/src/data/composite/wiki-properties/referencedArtworkList.js +++ b/src/data/composite/wiki-properties/referencedArtworkList.js @@ -1,7 +1,5 @@ import {input, templateCompositeFrom} from '#composite'; import find from '#find'; -import {isDate} from '#validators'; -import {combineWikiDataArrays} from '#wiki-data'; import annotatedReferenceList from './annotatedReferenceList.js'; @@ -10,47 +8,24 @@ export default templateCompositeFrom({ compose: false, - inputs: { - date: input({ - validate: isDate, - acceptsNull: true, - }), - }, - steps: () => [ { - dependencies: [ - 'albumData', - 'trackData', - ], - - compute: (continuation, { - albumData, - trackData, - }) => continuation({ - ['#data']: - combineWikiDataArrays([ - albumData, - trackData, - ]), - }), - }, - - { compute: (continuation) => continuation({ ['#find']: find.mixed({ - track: find.trackWithArtwork, - album: find.albumWithArtwork, + track: find.trackPrimaryArtwork, + album: find.albumPrimaryArtwork, }), }), }, annotatedReferenceList({ referenceType: input.value(['album', 'track']), - data: '#data', + + data: 'artworkData', find: '#find', - date: input('date'), + + thing: input.value('artwork'), }), ], }); diff --git a/src/data/composite/wiki-properties/seriesList.js b/src/data/composite/wiki-properties/seriesList.js deleted file mode 100644 index 2a101b45..00000000 --- a/src/data/composite/wiki-properties/seriesList.js +++ /dev/null @@ -1,31 +0,0 @@ -import {input, templateCompositeFrom} from '#composite'; -import {isSeriesList, validateThing} from '#validators'; - -import {exposeDependency} from '#composite/control-flow'; -import {withResolvedSeriesList} from '#composite/wiki-data'; - -export default templateCompositeFrom({ - annotation: `seriesList`, - - compose: false, - - inputs: { - group: input({ - validate: validateThing({referenceType: 'group'}), - }), - }, - - steps: () => [ - withResolvedSeriesList({ - group: input('group'), - - list: input.updateValue({ - validate: isSeriesList, - }), - }), - - exposeDependency({ - dependency: '#resolvedSeriesList', - }), - ], -}); diff --git a/src/data/composite/wiki-properties/singleReference.js b/src/data/composite/wiki-properties/singleReference.js index f532ebbe..25b97907 100644 --- a/src/data/composite/wiki-properties/singleReference.js +++ b/src/data/composite/wiki-properties/singleReference.js @@ -8,11 +8,19 @@ // import {input, templateCompositeFrom} from '#composite'; -import {isThingClass, validateReference} from '#validators'; +import {validateReference} from '#validators'; import {exposeDependency} from '#composite/control-flow'; -import {inputSoupyFind, inputWikiData, withResolvedReference} - from '#composite/wiki-data'; + +import { + inputFindOptions, + inputSoupyFind, + inputWikiData, + withResolvedReference, +} from '#composite/wiki-data'; + +import {referenceListInputDescriptions, referenceListUpdateDescription} + from './helpers/reference-list-helpers.js'; export default templateCompositeFrom({ annotation: `singleReference`, @@ -20,25 +28,24 @@ export default templateCompositeFrom({ compose: false, inputs: { - class: input.staticValue({validate: isThingClass}), + ...referenceListInputDescriptions(), + data: inputWikiData({allowMixedTypes: true}), find: inputSoupyFind(), - data: inputWikiData({allowMixedTypes: false}), + findOptions: inputFindOptions(), }, - update: ({ - [input.staticValue('class')]: thingClass, - }) => ({ - validate: - validateReference( - thingClass[Symbol.for('Thing.referenceType')]), - }), + update: + referenceListUpdateDescription({ + validateReferenceList: validateReference, + }), steps: () => [ withResolvedReference({ ref: input.updateValue(), data: input('data'), find: input('find'), + findOptions: input('findOptions'), }), exposeDependency({dependency: '#resolvedReference'}), diff --git a/src/data/composite/wiki-properties/soupyReverse.js b/src/data/composite/wiki-properties/soupyReverse.js index 269ccd6f..784a66b4 100644 --- a/src/data/composite/wiki-properties/soupyReverse.js +++ b/src/data/composite/wiki-properties/soupyReverse.js @@ -19,4 +19,19 @@ soupyReverse.contributionsBy = referenced: contrib => [contrib.artist], }); +soupyReverse.artworkContributionsBy = + (bindTo, artworkProperty, {single = false} = {}) => ({ + bindTo, + + referencing: thing => + (single + ? (thing[artworkProperty] + ? thing[artworkProperty].artistContribs + : []) + : thing[artworkProperty] + .flatMap(artwork => artwork.artistContribs)), + + referenced: contrib => [contrib.artist], + }); + export default soupyReverse; |