« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/data/checks.js156
-rw-r--r--src/data/composite/things/track/withMainRelease.js6
-rw-r--r--src/data/composite/things/track/withMainReleaseTrack.js14
-rw-r--r--src/data/composite/things/track/withPropertyFromMainRelease.js2
-rw-r--r--src/data/things/track.js4
-rw-r--r--src/find.js19
6 files changed, 150 insertions, 51 deletions
diff --git a/src/data/checks.js b/src/data/checks.js
index b7a237ad..e68b2ed0 100644
--- a/src/data/checks.js
+++ b/src/data/checks.js
@@ -11,6 +11,7 @@ import Thing from '#thing';
 import thingConstructors from '#things';
 
 import {
+  annotateError,
   annotateErrorWithIndex,
   conditionallySuppressError,
   decorateErrorWithIndex,
@@ -165,6 +166,69 @@ function getFieldPropertyMessage(yamlDocumentSpec, property) {
   return fieldPropertyMessage;
 }
 
+function decoAnnotateFindErrors(findFn) {
+  function annotateMultipleNameMatchesIncludingUnfortunatelyUnsecondary(error) {
+    const matches = error[Symbol.for('hsmusic.find.multipleNameMatches')];
+    if (!matches) return;
+
+    const notSoSecondary =
+      matches
+        .map(match => match.thing ?? match)
+        .filter(match =>
+          match.isTrack &&
+          match.isMainRelease &&
+          CacheableObject.getUpdateValue(match, 'mainRelease'));
+
+    if (empty(notSoSecondary)) return;
+
+    let {message} = error;
+    message += (message.includes('\n') ? '\n\n' : '\n');
+    message += colors.bright(colors.yellow('<!>')) + ' ';
+    message += colors.yellow(`Some of these tracks are meant to be secondary releases,`) + '\n';
+    message += ' '.repeat(4);
+    message += colors.yellow(`but another error is keeping that from processing correctly!`) + '\n';
+    message += ' '.repeat(4);
+    message += colors.yellow(`Probably look for an error to do with "Main Release", first.`);
+    Object.assign(error, {message});
+  }
+
+  return (...args) => {
+    try {
+      return findFn(...args);
+    } catch (caughtError) {
+      throw annotateError(caughtError, ...[
+        annotateMultipleNameMatchesIncludingUnfortunatelyUnsecondary,
+      ]);
+    }
+  };
+}
+
+function decoSuppressFindErrors(findFn, {property}) {
+  void property;
+
+  return 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
+      // corresponding tracks yet, so it won't be useful to report such reference
+      // errors until we take the time to address that. But other errors, like
+      // malformed reference strings or miscapitalized existing tracks, should
+      // still be reported, as samples of existing tracks *do* display on the
+      // website!
+      if (error.message.includes(`Didn't match anything`)) {
+        return true;
+      }
+    }
+    */
+
+    return false;
+  }, findFn);
+}
+
 // 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
