From a34b8d027866fbe858a4d2ff3543bc84c9d5983a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 17 Nov 2023 06:53:34 -0400 Subject: data, yaml, content: support multiple artists per commentary entry --- .../dependencies/generateCommentaryEntry.js | 13 ++++--- .../wiki-data/withParsedCommentaryEntries.js | 41 +++++++++++++++++----- .../wiki-properties/commentatorArtists.js | 23 +++++++----- src/data/yaml.js | 24 ++++++++++--- src/util/wiki-data.js | 2 +- 5 files changed, 75 insertions(+), 28 deletions(-) diff --git a/src/content/dependencies/generateCommentaryEntry.js b/src/content/dependencies/generateCommentaryEntry.js index b265ed41..0b2b2558 100644 --- a/src/content/dependencies/generateCommentaryEntry.js +++ b/src/content/dependencies/generateCommentaryEntry.js @@ -1,3 +1,5 @@ +import {empty} from '#sugar'; + export default { contentDependencies: [ 'generateColorStyleVariables', @@ -8,9 +10,10 @@ export default { extraDependencies: ['html', 'language'], relations: (relation, entry) => ({ - artistLink: - (entry.artist && !entry.artistDisplayText - ? relation('linkArtist', entry.artist) + artistLinks: + (!empty(entry.artists) && !entry.artistDisplayText + ? entry.artists + .map(artist => relation('linkArtist', artist)) : null), artistsContent: @@ -45,8 +48,8 @@ export default { html.tag('span', {class: 'commentary-entry-artists'}, (relations.artistsContent ? relations.artistsContent.slot('mode', 'inline') - : relations.artistLink - ? relations.artistLink + : relations.artistLinks + ? language.formatConjunctionList(relations.artistLinks) : language.$('misc.artistCommentary.entry.title.noArtists'))); const accentParts = ['misc.artistCommentary.entry.title.accent']; diff --git a/src/data/composite/wiki-data/withParsedCommentaryEntries.js b/src/data/composite/wiki-data/withParsedCommentaryEntries.js index 7b1c9484..25c07a37 100644 --- a/src/data/composite/wiki-data/withParsedCommentaryEntries.js +++ b/src/data/composite/wiki-data/withParsedCommentaryEntries.js @@ -4,7 +4,12 @@ import {stitchArrays} from '#sugar'; import {isCommentary} from '#validators'; import {commentaryRegex} from '#wiki-data'; -import {fillMissingListItems, withPropertiesFromList} from '#composite/data'; +import { + fillMissingListItems, + withFlattenedList, + withPropertiesFromList, + withUnflattenedList, +} from '#composite/data'; import withResolvedReferenceList from './withResolvedReferenceList.js'; @@ -86,23 +91,43 @@ export default templateCompositeFrom({ list: '#rawMatches.groups', prefix: input.value('#entries'), properties: input.value([ - 'artistReference', + 'artistReferences', 'artistDisplayText', 'annotation', 'date', ]), }), - // The artistReference group will always have a value, since it's required + // 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: '#entries.artistReference', + list: '#flattenedList', data: 'artistData', find: input.value(find.artist), notFoundMode: input.value('null'), + }), + + withUnflattenedList({ + list: '#resolvedReferenceList', + filter: input.value(false), }).outputs({ - '#resolvedReferenceList': '#entries.artist', + '#unflattenedList': '#entries.artists', }), fillMissingListItems({ @@ -127,7 +152,7 @@ export default templateCompositeFrom({ { dependencies: [ - '#entries.artist', + '#entries.artists', '#entries.artistDisplayText', '#entries.annotation', '#entries.date', @@ -135,7 +160,7 @@ export default templateCompositeFrom({ ], compute: (continuation, { - ['#entries.artist']: artist, + ['#entries.artists']: artists, ['#entries.artistDisplayText']: artistDisplayText, ['#entries.annotation']: annotation, ['#entries.date']: date, @@ -143,7 +168,7 @@ export default templateCompositeFrom({ }) => continuation({ ['#parsedCommentaryEntries']: stitchArrays({ - artist, + artists, artistDisplayText, annotation, date, diff --git a/src/data/composite/wiki-properties/commentatorArtists.js b/src/data/composite/wiki-properties/commentatorArtists.js index 8720e66d..65ab1466 100644 --- a/src/data/composite/wiki-properties/commentatorArtists.js +++ b/src/data/composite/wiki-properties/commentatorArtists.js @@ -4,8 +4,9 @@ import {input, templateCompositeFrom} from '#composite'; import {unique} from '#sugar'; -import {exitWithoutDependency} from '#composite/control-flow'; -import {withPropertyFromList} from '#composite/data'; +import {exitWithoutDependency, exposeDependency} + from '#composite/control-flow'; +import {withFlattenedList, withPropertyFromList} from '#composite/data'; import {withParsedCommentaryEntries} from '#composite/wiki-data'; export default templateCompositeFrom({ @@ -26,15 +27,19 @@ export default templateCompositeFrom({ withPropertyFromList({ list: '#parsedCommentaryEntries', - property: input.value('artist'), + property: input.value('artists'), }).outputs({ - '#parsedCommentaryEntries.artist': '#artists', + '#parsedCommentaryEntries.artists': '#artistLists', }), - { - dependencies: ['#artists'], - compute: ({'#artists': artists}) => - unique(artists.filter(artist => artist !== null)), - }, + withFlattenedList({ + list: '#artistLists', + }).outputs({ + '#flattenedList': '#artists', + }), + + exposeDependency({ + dependency: '#artists', + }), ], }); diff --git a/src/data/yaml.js b/src/data/yaml.js index 843e70b3..0734d539 100644 --- a/src/data/yaml.js +++ b/src/data/yaml.js @@ -21,6 +21,7 @@ import { decorateErrorWithIndex, decorateErrorWithAnnotation, empty, + filterAggregate, filterProperties, openAggregate, showAggregate, @@ -1686,8 +1687,10 @@ export function filterReferenceErrors(wikiData) { if (value) { value = Array.from(value.matchAll(commentaryRegex)) - .map(({groups}) => groups.artistReference); + .map(({groups}) => groups.artistReferences) + .map(text => text.split(',').map(text => text.trim())); } + writeProperty = false; break; } @@ -1804,11 +1807,22 @@ export function filterReferenceErrors(wikiData) { let newPropertyValue = value; - if (Array.isArray(value)) { + if (findFnKey === '_commentary') { + // Commentary doesn't write a property value, so no need to set. + filter( + value, {message: errorMessage}, + decorateErrorWithIndex(refs => + (refs.length === 1 + ? suppress(findFn)(refs[0]) + : filterAggregate( + refs, {message: `Errors in entry's artist references`}, + decorateErrorWithIndex(suppress(findFn))) + .aggregate + .close()))); + } else if (Array.isArray(value)) { newPropertyValue = filter( - value, - decorateErrorWithIndex(suppress(findFn)), - {message: errorMessage}); + value, {message: errorMessage}, + decorateErrorWithIndex(suppress(findFn))); } else { nest({message: errorMessage}, suppress(({call}) => { diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index 75a141d3..4c7ec043 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -652,7 +652,7 @@ export function sortFlashesChronologically(data, { // out of the original string based on the indices matched using this. // export const commentaryRegex = - /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=[,)]))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{2,4}))?\))?/gm; + /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=[,)]))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{2,4}))?\))?/gm; export function filterAlbumsByCommentary(albums) { return albums -- cgit 1.3.0-6-gf8a5 From 7da15227b8623a2fcef28c4f7988a2f89e5ab8b3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:22:08 -0400 Subject: content: transformContent: use marked for inline + own instances There's obviously lots of room to do more here, but this is a simple way to get typical inline contents passing through marked. --- src/content/dependencies/transformContent.js | 57 +++++++++++++++++++--------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js index dab89630..7b2d0573 100644 --- a/src/content/dependencies/transformContent.js +++ b/src/content/dependencies/transformContent.js @@ -1,7 +1,7 @@ import {bindFind} from '#find'; import {parseInput} from '#replacer'; -import {marked} from 'marked'; +import {Marked} from 'marked'; export const replacerSpec = { album: { @@ -147,6 +147,29 @@ const linkIndexRelationMap = { newsIndex: 'linkNewsIndex', }; +const commonMarkedOptions = { + headerIds: false, + mangle: false, +}; + +const multilineMarked = new Marked({ + ...commonMarkedOptions, +}); + +const inlineMarked = new Marked({ + ...commonMarkedOptions, + + renderer: { + paragraph(text) { + return text; + }, + }, +}); + +const lyricsMarked = new Marked({ + ...commonMarkedOptions, +}); + function getPlaceholder(node, content) { return {type: 'text', data: content.slice(node.i, node.iEnd)}; } @@ -447,21 +470,9 @@ export default { return link.data; } - // In inline mode, no further processing is needed! - - if (slots.mode === 'inline') { - return html.tags( - contentFromNodes.map(node => node.data), - {[html.joinChildren]: ''}); - } - - // Multiline mode has a secondary processing stage where it's passed... - // through marked! Rolling your own Markdown only gets you so far :D - - const markedOptions = { - headerIds: false, - mangle: false, - }; + // Content always goes through marked (i.e. parsing as Markdown). + // This does require some attention to detail, mostly to do with line + // breaks (in multiline mode) and extracting/re-inserting non-text nodes. // The content of non-text nodes can end up getting mangled by marked. // To avoid this, we replace them with mundane placeholders, then @@ -536,6 +547,16 @@ export default { return html.tags(tags, {[html.joinChildren]: ''}); }; + if (slots.mode === 'inline') { + const markedInput = + extractNonTextNodes(); + + const markedOutput = + inlineMarked.parse(markedInput); + + return reinsertNonTextNodes(markedOutput); + } + // This is separated into its own function just since we're gonna reuse // it in a minute if everything goes to heck in lyrics mode. const transformMultiline = () => { @@ -552,7 +573,7 @@ export default { .replace(/(?<=^>.*)\n+(?!^>)/gm, '\n\n'); const markedOutput = - marked.parse(markedInput, markedOptions); + multilineMarked.parse(markedInput); return reinsertNonTextNodes(markedOutput); } @@ -602,7 +623,7 @@ export default { }); const markedOutput = - marked.parse(markedInput, markedOptions); + lyricsMarked.parse(markedInput); return reinsertNonTextNodes(markedOutput); } -- cgit 1.3.0-6-gf8a5 From 42a8275ff59d13757b0d6a5a19588d4f8393ab6f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:19:49 -0400 Subject: update min node to 20, canonize in package.json --- README.md | 2 +- package.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7fc5824..2c99e3d8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ HSMusic, short for the *Homestuck Music Wiki*, is a revitalization and reimagini Install dependencies: -- [Node.js](https://nodejs.org/en/) - we recommend using [nvm](https://github.com/nvm-sh/nvm) to install Node and keep easy track of any versions you've got installed; development is generally tested on latest but 16.x LTS should also work +- [Node.js](https://nodejs.org/en/) - we recommend using [nvm](https://github.com/nvm-sh/nvm) to install Node and keep easy track of any versions you've got installed; development is generally tested on latest but 20.x LTS should also work - [ImageMagick](https://imagemagick.org/) - check your package manager if it's available (e.g. apt or homebrew) or follow [installation info right from the official website](https://imagemagick.org/script/download.php) Make a new empty folder for storing all your HSMusic repositories, then clone 'em with git: diff --git a/package.json b/package.json index 34b4ebf0..6f3323f2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,9 @@ "#wiki-data": "./src/util/wiki-data.js", "#yaml": "./src/data/yaml.js" }, + "engines": { + "node": ">= 20.9.0" + }, "dependencies": { "chroma-js": "^2.4.2", "command-exists": "^1.2.9", -- cgit 1.3.0-6-gf8a5 From f60c890fc4578879707bfdbb4e8219f78bd67b8b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:20:58 -0400 Subject: update marked from 5.0.2 to 10.0.0 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6433ea16..9e8f4dfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "he": "^1.2.0", "image-size": "^1.0.2", "js-yaml": "^4.1.0", - "marked": "^5.0.2", + "marked": "^10.0.0", "striptags": "^4.0.0-alpha.4", "word-wrap": "^1.2.3" }, @@ -3303,9 +3303,9 @@ } }, "node_modules/marked": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-5.0.2.tgz", - "integrity": "sha512-TXksm9GwqXCRNbFUZmMtqNLvy3K2cQHuWmyBDLOrY1e6i9UvZpOTJXoz7fBjYkJkaUFzV9hBFxMuZSyQt8R6KQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz", + "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==", "bin": { "marked": "bin/marked.js" }, @@ -8096,9 +8096,9 @@ } }, "marked": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-5.0.2.tgz", - "integrity": "sha512-TXksm9GwqXCRNbFUZmMtqNLvy3K2cQHuWmyBDLOrY1e6i9UvZpOTJXoz7fBjYkJkaUFzV9hBFxMuZSyQt8R6KQ==" + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-10.0.0.tgz", + "integrity": "sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==" }, "mimic-fn": { "version": "2.1.0", diff --git a/package.json b/package.json index 6f3323f2..719a5b89 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "he": "^1.2.0", "image-size": "^1.0.2", "js-yaml": "^4.1.0", - "marked": "^5.0.2", + "marked": "^10.0.0", "striptags": "^4.0.0-alpha.4", "word-wrap": "^1.2.3" }, -- cgit 1.3.0-6-gf8a5 From 4b7da4c1f8c359e5c82c4cc5e0cfb78f8204850f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:21:31 -0400 Subject: data: parse commentary heading contents to end of line --- src/util/wiki-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index 4c7ec043..8a6d4345 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -652,7 +652,7 @@ export function sortFlashesChronologically(data, { // out of the original string based on the indices matched using this. // export const commentaryRegex = - /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=[,)]))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{2,4}))?\))?/gm; + /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=,|\)$))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{2,4}))?\))?$/gm; export function filterAlbumsByCommentary(albums) { return albums -- cgit 1.3.0-6-gf8a5 From 522c982bf0b5a0bd39512eb56a9d0d8d8feea44e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:30:27 -0400 Subject: data: looser commentary date parsing + clearer regex explanation --- src/util/wiki-data.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index 8a6d4345..09b3623e 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -641,8 +641,10 @@ export function sortFlashesChronologically(data, { // // * "25 December 2019" - one or two number digits, followed by any text, // followed by four number digits -// * "12/25/2019" - one or two number digits, a slash, one or two number -// digits, a slash, and two to four number digits +// * "December 25, 2019" - one all-letters word, a space, one or two number +// digits, a comma, and four number digits +// * "12/25/2019" etc - three sets of one to four number digits, separated +// by slashes (only valid formats are MM/DD/YYYY and YYYY/MM/DD) // // Capturing group "artistReference" is all the characters between and // (apart from the pipe and "artistDisplayText" text, if present), and is either @@ -652,7 +654,7 @@ export function sortFlashesChronologically(data, { // out of the original string based on the indices matched using this. // export const commentaryRegex = - /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=,|\)$))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,2}\/[0-9]{1,2}\/[0-9]{2,4}))?\))?$/gm; + /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=,|\)$))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,4}\/[0-9]{1,4}\/[0-9]{1,4}))?\))?$/gm; export function filterAlbumsByCommentary(albums) { return albums -- cgit 1.3.0-6-gf8a5 From e874f7e4d0df5fed25d4c359c8cb403e67061e59 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:33:38 -0400 Subject: data: support dash-style short dates in commentary dates --- src/util/wiki-data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index 09b3623e..5e3182a9 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -644,7 +644,7 @@ export function sortFlashesChronologically(data, { // * "December 25, 2019" - one all-letters word, a space, one or two number // digits, a comma, and four number digits // * "12/25/2019" etc - three sets of one to four number digits, separated -// by slashes (only valid formats are MM/DD/YYYY and YYYY/MM/DD) +// by slashes or dashes (only valid orders are MM/DD/YYYY and YYYY/MM/DD) // // Capturing group "artistReference" is all the characters between and // (apart from the pipe and "artistDisplayText" text, if present), and is either @@ -654,7 +654,7 @@ export function sortFlashesChronologically(data, { // out of the original string based on the indices matched using this. // export const commentaryRegex = - /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=,|\)$))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,4}\/[0-9]{1,4}\/[0-9]{1,4}))?\))?$/gm; + /^(?.+?)(?:\|(?.+))?:<\/i>(?: \((?(?:.*?(?=,|\)$))*?)(?:,? ?(?[a-zA-Z]+ [0-9]{1,2}, [0-9]{4,4}|[0-9]{1,2} [^,]*[0-9]{4,4}|[0-9]{1,4}[-/][0-9]{1,4}[-/][0-9]{1,4}))?\))?$/gm; export function filterAlbumsByCommentary(albums) { return albums -- cgit 1.3.0-6-gf8a5 From 6fb9bbcb4d4806a4d31eb3d387dd229a598bc15d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:38:33 -0400 Subject: test: withParsedCommentaryEntries: update + test multiple artists --- .../data/composite/wiki-data/withParsedCommentaryEntries.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js b/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js index 50570de6..928a9c98 100644 --- a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js +++ b/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js @@ -49,7 +49,7 @@ t.test(`withParsedCommentaryEntries: basic behavior`, t => { `Very cool.\n`, }), [ { - artist: artist1, + artists: [artist1], artistDisplayText: null, annotation: null, date: null, @@ -67,32 +67,32 @@ t.test(`withParsedCommentaryEntries: basic behavior`, t => { `Second commentary entry. Yes. So cool.\n` + `Mystery Artist: (pingas, August 25, 2023)\n` + `Oh no.. Oh dear...\n` + - `Mobius Trip:\n` + + `Mobius Trip, Hadron Kaleido:\n` + `And back around we go.`, }), [ { - artist: artist1, + artists: [artist1], artistDisplayText: `Moo-bius Trip`, annotation: `music, art`, date: new Date('12 January 2015'), body: `First commentary entry.\nVery cool.`, }, { - artist: artist2, + artists: [artist2], artistDisplayText: `[[artist:hadron-kaleido|The Ol' Hadron]]`, annotation: `moral support`, date: new Date('4 April 2022'), body: `Second commentary entry. Yes. So cool.`, }, { - artist: null, + artists: [], artistDisplayText: null, annotation: `pingas`, date: new Date('25 August 2023'), body: `Oh no.. Oh dear...`, }, { - artist: artist1, + artists: [artist1, artist2], artistDisplayText: null, annotation: null, date: null, -- cgit 1.3.0-6-gf8a5 From 9eef57746726faeeeb8e571b1794ec5c14023e97 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:53:35 -0400 Subject: test: withParsedCommentaryEntries: use stricter t.same() matching --- test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js b/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js index 928a9c98..babe4fae 100644 --- a/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js +++ b/test/unit/data/composite/wiki-data/withParsedCommentaryEntries.js @@ -41,7 +41,7 @@ t.test(`withParsedCommentaryEntries: basic behavior`, t => { }, }); - t.match(composite.expose.compute({ + t.same(composite.expose.compute({ artistData, from: `Mobius Trip:\n` + @@ -57,7 +57,7 @@ t.test(`withParsedCommentaryEntries: basic behavior`, t => { }, ]); - t.match(composite.expose.compute({ + t.same(composite.expose.compute({ artistData, from: `Mobius Trip|Moo-bius Trip: (music, art, 12 January 2015)\n` + -- cgit 1.3.0-6-gf8a5 From 6cbd8ee458a25ed8608760f8867008aa8f55d5e3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 19:54:37 -0400 Subject: test: Track.commentatorArtists: update overall --- test/unit/data/things/track.js | 48 ++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js index 806efbf1..571624a5 100644 --- a/test/unit/data/things/track.js +++ b/test/unit/data/things/track.js @@ -214,7 +214,7 @@ t.test(`Track.color`, t => { }); t.test(`Track.commentatorArtists`, t => { - t.plan(6); + t.plan(8); const track = new Track(); const artist1 = stubArtist(`SnooPING`); @@ -226,47 +226,67 @@ t.test(`Track.commentatorArtists`, t => { artistData: [artist1, artist2, artist3], }); - track.commentary = + // Keep track of the last commentary string in a separate value, since + // the track.commentary property exposes as a completely different format + // (i.e. an array of objects, one for each entry), and so isn't compatible + // with the += operator on its own. + let commentary; + + track.commentary = commentary = `SnooPING:\n` + `Wow.\n`; t.same(track.commentatorArtists, [artist1], `Track.commentatorArtists #1: works with one commentator`); - track.commentary += + track.commentary = commentary += `ASUsual:\n` + `Yes!\n`; t.same(track.commentatorArtists, [artist1, artist2], `Track.commentatorArtists #2: works with two commentators`); - track.commentary += - `Icy:\n` + + track.commentary = commentary += + `Icy|Icy What You Did There:\n` + `Incredible.\n`; t.same(track.commentatorArtists, [artist1, artist2, artist3], - `Track.commentatorArtists #3: works with boldface name`); + `Track.commentatorArtists #3: works with custom artist text`); - track.commentary = + track.commentary = commentary = `Icy: (project manager)\n` + `Very good track.\n`; t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #4: works with parenthical accent`); + `Track.commentatorArtists #4: works with annotation`); - track.commentary += - `SNooPING ASUsual Icy:\n` + - `WITH ALL THREE POWERS COMBINED...`; + track.commentary = commentary = + `Icy: (project manager, 08/15/2023)\n` + + `Very very good track.\n`; + + t.same(track.commentatorArtists, [artist3], + `Track.commentatorArtists #5: works with date`); + + track.commentary = commentary += + `Ohohohoho:\n` + + `OHOHOHOHOHOHO...\n`; t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #5: ignores artist names not found`); + `Track.commentatorArtists #6: ignores artist names not found`); - track.commentary += + track.commentary = commentary += `Icy:\n` + `I'm back!\n`; t.same(track.commentatorArtists, [artist3], - `Track.commentatorArtists #6: ignores duplicate artist`); + `Track.commentatorArtists #7: ignores duplicate artist`); + + track.commentary = commentary += + `SNooPING, ASUsual, Icy:\n` + + `WITH ALL THREE POWERS COMBINED...`; + + t.same(track.commentatorArtists, [artist3, artist1, artist2], + `Track.commentatorArtists #8: works with more than one artist in one entry`); }); t.test(`Track.coverArtistContribs`, t => { -- cgit 1.3.0-6-gf8a5 From f82b74f62595c4def21b176c366c703da02c331e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 20:15:07 -0400 Subject: data, test: withUniqueItemsOnly (#composite/data) --- src/data/composite/data/index.js | 1 + src/data/composite/data/withUniqueItemsOnly.js | 40 +++++++++++ .../data/composite/data/withUniqueItemsOnly.js | 84 ++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 src/data/composite/data/withUniqueItemsOnly.js create mode 100644 test/unit/data/composite/data/withUniqueItemsOnly.js diff --git a/src/data/composite/data/index.js b/src/data/composite/data/index.js index db1c37cc..e2927afd 100644 --- a/src/data/composite/data/index.js +++ b/src/data/composite/data/index.js @@ -11,3 +11,4 @@ export {default as withPropertiesFromObject} from './withPropertiesFromObject.js export {default as withPropertyFromList} from './withPropertyFromList.js'; export {default as withPropertyFromObject} from './withPropertyFromObject.js'; export {default as withUnflattenedList} from './withUnflattenedList.js'; +export {default as withUniqueItemsOnly} from './withUniqueItemsOnly.js'; diff --git a/src/data/composite/data/withUniqueItemsOnly.js b/src/data/composite/data/withUniqueItemsOnly.js new file mode 100644 index 00000000..7ee08b08 --- /dev/null +++ b/src/data/composite/data/withUniqueItemsOnly.js @@ -0,0 +1,40 @@ +// Excludes duplicate items from a list and provides the results, overwriting +// the list in-place, if possible. + +import {input, templateCompositeFrom} from '#composite'; +import {unique} from '#sugar'; + +export default templateCompositeFrom({ + annotation: `withUniqueItemsOnly`, + + inputs: { + list: input({type: 'array'}), + }, + + outputs: ({ + [input.staticDependency('list')]: list, + }) => [list ?? '#uniqueItems'], + + steps: () => [ + { + dependencies: [input('list')], + compute: (continuation, { + [input('list')]: list, + }) => continuation({ + ['#values']: + unique(list), + }), + }, + + { + dependencies: ['#values', input.staticDependency('list')], + compute: (continuation, { + '#values': values, + [input.staticDependency('list')]: list, + }) => continuation({ + [list ?? '#uniqueItems']: + values, + }), + }, + ], +}); diff --git a/test/unit/data/composite/data/withUniqueItemsOnly.js b/test/unit/data/composite/data/withUniqueItemsOnly.js new file mode 100644 index 00000000..965b14b5 --- /dev/null +++ b/test/unit/data/composite/data/withUniqueItemsOnly.js @@ -0,0 +1,84 @@ +import t from 'tap'; + +import {compositeFrom, input} from '#composite'; +import {exposeDependency} from '#composite/control-flow'; +import {withUniqueItemsOnly} from '#composite/data'; + +t.test(`withUniqueItemsOnly: basic behavior`, t => { + t.plan(3); + + const composite = compositeFrom({ + compose: false, + + steps: [ + withUniqueItemsOnly({ + list: 'list', + }), + + exposeDependency({dependency: '#list'}), + ], + }); + + t.match(composite, { + expose: { + dependencies: ['list'], + }, + }); + + t.same(composite.expose.compute({ + list: ['apple', 'banana', 'banana', 'banana', 'apple', 'watermelon'], + }), ['apple', 'banana', 'watermelon']); + + t.same(composite.expose.compute({ + list: [], + }), []); +}); + +t.test(`withUniqueItemsOnly: output shapes & values`, t => { + t.plan(2 * 3 ** 1); + + const dependencies = { + ['list_dependency']: + [1, 1, 2, 3, 3, 4, 'foo', false, false, 4], + [input('list_neither')]: + [8, 8, 7, 6, 6, 5, 'bar', true, true, 5], + }; + + const mapLevel1 = [ + ['list_dependency', { + '#list_dependency': [1, 2, 3, 4, 'foo', false], + }], + [input.value([-1, -1, 'interesting', 'very', 'interesting']), { + '#uniqueItems': [-1, 'interesting', 'very'], + }], + [input('list_neither'), { + '#uniqueItems': [8, 7, 6, 5, 'bar', true], + }], + ]; + + for (const [listInput, outputDict] of mapLevel1) { + const step = withUniqueItemsOnly({ + list: listInput, + }); + + quickCheckOutputs(step, outputDict); + } + + function quickCheckOutputs(step, outputDict) { + t.same( + Object.keys(step.toDescription().outputs), + Object.keys(outputDict)); + + const composite = compositeFrom({ + compose: false, + steps: [step, { + dependencies: Object.keys(outputDict), + compute: dependencies => dependencies, + }], + }); + + t.same( + composite.expose.compute(dependencies), + outputDict); + } +}); -- cgit 1.3.0-6-gf8a5 From 5b8060bb86d457a0d23b607aa866c4d7d6eb6f0f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 20:16:40 -0400 Subject: data: withParsedCommentaryEntries: filter out null artists --- src/data/composite/wiki-data/withParsedCommentaryEntries.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/composite/wiki-data/withParsedCommentaryEntries.js b/src/data/composite/wiki-data/withParsedCommentaryEntries.js index 25c07a37..edfc9e3c 100644 --- a/src/data/composite/wiki-data/withParsedCommentaryEntries.js +++ b/src/data/composite/wiki-data/withParsedCommentaryEntries.js @@ -125,7 +125,6 @@ export default templateCompositeFrom({ withUnflattenedList({ list: '#resolvedReferenceList', - filter: input.value(false), }).outputs({ '#unflattenedList': '#entries.artists', }), -- cgit 1.3.0-6-gf8a5 From e35d23f4e9492b497138dce3f21382872e329e71 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 18 Nov 2023 20:16:57 -0400 Subject: data: commentatorArtists: filter out duplicate artists --- src/data/composite/wiki-properties/commentatorArtists.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/data/composite/wiki-properties/commentatorArtists.js b/src/data/composite/wiki-properties/commentatorArtists.js index 65ab1466..f400bbfc 100644 --- a/src/data/composite/wiki-properties/commentatorArtists.js +++ b/src/data/composite/wiki-properties/commentatorArtists.js @@ -6,7 +6,8 @@ import {unique} from '#sugar'; import {exitWithoutDependency, exposeDependency} from '#composite/control-flow'; -import {withFlattenedList, withPropertyFromList} from '#composite/data'; +import {withFlattenedList, withPropertyFromList, withUniqueItemsOnly} + from '#composite/data'; import {withParsedCommentaryEntries} from '#composite/wiki-data'; export default templateCompositeFrom({ @@ -38,6 +39,10 @@ export default templateCompositeFrom({ '#flattenedList': '#artists', }), + withUniqueItemsOnly({ + list: '#artists', + }), + exposeDependency({ dependency: '#artists', }), -- cgit 1.3.0-6-gf8a5