« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/checks.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/checks.js')
-rw-r--r--src/data/checks.js187
1 files changed, 162 insertions, 25 deletions
diff --git a/src/data/checks.js b/src/data/checks.js
index ad621bab..25863d2d 100644
--- a/src/data/checks.js
+++ b/src/data/checks.js
@@ -9,7 +9,6 @@ import {compareArrays, cut, cutStart, empty, getNestedProp, iterateMultiline}
   from '#sugar';
 import Thing from '#thing';
 import thingConstructors from '#things';
-import {commentaryRegexCaseSensitive} from '#wiki-data';
 
 import {
   annotateErrorWithIndex,
@@ -20,6 +19,12 @@ import {
   withAggregate,
 } from '#aggregate';
 
+import {
+  combineWikiDataArrays,
+  commentaryRegexCaseSensitive,
+  oldStyleLyricsDetectionRegex,
+} from '#wiki-data';
+
 function inspect(value, opts = {}) {
   return nodeInspect(value, {colors: ENABLE_COLOR, ...opts});
 }
@@ -50,7 +55,7 @@ export function reportDirectoryErrors(wikiData, {
     if (!thingData) continue;
 
     for (const thing of thingData) {
-      if (findSpec.include && !findSpec.include(thing)) {
+      if (findSpec.include && !findSpec.include(thing, thingConstructors)) {
         continue;
       }
 
@@ -172,6 +177,7 @@ function getFieldPropertyMessage(yamlDocumentSpec, property) {
 // any errors). At the same time, we remove errored references from the thing's
 // data array.
 export function filterReferenceErrors(wikiData, {
+  find,
   bindFind,
 }) {
   const referenceSpec = [
@@ -183,9 +189,14 @@ export function filterReferenceErrors(wikiData, {
       bannerArtistContribs: '_contrib',
       groups: 'group',
       artTags: '_artTag',
+      referencedArtworks: '_artwork',
       commentary: '_commentary',
     }],
 
+    ['artTagData', {
+      directDescendantArtTags: 'artTag',
+    }],
+
     ['flashData', {
       commentary: '_commentary',
     }],
@@ -194,7 +205,13 @@ export function filterReferenceErrors(wikiData, {
       groups: 'group',
     }],
 
-    ['homepageLayout.rows', {
+    ['homepageLayout.sections.rows', {
+      _include: row => row.type === 'album carousel',
+      albums: 'album',
+    }],
+
+    ['homepageLayout.sections.rows', {
+      _include: row => row.type === 'album grid',
       sourceGroup: '_homepageSourceGroup',
       sourceAlbums: 'album',
     }],
@@ -208,14 +225,19 @@ export function filterReferenceErrors(wikiData, {
       flashes: 'flash',
     }],
 
+    ['groupData', {
+      serieses: '_serieses',
+    }],
+
     ['trackData', {
       artistContribs: '_contrib',
       contributorContribs: '_contrib',
       coverArtistContribs: '_contrib',
-      referencedTracks: '_trackNotRerelease',
-      sampledTracks: '_trackNotRerelease',
+      referencedTracks: '_trackMainReleasesOnly',
+      sampledTracks: '_trackMainReleasesOnly',
       artTags: '_artTag',
-      originalReleaseTrack: '_trackNotRerelease',
+      referencedArtworks: '_artwork',
+      mainReleaseTrack: '_trackMainReleasesOnly',
       commentary: '_commentary',
     }],
 
@@ -230,11 +252,23 @@ export function filterReferenceErrors(wikiData, {
   const aggregate = openAggregate({message: `Errors validating between-thing references in data`});
   for (const [thingDataProp, propSpec] of referenceSpec) {
     const thingData = getNestedProp(wikiData, thingDataProp);
-    const things = Array.isArray(thingData) ? thingData : [thingData];
+    const things =
+      (Array.isArray(thingData)
+        ? thingData.flat(Infinity)
+        : [thingData]);
+
     aggregate.nest({message: `Reference errors in ${colors.green('wikiData.' + thingDataProp)}`}, ({nest}) => {
       for (const thing of things) {
+        if (propSpec._include && !propSpec._include(thing)) {
+          continue;
+        }
+
         nest({message: `Reference errors in ${inspect(thing)}`}, ({nest, push, filter}) => {
           for (const [property, findFnKey] of Object.entries(propSpec)) {
+            if (property === '_include') {
+              continue;
+            }
+
             let value = CacheableObject.getUpdateValue(thing, property);
             let writeProperty = true;
 
@@ -258,6 +292,15 @@ export function filterReferenceErrors(wikiData, {
                 // need writing, humm...)
                 writeProperty = false;
                 break;
+
+              case '_serieses':
+                if (value) {
+                  // Doesn't report on which series has the error, but...
+                  value = value.flatMap(series => series.albums);
+                }
+
+                writeProperty = false;
+                break;
             }
 
             if (value === undefined) {
@@ -272,6 +315,21 @@ export function filterReferenceErrors(wikiData, {
             let findFn;
 
             switch (findFnKey) {
+              case '_artwork': {
+                const mixed =
+                  find.mixed({
+                    album: find.albumPrimaryArtwork,
+                    track: find.trackPrimaryArtwork,
+                  });
+
+                const data =
+                  wikiData.artworkData;
+
+                findFn = ref => mixed(ref.reference, data, {mode: 'error'});
+
+                break;
+              }
+
               case '_artTag':
                 findFn = boundFind.artTag;
                 break;
@@ -294,29 +352,36 @@ export function filterReferenceErrors(wikiData, {
                 };
                 break;
 
-              case '_trackNotRerelease':
+              case '_serieses':
+                findFn = boundFind.album;
+                break;
+
+              case '_trackArtwork':
+                findFn = ref => boundFind.track(ref.reference);
+                break;
+
+              case '_trackMainReleasesOnly':
                 findFn = trackRef => {
                   const track = boundFind.track(trackRef);
-                  const originalRef = track && CacheableObject.getUpdateValue(track, 'originalReleaseTrack');
+                  const mainRef = track && CacheableObject.getUpdateValue(track, 'mainReleaseTrack');
 
-                  if (originalRef) {
-                    // It's possible for the original to not actually exist, in this case.
-                    // It should still be reported since the 'Originally Released As' field
-                    // was present.
-                    const original = boundFind.track(originalRef, {mode: 'quiet'});
+                  if (mainRef) {
+                    // It's possible for the main release to not actually exist, in this case.
+                    // It should still be reported since the 'Main Release' field was present.
+                    const main = boundFind.track(mainRef, {mode: 'quiet'});
 
                     // Prefer references by name, but only if it's unambiguous.
-                    const originalByName =
-                      (original
-                        ? boundFind.track(original.name, {mode: 'quiet'})
+                    const mainByName =
+                      (main
+                        ? boundFind.track(main.name, {mode: 'quiet'})
                         : null);
 
                     const shouldBeMessage =
-                      (originalByName
-                        ? colors.green(original.name)
-                     : original
-                        ? colors.green('track:' + original.directory)
-                        : colors.green(originalRef));
+                      (mainByName
+                        ? colors.green(main.name)
+                     : main
+                        ? colors.green('track:' + main.directory)
+                        : colors.green(mainRef));
 
                     throw new Error(`Reference ${colors.red(trackRef)} is to a rerelease, should be ${shouldBeMessage}`);
                   }
@@ -331,6 +396,10 @@ export function filterReferenceErrors(wikiData, {
             }
 
             const suppress = fn => conditionallySuppressError(error => {
+              // We're not suppressing any errors at the moment.
+              // An old suppression is kept below for reference.
+
+              /*
               if (property === 'sampledTracks') {
                 // Suppress "didn't match anything" errors in particular, just for samples.
                 // In hsmusic-data we have a lot of "stub" sample data which don't have
@@ -343,6 +412,7 @@ export function filterReferenceErrors(wikiData, {
                   return true;
                 }
               }
+              */
 
               return false;
             }, fn);
@@ -503,12 +573,22 @@ export function reportContentTextErrors(wikiData, {
     annotation: 'commentary annotation',
   };
 
+  const newStyleLyricsShape = {
+    body: 'lyrics body',
+    artistDisplayText: 'lyrics artist display text',
+    annotation: 'lyrics annotation',
+  };
+
   const contentTextSpec = [
     ['albumData', {
       additionalFiles: additionalFileShape,
       commentary: commentaryShape,
     }],
 
+    ['artTagData', {
+      description: '_content',
+    }],
+
     ['artistData', {
       contextNotes: '_content',
     }],
@@ -544,7 +624,8 @@ export function reportContentTextErrors(wikiData, {
     ['trackData', {
       additionalFiles: additionalFileShape,
       commentary: commentaryShape,
-      lyrics: '_content',
+      creditSources: commentaryShape,
+      lyrics: '_lyrics',
       midiProjectFiles: additionalFileShape,
       sheetMusicFiles: additionalFileShape,
     }],
@@ -667,8 +748,9 @@ export function reportContentTextErrors(wikiData, {
         for (const thing of things) {
           nest({message: `Content text errors in ${inspect(thing)}`}, ({nest, push}) => {
 
-            for (const [property, shape] of Object.entries(propSpec)) {
-              const value = thing[property];
+            for (let [property, shape] of Object.entries(propSpec)) {
+              const rawValue = CacheableObject.getUpdateValue(thing, property);
+              let value = thing[property];
 
               if (value === undefined) {
                 push(new TypeError(`Property ${colors.red(property)} isn't valid for ${colors.green(thing.constructor.name)}`));
@@ -679,6 +761,15 @@ export function reportContentTextErrors(wikiData, {
                 continue;
               }
 
+              if (shape === '_lyrics') {
+                if (oldStyleLyricsDetectionRegex.test(rawValue)) {
+                  value = rawValue;
+                  shape = '_content';
+                } else {
+                  shape = newStyleLyricsShape;
+                }
+              }
+
               const fieldPropertyMessage =
                 getFieldPropertyMessage(
                   thing.constructor[Thing.yamlDocumentSpec],
@@ -722,3 +813,49 @@ export function reportContentTextErrors(wikiData, {
     }
   });
 }
+
+export function reportOrphanedArtworks(wikiData) {
+  const aggregate =
+    openAggregate({message: `Artwork objects are orphaned`});
+
+  const assess = ({
+    message,
+    filterThing,
+    filterContribs,
+    link,
+  }) => {
+    aggregate.nest({message: `Orphaned ${message}`}, ({push}) => {
+      const ostensibleArtworks =
+        wikiData.artworkData
+          .filter(artwork =>
+            artwork.thing instanceof filterThing &&
+            artwork.artistContribsFromThingProperty === filterContribs);
+
+      const orphanedArtworks =
+        ostensibleArtworks
+          .filter(artwork => !artwork.thing[link].includes(artwork));
+
+      for (const artwork of orphanedArtworks) {
+        push(new Error(`Orphaned: ${inspect(artwork)}`));
+      }
+    });
+  };
+
+  const {Album, Track} = thingConstructors;
+
+  assess({
+    message: `album cover artworks`,
+    filterThing: Album,
+    filterContribs: 'coverArtistContribs',
+    link: 'coverArtworks',
+  });
+
+  assess({
+    message: `track artworks`,
+    filterThing: Track,
+    filterContribs: 'coverArtistContribs',
+    link: 'trackArtworks',
+  });
+
+  aggregate.close();
+}