From cd3a59d5024984a2d3be5164f6b2ce9ee05e9f62 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 27 Feb 2022 12:13:53 -0400 Subject: generalized reference errors --- src/data/things.js | 76 +++++++---- src/data/validators.js | 4 +- src/page/homepage.js | 2 - src/upd8.js | 349 ++++++++++++++++++++++++------------------------- src/util/find.js | 75 ++++++----- src/util/replacer.js | 12 +- 6 files changed, 271 insertions(+), 247 deletions(-) diff --git a/src/data/things.js b/src/data/things.js index c09f740..475c4e1 100644 --- a/src/data/things.js +++ b/src/data/things.js @@ -34,6 +34,9 @@ import { import find from '../util/find.js'; +import { inspect } from 'util'; +import { color } from '../util/cli.js'; + // Stub classes (and their exports) at the top of the file - these are // referenced later when we actually define static class fields. We deliberately // define the classes and set their static fields in two separate steps so that @@ -216,17 +219,17 @@ Thing.common = { // matching actual Thing-subclass objects. dynamicThingsFromReferenceList: ( referenceListProperty, - wikiDataProperty, + thingDataProperty, findFn ) => ({ flags: {expose: true}, expose: { - dependencies: [referenceListProperty, wikiDataProperty], - compute: ({ [referenceListProperty]: refs, [wikiDataProperty]: wikiData }) => ( - (refs && wikiData + dependencies: [referenceListProperty, thingDataProperty], + compute: ({ [referenceListProperty]: refs, [thingDataProperty]: thingData }) => ( + (refs && thingData ? (refs - .map(ref => findFn(ref, {wikiData: {[wikiDataProperty]: wikiData}})) + .map(ref => findFn(ref, thingData, {mode: 'quiet'})) .filter(Boolean)) : []) ) @@ -236,17 +239,15 @@ Thing.common = { // Corresponding function for a single reference. dynamicThingFromSingleReference: ( singleReferenceProperty, - wikiDataProperty, + thingDataProperty, findFn ) => ({ flags: {expose: true}, expose: { - dependencies: [singleReferenceProperty, wikiDataProperty], - compute: ({ [singleReferenceProperty]: ref, [wikiDataProperty]: wikiData }) => ( - (ref && wikiData - ? findFn(ref, {wikiData: {[wikiDataProperty]: wikiData}}) - : []) + dependencies: [singleReferenceProperty, thingDataProperty], + compute: ({ [singleReferenceProperty]: ref, [thingDataProperty]: thingData }) => ( + (ref && thingData ? findFn(ref, thingData, {mode: 'quiet'}) : []) ) } }), @@ -274,7 +275,7 @@ Thing.common = { ((contribsByRef && artistData) ? (contribsByRef .map(({ who: ref, what }) => ({ - who: find.artist(ref, {wikiData: {artistData}}), + who: find.artist(ref, artistData), what })) .filter(({ who }) => who)) @@ -293,24 +294,24 @@ Thing.common = { dynamicInheritContribs: ( contribsByRefProperty, parentContribsByRefProperty, - wikiDataProperty, + thingDataProperty, findFn ) => ({ flags: {expose: true}, expose: { - dependencies: [contribsByRefProperty, wikiDataProperty, 'artistData'], + dependencies: [contribsByRefProperty, thingDataProperty, 'artistData'], compute({ [Thing.instance]: thing, [contribsByRefProperty]: contribsByRef, - [wikiDataProperty]: wikiData, + [thingDataProperty]: thingData, artistData }) { if (!artistData) return []; - const refs = (contribsByRef ?? findFn(thing, wikiData)?.[parentContribsByRefProperty]); + const refs = (contribsByRef ?? findFn(thing, thingData, {mode: 'quiet'})?.[parentContribsByRefProperty]); if (!refs) return []; return (refs .map(({ who: ref, what }) => ({ - who: find.artist(ref, {wikiData: {artistData}}), + who: find.artist(ref, artistData), what })) .filter(({ who }) => who)); @@ -375,7 +376,7 @@ Thing.common = { .from(commentary .replace(/<\/?b>/g, '') .matchAll(/(?.*?):<\/i>/g)) - .map(({ groups: {who} }) => find.artist(who, {wikiData: {artistData}, quiet: true}))))) + .map(({ groups: {who} }) => find.artist(who, artistData, {mode: 'quiet'}))))) : [])) } }), @@ -394,6 +395,18 @@ Thing.getReference = function(thing) { return `${thing.constructor[Thing.referenceType]}:${thing.directory}`; }; +// Default custom inspect function, which may be overridden by Thing subclasses. +// This will be used when displaying aggregate errors and other in command-line +// logging - it's the place to provide information useful in identifying the +// Thing being presented. +Thing.prototype[inspect.custom] = function() { + const cname = this.constructor.name; + + return (this.name + ? `${cname} ${color.green(`"${this.name}"`)}` + : `${cname}`); +}; + // -> Album Album.propertyDescriptors = { @@ -476,7 +489,7 @@ Album.propertyDescriptors = { (trackGroups && trackData ? (trackGroups .flatMap(group => group.tracksByRef ?? []) - .map(ref => find.track(ref, {wikiData: {trackData}})) + .map(ref => find.track(ref, trackData)) .filter(Boolean)) : []) ) @@ -537,7 +550,7 @@ TrackGroup.propertyDescriptors = { compute: ({ tracksByRef, trackData }) => ( (tracksByRef && trackData ? (tracksByRef - .map(ref => find.track(ref, {wikiData: {trackData}})) + .map(ref => find.track(ref, trackData)) .filter(Boolean)) : []) ) @@ -570,13 +583,13 @@ Track.propertyDescriptors = { hasURLs: Thing.common.flag(true), - referencedTracksByRef: Thing.common.referenceList(Track), - artTagsByRef: Thing.common.referenceList(ArtTag), - artistContribsByRef: Thing.common.contribsByRef(), contributorContribsByRef: Thing.common.contribsByRef(), coverArtistContribsByRef: Thing.common.contribsByRef(), + referencedTracksByRef: Thing.common.referenceList(Track), + artTagsByRef: Thing.common.referenceList(ArtTag), + hasCoverArt: { flags: {update: true, expose: true}, @@ -676,7 +689,7 @@ Track.propertyDescriptors = { return []; } - const tOrig = find.track(ref1, {wikiData: {trackData}}); + const tOrig = find.track(ref1, trackData); if (!tOrig) { return []; } @@ -688,7 +701,7 @@ Track.propertyDescriptors = { return ( t2 !== t1 && ref2 && - find.track(ref2, {wikiData: {trackData}}) === tOrig + find.track(ref2, trackData) === tOrig ); }) ]; @@ -717,6 +730,13 @@ Track.propertyDescriptors = { artTags: Thing.common.dynamicThingsFromReferenceList('artTagsByRef', 'artTagData', find.artTag), }; +Track.prototype[inspect.custom] = function() { + const base = Thing.prototype[inspect.custom].apply(this); + return (this.album?.name + ? base + ` (from ${color.green(this.album.name)})` + : base); +} + // -> Artist Artist.filterByContrib = (thingDataProperty, contribsProperty) => ({ @@ -765,7 +785,7 @@ Artist.propertyDescriptors = { dependencies: ['artistData', 'aliasedArtistRef'], compute: ({ artistData, aliasedArtistRef }) => ( (aliasedArtistRef && artistData - ? find.artist(aliasedArtistRef, {wikiData: {artistData}}, {quiet: true}) + ? find.artist(aliasedArtistRef, artistData, {mode: 'quiet'}) : null) ) } @@ -1095,10 +1115,10 @@ Flash.propertyDescriptors = { update: {validate: isFileExtension} }, - featuredTracksByRef: Thing.common.referenceList(Track), - contributorContribsByRef: Thing.common.contribsByRef(), + featuredTracksByRef: Thing.common.referenceList(Track), + urls: Thing.common.urls(), // Update only diff --git a/src/data/validators.js b/src/data/validators.js index 8f4d06d..2d90987 100644 --- a/src/data/validators.js +++ b/src/data/validators.js @@ -155,7 +155,7 @@ function validateArrayItemsHelper(itemValidator) { export function validateArrayItems(itemValidator) { const fn = validateArrayItemsHelper(itemValidator); - return decorateTime('validateArrayItems -> work', array => { + return array => { isArray(array); withAggregate({message: 'Errors validating array items'}, ({ wrap }) => { @@ -163,7 +163,7 @@ export function validateArrayItems(itemValidator) { }); return true; - }); + }; } export function validateInstanceOf(constructor) { diff --git a/src/page/homepage.js b/src/page/homepage.js index 379e5de..aa99527 100644 --- a/src/page/homepage.js +++ b/src/page/homepage.js @@ -4,8 +4,6 @@ import fixWS from 'fix-whitespace'; -import find from '../util/find.js'; - import * as html from '../util/html.js'; import { diff --git a/src/upd8.js b/src/upd8.js index 4553826..e903d6a 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -424,7 +424,7 @@ const replacerSpec = { } }; -if (!validateReplacerSpec(replacerSpec, unbound_link)) { +if (!validateReplacerSpec(replacerSpec, {find, link: unbound_link})) { process.exit(); } @@ -2049,6 +2049,35 @@ async function wrapLanguages(fn, {writeOneLanguage = null}) { } } +// Handy utility function for binding the find.thing() functions to a complete +// wikiData object, optionally taking default options to provide to the find +// function. Note that this caches the arrays read from wikiData right when it's +// called, so if their values change, you'll have to continue with a fresh call +// to indFind. +function bindFind(wikiData, opts1) { + return Object.fromEntries(Object.entries({ + album: 'albumData', + artist: 'artistData', + artTag: 'artTagData', + flash: 'flashData', + group: 'groupData', + listing: 'listingSpec', + newsEntry: 'newsData', + staticPage: 'staticPageData', + track: 'trackData', + }).map(([ key, value ]) => { + const findFn = find[key]; + const thingData = wikiData[value]; + return [key, (opts1 + ? (ref, opts2) => (opts2 + ? findFn(ref, thingData, {...opts1, ...opts2}) + : findFn(ref, thingData, opts1)) + : (ref, opts2) => (opts2 + ? findFn(ref, thingData, opts2) + : findFn(ref, thingData)))]; + })); +} + async function main() { Error.stackTraceLimit = Infinity; @@ -2109,6 +2138,13 @@ async function main() { type: 'flag' }, + // Just working on data entries and not interested in actually + // generating site HTML yet? This flag will cut execution off right + // 8efore any site 8uilding actually happens. + 'no-build': { + type: 'flag' + }, + // Only want to 8uild one language during testing? This can chop down // 8uild times a pretty 8ig chunk! Just pass a single language code. 'lang': { @@ -2182,6 +2218,7 @@ async function main() { const skipThumbs = miscOptions['skip-thumbs'] ?? false; const thumbsOnly = miscOptions['thumbs-only'] ?? false; + const noBuild = miscOptions['no-build'] ?? false; const showAggregateTraces = miscOptions['show-traces'] ?? false; const niceShowAggregate = (error, ...opts) => { @@ -2244,7 +2281,9 @@ async function main() { logInfo`Loaded language strings: ${Object.keys(languages).join(', ')}`; - if (writeOneLanguage && !(writeOneLanguage in languages)) { + if (noBuild) { + logInfo`Not generating any site or page files this run (--no-build passed).`; + } else if (writeOneLanguage && !(writeOneLanguage in languages)) { logError`Specified to write only ${writeOneLanguage}, but there is no strings file with this language code!`; return; } else if (writeOneLanguage) { @@ -2765,26 +2804,138 @@ async function main() { albumData: sortByDate(WD.albumData.slice()), trackData: sortByDate(WD.trackData.slice()) }); - } - // Now post-process data in three steps... + // Re-link data arrays, so that every object has the new, sorted + // versions. Note that the sorting step deliberately creates new arrays + // (mutating slices instead of the original arrays) - this is so that + // the object caching system understands that it's working with a new + // ordering. We still need to actually provide those updated arrays + // over again! + linkDataArrays(); + } + + // Warn about references across data which don't match anything. + // This involves using the find() functions on all references, setting it to + // 'error' mode, and collecting everything in a structured logged (which + // gets logged if there are any errors). At the same time, we remove errored + // references from the thing's data array. + + const referenceSpec = [ + ['albumData', { + artistContribsByRef: '_contrib', + coverArtistContribsByRef: '_contrib', + trackCoverArtistContribsByRef: '_contrib', + wallpaperArtistContribsByRef: '_contrib', + bannerArtistContribsByRef: '_contrib', + groupsByRef: 'group', + artTagsByRef: 'artTag', + }], + + ['trackData', { + artistContribsByRef: '_contrib', + contributorContribsByRef: '_contrib', + coverArtistContribsByRef: '_contrib', + referencedTracksByRef: 'track', + artTagsByRef: 'artTag', + originalReleaseTrackByRef: 'track', + }], + + ['groupCategoryData', { + groupsByRef: 'group', + }], + + ['homepageLayout.rows', { + sourceGroupsByRef: 'group', + sourceAlbumsByRef: 'album', + }], + + ['flashData', { + contributorContribsByRef: '_contrib', + featuredTracksByRef: 'track', + }], + + ['flashActData', { + flashesByRef: 'flash', + }], + ]; + + function getNestedProp(obj, key) { + const recursive = (o, k) => (k.length === 1 + ? o[k[0]] + : recursive(o[k[0]], k.slice(1))); + const keys = key.split(/(?<=(? { + for (const thing of thingData) { + nest({message: `Reference errors in ${inspect(thing)}`}, ({ filter }) => { + for (const [ property, findFnKey ] of Object.entries(propSpec)) { + if (!thing[property]) continue; + if (findFnKey === '_contrib') { + thing[property] = filter(thing[property], + decorateErrorWithIndex(({ who }) => boundFind.artist(who)), + {message: `Reference errors in contributions ${color.green(property)}`}); + continue; + } + const findFn = boundFind[findFnKey]; + const value = thing[property]; + if (Array.isArray(value)) { + thing[property] = filter(value, decorateErrorWithIndex(findFn), + {message: `Reference errors in property ${color.green(property)}`}); + } else { + nest({message: `Reference error in property ${color.green(property)}`}, ({ call }) => { + try { + call(findFn, value); + } catch (error) { + thing[property] = null; + throw error; + } + }); + } + } + }); + } + }); + } - // 1. Link data arrays so that all essential references between objects are - // are complete, so properties (like dates!) are inherited where that's + let errorless = true; + try { + aggregate.close(); + } catch (error) { + niceShowAggregate(error); + logWarn`The above errors were detected while validating references in data files.`; + logWarn`If the remaining valid data is complete enough, the wiki will still build -`; + logWarn`but all errored references will be skipped.`; + logWarn`(Resolve errors for more complete output!)`; + errorless = false; + } + + if (errorless) { + logInfo`All references validated without any errors - nice!`; + logInfo`(This means all references between things, such as leitmotif references` + logInfo` and artist credits, will be fully accounted for during page generation.)`; + } + } + + // Link data arrays so that all essential references between objects are + // complete, so properties (like dates!) are inherited where that's // appropriate. linkDataArrays(); - // 2. Sort data arrays so that they're all in order! This may use properties + // Filter out any reference errors throughout the data, warning about them + // too. + filterAndShowReferenceErrors(); + + // Sort data arrays so that they're all in order! This may use properties // which are only available after the initial linking. sortDataArrays(); - // 3. Re-link data arrays, so that every object has the new, sorted - // versions. Note that the sorting step deliberately creates new arrays - // (mutating slices instead of the original arrays) - this is so that the - // object caching system understands that it's working with a new ordering. - // We still need to actually provide those updated arrays over again! - linkDataArrays(); - // const track = WD.trackData.find(t => t.name === 'Under the Sun'); // console.log(track.album.trackGroups.find(tg => tg.tracks.includes(track)).color, track.color); // console.log(WD.homepageLayout.rows[0].countAlbumsFromGroup); @@ -2816,7 +2967,7 @@ async function main() { const tagRefs = new Set([...WD.trackData, ...WD.albumData].flatMap(thing => thing.artTagsByRef ?? [])); for (const ref of tagRefs) { - if (find.artTag(ref, {wikiData})) { + if (find.artTag(ref, WD.artTagData)) { tagRefs.delete(ref); } } @@ -2845,75 +2996,6 @@ async function main() { }) ?? []); }); - // TODO: this should probably be some kinda generalized function lol - { - const aggregate = openAggregate({message: `Errors validating artist references in data`}); - - const sources = [ - [WD.albumData, [ - 'artistContribsByRef', - 'coverArtistContribsByRef', - 'trackCoverArtistContribsByRef', - 'wallpaperArtistContribsByRef', - 'bannerArtistContribsByRef' - ]], - [WD.trackData, [ - 'artistContribsByRef', - 'contributorContribsByRef', - 'coverArtistContribsByRef' - ]], - [WD.flashData, [ - 'contributorContribsByRef' - ]] - ] - - for (const [ things, properties ] of sources) { - if (!things) { - continue; - } - - for (const thing of things) { - aggregate.nest({message: `Errors for ${thing.constructor.name} ${color.green(thing.name)} (${color.green(Thing.getReference(thing))})`}, thingAgg => { - for (const prop of properties) { - const contribs = thing[prop]; - if (!contribs) { - continue; - } - thingAgg.nest({message: `Errors for property ${color.green(prop)}`}, propAgg => { - for (const { who: ref } of contribs) { - propAgg.call(() => { - const entryAlias = find.artist(ref, {wikiData: {artistData: wikiData.artistAliasData}, quiet: true}); - if (entryAlias) { - const orig = find.artist(entryAlias.aliasedArtistRef, {wikiData: {artistData: wikiData.artistData}, quiet: true}); - throw new Error(`Reference ${color.red(ref)} is to an alias, reference ${color.green(orig.name)} instead`); - } - const entry = find.artist(ref, {wikiData: {artistData: wikiData.artistData}, quiet: true}); - if (!entry) { - throw new Error(`No entry found for reference ${color.red(ref)}`); - } - if ( - ref.toLowerCase() === entry.name.toLowerCase() && - ref !== entry.name - ) { - throw new Error(`Miscapitalized name ${color.red(ref)}, reference ${color.green(entry.name)} instead`); - } - }); - } - }); - } - }); - } - } - - try { - aggregate.close(); - } catch (error) { - niceShowAggregate(error); - // TODO: more graceful auto-resolve, filter out invalid references - return; - } - } - { const directories = []; for (const { directory, name } of WD.albumData) { @@ -2941,103 +3023,11 @@ async function main() { } } - /* - const bound = { - findGroup: x => find.group(x, {wikiData}), - findTrack: x => find.track(x, {wikiData}), - findTag: x => find.tag(x, {wikiData}) - }; - - for (const track of WD.trackData) { - const context = () => track.album.name; - track.aka = find.track(track.aka, {wikiData}); - mapAndFilter(track, 'references', {map: bound.findTrack, context}); - mapAndFilter(track, 'artTags', {map: bound.findTag, context}); - } - - for (const track1 of WD.trackData) { - track1.referencedBy = WD.trackData.filter(track2 => track2.references.includes(track1)); - track1.otherReleases = [ - track1.aka, - ...WD.trackData.filter(track2 => - track2.aka === track1 || - (track1.aka && track2.aka === track1.aka)) - ].filter(x => x && x !== track1); - } - - for (const album of WD.albumData) { - mapAndFilter(album, 'groups', {map: bound.findGroup}); - mapAndFilter(album, 'artTags', {map: bound.findTag}); - } - - mapAndFilter(WD, 'artistAliasData', { - map: artist => { - artist.alias = find.artist(artist.alias, {wikiData}); - return artist; - }, - filter: artist => artist.alias - }); - - for (const group of WD.groupData) { - group.albums = WD.albumData.filter(album => album.groups.includes(group)); - group.category = WD.groupCategoryData.find(x => x.name === group.category); - } - - for (const category of WD.groupCategoryData) { - category.groups = WD.groupData.filter(x => x.category === category); - } - - const albumAndTrackDataSortedByArtDateMan = sortByArtDate([...WD.albumData, ...WD.trackData]); - - for (const tag of WD.artTagData) { - tag.things = albumAndTrackDataSortedByArtDateMan.filter(thing => thing.artTags.includes(tag)); - } - - if (WD.wikiInfo.enableFlashesAndGames) { - for (const flash of WD.flashData) { - flash.act = WD.flashActData.find(act => act.name === flash.act); - mapAndFilter(flash, 'tracks', {map: bound.findTrack}); - } - - for (const act of WD.flashActData) { - act.flashes = WD.flashData.filter(flash => flash.act === act); - } - - for (const track of WD.trackData) { - track.flashes = WD.flashData.filter(flash => flash.tracks.includes(track)); - } - } - - for (const artist of WD.artistData) { - const filterProp = (array, prop) => array.filter(thing => thing[prop]?.some(({ who }) => who === artist)); - const filterCommentary = array => array.filter(thing => thing.commentary && thing.commentary.replace(/<\/?b>/g, '').includes('' + artist.name + ':')); - artist.tracks = { - asArtist: filterProp(WD.trackData, 'artists'), - asCommentator: filterCommentary(WD.trackData), - asContributor: filterProp(WD.trackData, 'contributors'), - asCoverArtist: filterProp(WD.trackData, 'coverArtists'), - asAny: WD.trackData.filter(track => ( - [...track.artists, ...track.contributors, ...track.coverArtists || []].some(({ who }) => who === artist) - )) - }; - artist.albums = { - asArtist: filterProp(WD.albumData, 'artists'), - asCommentator: filterCommentary(WD.albumData), - asCoverArtist: filterProp(WD.albumData, 'coverArtists'), - asWallpaperArtist: filterProp(WD.albumData, 'wallpaperArtists'), - asBannerArtist: filterProp(WD.albumData, 'bannerArtists') - }; - if (WD.wikiInfo.enableFlashesAndGames) { - artist.flashes = { - asContributor: filterProp(WD.flashData, 'contributors') - }; - } - } - */ - WD.officialAlbumData = WD.albumData.filter(album => album.groups.some(group => group.directory === OFFICIAL_GROUP_DIRECTORY)); WD.fandomAlbumData = WD.albumData.filter(album => album.groups.every(group => group.directory !== OFFICIAL_GROUP_DIRECTORY)); + if (noBuild) return; + // Makes writing a little nicer on CPU theoretically, 8ut also costs in // performance right now 'cuz it'll w8 for file writes to 8e completed // 8efore moving on to more data processing. So, defaults to zero, which @@ -3248,7 +3238,10 @@ async function main() { to }); + bound.find = bindFind(wikiData, {mode: 'warn'}); + bound.transformInline = bindOpts(transformInline, { + find: bound.find, link: bound.link, replacerSpec, strings, diff --git a/src/util/find.js b/src/util/find.js index e8e04a5..fc82ba9 100644 --- a/src/util/find.js +++ b/src/util/find.js @@ -1,9 +1,21 @@ import { + color, logError, logWarn } from './cli.js'; -function findHelper(keys, dataProp, findFns = {}) { +function warnOrThrow(mode, message) { + switch (mode) { + case 'error': + throw new Error(message); + case 'warn': + logWarn(message); + default: + return null; + } +} + +function findHelper(keys, findFns = {}) { // Note: This cache explicitly *doesn't* support mutable data arrays. If the // data array is modified, make sure it's actually a new array object, not // the original, or the cache here will break and act as though the data @@ -15,18 +27,24 @@ function findHelper(keys, dataProp, findFns = {}) { const keyRefRegex = new RegExp(String.raw`^(?:(${keys.join('|')}):(?=\S))?(.*)$`); - return (fullRef, {wikiData, quiet = false}) => { + // The mode argument here may be 'warn', 'error', or 'quiet'. 'error' throws + // errors for null matches (with details about the error), while 'warn' and + // 'quiet' both return null, with 'warn' logging details directly to the + // console. + return (fullRef, data, {mode = 'warn'} = {}) => { if (!fullRef) return null; if (typeof fullRef !== 'string') { throw new Error(`Got a reference that is ${typeof fullRef}, not string: ${fullRef}`); } - const data = wikiData[dataProp]; - if (!data) { throw new Error(`Expected data to be present`); } + if (!Array.isArray(data) && data.wikiData) { + throw new Error(`Old {wikiData: {...}} format provided`); + } + let cacheForThisData = cache.get(data); const cachedValue = cacheForThisData?.[fullRef]; if (cachedValue) { @@ -40,18 +58,18 @@ function findHelper(keys, dataProp, findFns = {}) { const match = fullRef.match(keyRefRegex); if (!match) { - throw new Error(`Malformed link reference: "${fullRef}"`); + return warnOrThrow(mode, `Malformed link reference: "${fullRef}"`); } const key = match[1]; const ref = match[2]; const found = (key - ? byDirectory(ref, data, quiet) - : byName(ref, data, quiet)); + ? byDirectory(ref, data, mode) + : byName(ref, data, mode)); - if (!found && !quiet) { - logWarn`Didn't match anything for ${fullRef}!`; + if (!found) { + warnOrThrow(mode, `Didn't match anything for ${color.bright(fullRef)}`); } cacheForThisData[fullRef] = found; @@ -60,23 +78,18 @@ function findHelper(keys, dataProp, findFns = {}) { }; } -function matchDirectory(ref, data, quiet) { +function matchDirectory(ref, data, mode) { return data.find(({ directory }) => directory === ref); } -function matchName(ref, data, quiet) { +function matchName(ref, data, mode) { const matches = data.filter(({ name }) => name.toLowerCase() === ref.toLowerCase()); if (matches.length > 1) { - // TODO: This should definitely be a thrown error. - if (!quiet) { - logError`Multiple matches for reference "${ref}". Please resolve:`; - for (const match of matches) { - logError`- ${match.name} (${match.directory})`; - } - logError`Returning null for this reference.`; - } - return null; + return warnOrThrow(mode, + `Multiple matches for reference "${ref}". Please resolve:\n` + + matches.map(match => `- ${match.name} (${match.directory})\n`).join('') + + `Returning null for this reference.`); } if (matches.length === 0) { @@ -85,8 +98,8 @@ function matchName(ref, data, quiet) { const thing = matches[0]; - if (ref !== thing.name && !quiet) { - logWarn`Bad capitalization: ${'\x1b[31m' + ref} -> ${'\x1b[32m' + thing.name}`; + if (ref !== thing.name) { + warnOrThrow(mode, `Bad capitalization: ${color.red(ref)} -> ${color.green(thing.name)}`); } return thing; @@ -97,15 +110,15 @@ function matchTagName(ref, data, quiet) { } const find = { - album: findHelper(['album', 'album-commentary'], 'albumData'), - artist: findHelper(['artist', 'artist-gallery'], 'artistData'), - artTag: findHelper(['tag'], 'artTagData', {byName: matchTagName}), - flash: findHelper(['flash'], 'flashData'), - group: findHelper(['group', 'group-gallery'], 'groupData'), - listing: findHelper(['listing'], 'listingSpec'), - newsEntry: findHelper(['news-entry'], 'newsData'), - staticPage: findHelper(['static'], 'staticPageData'), - track: findHelper(['track'], 'trackData') + album: findHelper(['album', 'album-commentary']), + artist: findHelper(['artist', 'artist-gallery']), + artTag: findHelper(['tag'], {byName: matchTagName}), + flash: findHelper(['flash']), + group: findHelper(['group', 'group-gallery']), + listing: findHelper(['listing']), + newsEntry: findHelper(['news-entry']), + staticPage: findHelper(['static']), + track: findHelper(['track']) }; export default find; diff --git a/src/util/replacer.js b/src/util/replacer.js index 6c52477..0066d21 100644 --- a/src/util/replacer.js +++ b/src/util/replacer.js @@ -1,8 +1,7 @@ -import find from './find.js'; import {logError, logWarn} from './cli.js'; import {escapeRegex} from './sugar.js'; -export function validateReplacerSpec(replacerSpec, link) { +export function validateReplacerSpec(replacerSpec, {find, link}) { let success = true; for (const [key, {link: linkKey, find: findKey, value, html}] of Object.entries(replacerSpec)) { @@ -320,7 +319,7 @@ export function parseInput(input) { } function evaluateTag(node, opts) { - const { input, link, replacerSpec, strings, to, wikiData } = opts; + const { find, input, link, replacerSpec, strings, to, wikiData } = opts; const source = input.slice(node.i, node.iEnd); @@ -348,7 +347,7 @@ function evaluateTag(node, opts) { valueFn ? valueFn(replacerValue) : findKey ? find[findKey]((replacerKeyImplied ? replacerValue - : replacerKey + `:` + replacerValue), {wikiData}) : + : replacerKey + `:` + replacerValue)) : { directory: replacerValue, name: null @@ -417,13 +416,14 @@ function transformNodes(nodes, opts) { return nodes.map(node => transformNode(node, opts)).join(''); } -export function transformInline(input, {replacerSpec, link, strings, to, wikiData}) { +export function transformInline(input, {replacerSpec, find, link, strings, to, wikiData}) { if (!replacerSpec) throw new Error('Expected replacerSpec'); + if (!find) throw new Error('Expected find'); if (!link) throw new Error('Expected link'); if (!strings) throw new Error('Expected strings'); if (!to) throw new Error('Expected to'); if (!wikiData) throw new Error('Expected wikiData'); const nodes = parseInput(input); - return transformNodes(nodes, {input, link, replacerSpec, strings, to, wikiData}); + return transformNodes(nodes, {input, find, link, replacerSpec, strings, to, wikiData}); } -- cgit 1.3.0-6-gf8a5