@@ -353,7 +417,12 @@ export function filterReferenceErrors(wikiData, {
                   if (ref === 'same name single') {
                     // Accessing the current thing here.
                     try {
-                      return boundFind.albumSinglesOnly(thing.name);
+                      return boundFind.albumSinglesOnly(thing.name, {
+                        fuzz: {
+                          capitalization: true,
+                          kebab: true,
+                        },
+                      });
                     } catch (caughtError) {
                       throw new Error(
                         `Didn't match a single with the same name`,
@@ -400,11 +469,20 @@ export function filterReferenceErrors(wikiData, {
                     // gets refactored, there might be trouble here...
 
                     if (thing.mainReleaseTrack === null) {
-                      throw new Error(
-                        `Matched album for reference "${ref}":\n` +
-                        `- ` + inspect(album) + `\n` +
-                        `...but none of its tracks automatically match this secondary release.\n` +
-                        `Please resolve by specifying the track here, instead of the album.`);
+                      if (album === thing.album) {
+                        throw new Error(
+                          `Matched album for reference "${ref}":\n` +
+                          `- ` + inspect(album) + `\n` +
+                          `...but this is the album that includes this secondary release, itself.\n` +
+                          `Please resolve by pointing to aonther album here, or by removing this\n` +
+                          `Main Release field, if this track is meant to be the main release.`);
+                      } else {
+                        throw new Error(
+                          `Matched album for reference "${ref}":\n` +
+                          `- ` + inspect(album) + `\n` +
+                          `...but none of its tracks automatically match this secondary release.\n` +
+                          `Please resolve by specifying the track here, instead of the album.`);
+                      }
                     } else {
                       return album;
                     }
@@ -428,9 +506,16 @@ export function filterReferenceErrors(wikiData, {
 
               case '_trackMainReleasesOnly':
                 findFn = trackRef => {
-                  const track = boundFind.track(trackRef);
-                  const mainRef = track && CacheableObject.getUpdateValue(track, 'mainRelease');
+                  let track = boundFind.trackMainReleasesOnly(trackRef, {mode: 'quiet'});
+                  if (track) {
+                    return track;
+                  }
 
+                  // Will error normally, if this can't unambiguously resolve
+                  // or doesn't match any track.
+                  track = boundFind.track(trackRef);
+
+                  const mainRef = CacheableObject.getUpdateValue(track, 'mainRelease');
                   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.
@@ -461,27 +546,8 @@ export function filterReferenceErrors(wikiData, {
                 break;
             }
 
-            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
-                // corresponding tracks yet, so it won't be useful to report such reference
-                // errors until we take the time to address that. But other errors, like
-                // malformed reference strings or miscapitalized existing tracks, should
-                // still be reported, as samples of existing tracks *do* display on the
-                // website!
-                if (error.message.includes(`Didn't match anything`)) {
-                  return true;
-                }
-              }
-              */
-
-              return false;
-            }, fn);
+            findFn = decoSuppressFindErrors(findFn, {property});
+            findFn = decoAnnotateFindErrors(findFn);
 
             const fieldPropertyMessage =
               getFieldPropertyMessage(
@@ -537,10 +603,10 @@ export function filterReferenceErrors(wikiData, {
                   value, {message: errorMessage},
                   decorateErrorWithIndex(refs =>
                     (refs.length === 1
-                      ? suppress(findFn)(refs[0])
+                      ? findFn(refs[0])
                       : filterAggregate(
                           refs, {message: `Errors in entry's artist references`},
-                          decorateErrorWithIndex(suppress(findFn)))
+                          decorateErrorWithIndex(findFn))
                             .aggregate
                             .close())));
 
@@ -552,19 +618,18 @@ export function filterReferenceErrors(wikiData, {
               if (Array.isArray(value)) {
                 newPropertyValue = filter(
                   value, {message: errorMessage},
-                  decorateErrorWithIndex(suppress(findFn)));
+                  decorateErrorWithIndex(findFn));
                 break determineNewPropertyValue;
               }
 
-              nest({message: errorMessage},
-                suppress(({call}) => {
-                  try {
-                    call(findFn, value);
-                  } catch (error) {
-                    newPropertyValue = null;
-                    throw error;
-                  }
-                }));
+              nest({message: errorMessage}, ({call}) => {
+                try {
+                  call(findFn, value);
+                } catch (error) {
+                  newPropertyValue = null;
+                  throw error;
+                }
+              });
             }
 
             if (writeProperty) {
@@ -588,7 +653,11 @@ export class ContentNodeError extends Error {
     message,
   }) {
     const headingLine =
-      `(${where}) ${message}`;
+      (message.includes('\n\n')
+        ? `(${where})\n\n` + message + '\n'
+     : message.includes('\n')
+        ? `(${where})\n` + message
+        : `(${where}) ${message}`);
 
     const textUpToNode =
       containingLine.slice(0, columnNumber);
@@ -762,6 +831,9 @@ export function reportContentTextErrors(wikiData, {
               break;
           }
 
+          findFn = decoSuppressFindErrors(findFn, {property: null});
+          findFn = decoAnnotateFindErrors(findFn);
+
           const findRef =
             (replacerKeyImplied
               ? replacerValue
diff --git a/src/data/composite/things/track/withMainRelease.js b/src/data/composite/things/track/withMainRelease.js
index f1b0841a..67a312ae 100644
--- a/src/data/composite/things/track/withMainRelease.js
+++ b/src/data/composite/things/track/withMainRelease.js
@@ -67,6 +67,12 @@ export default templateCompositeFrom({
     withResolvedReference({
       ref: '#sameNameSingleReference',
       find: soupyFind.input('albumSinglesOnly'),
+      findOptions: input.value({
+        fuzz: {
+          capitalization: true,
+          kebab: true,
+        },
+      }),
     }).outputs({
       '#resolvedReference': '#sameNameSingle',
     }),
diff --git a/src/data/composite/things/track/withMainReleaseTrack.js b/src/data/composite/things/track/withMainReleaseTrack.js
index 871eba7d..e498582d 100644
--- a/src/data/composite/things/track/withMainReleaseTrack.js
+++ b/src/data/composite/things/track/withMainReleaseTrack.js
@@ -203,5 +203,19 @@ export default templateCompositeFrom({
           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/withPropertyFromMainRelease.js b/src/data/composite/things/track/withPropertyFromMainRelease.js
index 3e1b6d19..c6f65653 100644
--- a/src/data/composite/things/track/withPropertyFromMainRelease.js
+++ b/src/data/composite/things/track/withPropertyFromMainRelease.js
@@ -13,7 +13,7 @@ import {withPropertyFromObject} from '#composite/data';
 import withMainReleaseTrack from './withMainReleaseTrack.js';
 
 export default templateCompositeFrom({
-  annotation: `inheritFromMainRelease`,
+  annotation: `withPropertyFromMainRelease`,
 
   inputs: {
     property: input({type: 'string'}),
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 110769e0..1871e86b 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -398,7 +398,7 @@ export class Track extends Thing {
 
       referenceList({
         class: input.value(Track),
-        find: soupyFind.input('track'),
+        find: soupyFind.input('trackMainReleasesOnly'),
       }),
     ],
 
@@ -409,7 +409,7 @@ export class Track extends Thing {
 
       referenceList({
         class: input.value(Track),
-        find: soupyFind.input('track'),
+        find: soupyFind.input('trackMainReleasesOnly'),
       }),
     ],
 
diff --git a/src/find.js b/src/find.js
index b44c1bb2..7b605e97 100644
--- a/src/find.js
+++ b/src/find.js
@@ -140,12 +140,19 @@ function oopsMultipleNameMatches(mode, {
   normalizedName,
   multipleNameMatches,
 }) {
-  return warnOrThrow(mode,
-    `Multiple matches for reference "${name}". Please resolve:\n` +
-    multipleNameMatches[normalizedName]
-      .map(match => `- ${inspect(match)}\n`)
-      .join('') +
-    `Returning null for this reference.`);
+  try {
+    return warnOrThrow(mode,
+      `Multiple matches for reference "${name}". Please resolve:\n` +
+      multipleNameMatches[normalizedName]
+        .map(match => `- ${inspect(match)}\n`)
+        .join('') +
+      `Returning null for this reference.`);
+  } catch (caughtError) {
+    throw Object.assign(caughtError, {
+      [Symbol.for('hsmusic.find.multipleNameMatches')]:
+        multipleNameMatches[normalizedName],
+    });
+  }
 }
 
 function oopsNameCapitalizationMismatch(mode, {