« get me outta code hell

yaml, data: store document specs statically on Thing subclasses - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-01-20 16:13:36 -0400
committer(quasar) nebula <qznebula@protonmail.com>2024-01-30 07:59:34 -0400
commit296a4961a951e44ea53509391ad225d1491197f9 (patch)
tree4bdedf0f85b7af8d3039bb46ccfd2f1f600db5ce /src/data/things
parentac277f23abe0d8432a94f72913f4421b0eebaa62 (diff)
yaml, data: store document specs statically on Thing subclasses
Diffstat (limited to 'src/data/things')
-rw-r--r--src/data/things/album.js79
-rw-r--r--src/data/things/art-tag.js11
-rw-r--r--src/data/things/artist.js20
-rw-r--r--src/data/things/flash.js43
-rw-r--r--src/data/things/group.js20
-rw-r--r--src/data/things/homepage-layout.js29
-rw-r--r--src/data/things/news-entry.js13
-rw-r--r--src/data/things/static-page.js14
-rw-r--r--src/data/things/thing.js42
-rw-r--r--src/data/things/track.js95
-rw-r--r--src/data/things/wiki-info.js18
11 files changed, 376 insertions, 8 deletions
diff --git a/src/data/things/album.js b/src/data/things/album.js
index e48ad41a..02d34544 100644
--- a/src/data/things/album.js
+++ b/src/data/things/album.js
@@ -25,10 +25,13 @@ import {
   wikiData,
 } from '#composite/wiki-properties';
 
+import {withTracks, withTrackSections} from '#composite/things/album';
+
 import {
-  withTracks,
-  withTrackSections,
-} from '#composite/things/album';
+  parseAdditionalFiles,
+  parseContributors,
+  parseDimensions,
+} from '#yaml';
 
 import Thing from './thing.js';
 
@@ -200,6 +203,64 @@ export class Album extends Thing {
     artTags: S.toRefs,
     commentatorArtists: S.toRefs,
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    fieldTransformations: {
+      'Artists': parseContributors,
+      'Cover Artists': parseContributors,
+      'Default Track Cover Artists': parseContributors,
+      'Wallpaper Artists': parseContributors,
+      'Banner Artists': parseContributors,
+
+      'Date': (value) => new Date(value),
+      'Date Added': (value) => new Date(value),
+      'Cover Art Date': (value) => new Date(value),
+      'Default Track Cover Art Date': (value) => new Date(value),
+
+      'Banner Dimensions': parseDimensions,
+
+      'Additional Files': parseAdditionalFiles,
+    },
+
+    propertyFieldMapping: {
+      name: 'Album',
+      directory: 'Directory',
+      date: 'Date',
+      color: 'Color',
+      urls: 'URLs',
+
+      hasTrackNumbers: 'Has Track Numbers',
+      isListedOnHomepage: 'Listed on Homepage',
+      isListedInGalleries: 'Listed in Galleries',
+
+      coverArtDate: 'Cover Art Date',
+      trackArtDate: 'Default Track Cover Art Date',
+      dateAddedToWiki: 'Date Added',
+
+      coverArtFileExtension: 'Cover Art File Extension',
+      trackCoverArtFileExtension: 'Track Art File Extension',
+
+      wallpaperArtistContribs: 'Wallpaper Artists',
+      wallpaperStyle: 'Wallpaper Style',
+      wallpaperFileExtension: 'Wallpaper File Extension',
+
+      bannerArtistContribs: 'Banner Artists',
+      bannerStyle: 'Banner Style',
+      bannerFileExtension: 'Banner File Extension',
+      bannerDimensions: 'Banner Dimensions',
+
+      commentary: 'Commentary',
+      additionalFiles: 'Additional Files',
+
+      artistContribs: 'Artists',
+      coverArtistContribs: 'Cover Artists',
+      trackCoverArtistContribs: 'Default Track Cover Artists',
+      groups: 'Groups',
+      artTags: 'Art Tags',
+    },
+
+    ignoredFields: ['Review Points'],
+  };
 }
 
 export class TrackSectionHelper extends Thing {
@@ -211,4 +272,16 @@ export class TrackSectionHelper extends Thing {
     dateOriginallyReleased: simpleDate(),
     isDefaultTrackGroup: flag(false),
   })
+
+  static [Thing.yamlDocumentSpec] = {
+    fieldTransformations: {
+      'Date Originally Released': (value) => new Date(value),
+    },
+
+    propertyFieldMapping: {
+      name: 'Section',
+      color: 'Color',
+      dateOriginallyReleased: 'Date Originally Released',
+    },
+  };
 }
diff --git a/src/data/things/art-tag.js b/src/data/things/art-tag.js
index f9e5f0f3..c0b4a6d6 100644
--- a/src/data/things/art-tag.js
+++ b/src/data/things/art-tag.js
@@ -63,4 +63,15 @@ export class ArtTag extends Thing {
       },
     },
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Tag',
+      nameShort: 'Short Name',
+      directory: 'Directory',
+
+      color: 'Color',
+      isContentWarning: 'Is CW',
+    },
+  };
 }
diff --git a/src/data/things/artist.js b/src/data/things/artist.js
index a58cebc4..42090557 100644
--- a/src/data/things/artist.js
+++ b/src/data/things/artist.js
@@ -16,9 +16,7 @@ import {
   wikiData,
 } from '#composite/wiki-properties';
 
-import {
-  withReverseContributionList,
-} from '#composite/wiki-data';
+import {withReverseContributionList} from '#composite/wiki-data';
 
 import Thing from './thing.js';
 
@@ -242,4 +240,20 @@ export class Artist extends Thing {
 
     flashesAsContributor: S.toRefs,
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Artist',
+      directory: 'Directory',
+      urls: 'URLs',
+      contextNotes: 'Context Notes',
+
+      hasAvatar: 'Has Avatar',
+      avatarFileExtension: 'Avatar File Extension',
+
+      aliasNames: 'Aliases',
+    },
+
+    ignoredFields: ['Dead URLs', 'Review Points'],
+  };
 }
diff --git a/src/data/things/flash.js b/src/data/things/flash.js
index 85fe343e..d7e8bb46 100644
--- a/src/data/things/flash.js
+++ b/src/data/things/flash.js
@@ -28,6 +28,8 @@ import {
 
 import {withFlashAct} from '#composite/things/flash';
 
+import {parseContributors} from '#yaml';
+
 import Thing from './thing.js';
 
 export class Flash extends Thing {
@@ -133,6 +135,30 @@ export class Flash extends Thing {
     urls: S.id,
     color: S.id,
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    fieldTransformations: {
+      'Date': (value) => new Date(value),
+
+      'Contributors': parseContributors,
+    },
+
+    propertyFieldMapping: {
+      name: 'Flash',
+      directory: 'Directory',
+      page: 'Page',
+      color: 'Color',
+      urls: 'URLs',
+
+      date: 'Date',
+      coverArtFileExtension: 'Cover Art File Extension',
+
+      featuredTracks: 'Featured Tracks',
+      contributorContribs: 'Contributors',
+    },
+
+    ignoredFields: ['Review Points'],
+  };
 }
 
 export class FlashAct extends Thing {
@@ -170,5 +196,20 @@ export class FlashAct extends Thing {
     flashData: wikiData({
       class: input.value(Flash),
     }),
-  })
+  });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Act',
+      directory: 'Directory',
+
+      color: 'Color',
+      listTerminology: 'List Terminology',
+
+      jump: 'Jump',
+      jumpColor: 'Jump Color',
+    },
+
+    ignoredFields: ['Review Points'],
+  };
 }
diff --git a/src/data/things/group.js b/src/data/things/group.js
index 38d169de..a9708fb4 100644
--- a/src/data/things/group.js
+++ b/src/data/things/group.js
@@ -85,6 +85,19 @@ export class Group extends Thing {
       },
     },
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Group',
+      directory: 'Directory',
+      description: 'Description',
+      urls: 'URLs',
+
+      featuredAlbums: 'Featured Albums',
+    },
+
+    ignoredFields: ['Review Points'],
+  };
 }
 
 export class GroupCategory extends Thing {
@@ -111,4 +124,11 @@ export class GroupCategory extends Thing {
       class: input.value(Group),
     }),
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Category',
+      color: 'Color',
+    },
+  };
 }
diff --git a/src/data/things/homepage-layout.js b/src/data/things/homepage-layout.js
index dd6c1d9d..b4fb97db 100644
--- a/src/data/things/homepage-layout.js
+++ b/src/data/things/homepage-layout.js
@@ -45,7 +45,16 @@ export class HomepageLayout extends Thing {
         validate: validateArrayItems(validateInstanceOf(HomepageLayoutRow)),
       },
     },
-  })
+  });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      sidebarContent: 'Sidebar Content',
+      navbarLinks: 'Navbar Links',
+    },
+
+    ignoredFields: ['Homepage'],
+  };
 }
 
 export class HomepageLayoutRow extends Thing {
@@ -82,6 +91,14 @@ export class HomepageLayoutRow extends Thing {
       class: input.value(Group),
     }),
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Row',
+      color: 'Color',
+      type: 'Type',
+    },
+  };
 }
 
 export class HomepageLayoutAlbumsRow extends HomepageLayoutRow {
@@ -162,4 +179,14 @@ export class HomepageLayoutAlbumsRow extends HomepageLayoutRow {
       update: {validate: validateArrayItems(isString)},
     },
   });
+
+  static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(HomepageLayoutRow, {
+    propertyFieldMapping: {
+      displayStyle: 'Display Style',
+      sourceGroup: 'Group',
+      countAlbumsFromGroup: 'Count',
+      sourceAlbums: 'Albums',
+      actionLinks: 'Actions',
+    },
+  });
 }
diff --git a/src/data/things/news-entry.js b/src/data/things/news-entry.js
index f220b270..06dad629 100644
--- a/src/data/things/news-entry.js
+++ b/src/data/things/news-entry.js
@@ -32,4 +32,17 @@ export class NewsEntry extends Thing {
       },
     },
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    fieldTransformations: {
+      'Date': (value) => new Date(value),
+    },
+
+    propertyFieldMapping: {
+      name: 'Name',
+      directory: 'Directory',
+      date: 'Date',
+      content: 'Content',
+    },
+  };
 }
diff --git a/src/data/things/static-page.js b/src/data/things/static-page.js
index d1cc5b26..00c0b09c 100644
--- a/src/data/things/static-page.js
+++ b/src/data/things/static-page.js
@@ -33,4 +33,18 @@ export class StaticPage extends Thing {
     stylesheet: simpleString(),
     script: simpleString(),
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Name',
+      nameShort: 'Short Name',
+      directory: 'Directory',
+
+      stylesheet: 'Style',
+      script: 'Script',
+      content: 'Content',
+    },
+
+    ignoredFields: ['Review Points'],
+  };
 }
diff --git a/src/data/things/thing.js b/src/data/things/thing.js
index def7e914..42971c04 100644
--- a/src/data/things/thing.js
+++ b/src/data/things/thing.js
@@ -14,6 +14,8 @@ export default class Thing extends CacheableObject {
   static getPropertyDescriptors = Symbol('Thing.getPropertyDescriptors');
   static getSerializeDescriptors = Symbol('Thing.getSerializeDescriptors');
 
+  static yamlDocumentSpec = Symbol.for('Thing.yamlDocumentSpec');
+
   // Default custom inspect function, which may be overridden by Thing
   // subclasses. This will be used when displaying aggregate errors and other
   // command-line logging - it's the place to provide information useful in
@@ -38,4 +40,44 @@ export default class Thing extends CacheableObject {
 
     return `${thing.constructor[Thing.referenceType]}:${thing.directory}`;
   }
+
+  static extendDocumentSpec(thingClass, subspec) {
+    const superspec = thingClass[Thing.yamlDocumentSpec];
+
+    const {
+      fieldTransformations,
+      propertyFieldMapping,
+      ignoredFields,
+      invalidFieldCombinations,
+      ...restOfSubspec
+    } = subspec;
+
+    const newFields =
+      Object.values(subspec.propertyFieldMapping ?? {});
+
+    return {
+      ...superspec,
+      ...restOfSubspec,
+
+      fieldTransformations: {
+        ...superspec.fieldTransformations,
+        ...fieldTransformations,
+      },
+
+      propertyFieldMapping: {
+        ...superspec.propertyFieldMapping,
+        ...propertyFieldMapping,
+      },
+
+      ignoredFields:
+        (superspec.ignoredFields ?? [])
+          .filter(field => newFields.includes(field))
+          .concat(ignoredFields ?? []),
+
+      invalidFieldCombinations: [
+        ...superspec.invalidFieldCombinations ?? [],
+        ...invalidFieldCombinations ?? [],
+      ],
+    };
+  }
 }
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 375dd81d..3621510b 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -55,6 +55,13 @@ import {
   withPropertyFromAlbum,
 } from '#composite/things/track';
 
+import {
+  parseAdditionalFiles,
+  parseAdditionalNames,
+  parseContributors,
+  parseDuration,
+} from '#yaml';
+
 import CacheableObject from './cacheable-object.js';
 import Thing from './thing.js';
 
@@ -332,6 +339,94 @@ export class Track extends Thing {
     }),
   });
 
+  static [Thing.yamlDocumentSpec] = {
+    fieldTransformations: {
+      'Additional Names': parseAdditionalNames,
+      'Duration': parseDuration,
+
+      'Date First Released': (value) => new Date(value),
+      'Cover Art Date': (value) => new Date(value),
+      'Has Cover Art': (value) =>
+        (value === true ? false :
+         value === false ? true :
+         value),
+
+      'Artists': parseContributors,
+      'Contributors': parseContributors,
+      'Cover Artists': parseContributors,
+
+      'Additional Files': parseAdditionalFiles,
+      'Sheet Music Files': parseAdditionalFiles,
+      'MIDI Project Files': parseAdditionalFiles,
+    },
+
+    propertyFieldMapping: {
+      name: 'Track',
+      directory: 'Directory',
+      additionalNames: 'Additional Names',
+      duration: 'Duration',
+      color: 'Color',
+      urls: 'URLs',
+
+      dateFirstReleased: 'Date First Released',
+      coverArtDate: 'Cover Art Date',
+      coverArtFileExtension: 'Cover Art File Extension',
+      disableUniqueCoverArt: 'Has Cover Art', // This gets transformed to flip true/false.
+
+      alwaysReferenceByDirectory: 'Always Reference By Directory',
+
+      lyrics: 'Lyrics',
+      commentary: 'Commentary',
+      additionalFiles: 'Additional Files',
+      sheetMusicFiles: 'Sheet Music Files',
+      midiProjectFiles: 'MIDI Project Files',
+
+      originalReleaseTrack: 'Originally Released As',
+      referencedTracks: 'Referenced Tracks',
+      sampledTracks: 'Sampled Tracks',
+      artistContribs: 'Artists',
+      contributorContribs: 'Contributors',
+      coverArtistContribs: 'Cover Artists',
+      artTags: 'Art Tags',
+    },
+
+    ignoredFields: ['Review Points'],
+
+    invalidFieldCombinations: [
+      {message: `Re-releases inherit references from the original`, fields: [
+        'Originally Released As',
+        'Referenced Tracks',
+      ]},
+
+      {message: `Re-releases inherit samples from the original`, fields: [
+        'Originally Released As',
+        'Sampled Tracks',
+      ]},
+
+      {message: `Re-releases inherit artists from the original`, fields: [
+        'Originally Released As',
+        'Artists',
+      ]},
+
+      {message: `Re-releases inherit contributors from the original`, fields: [
+        'Originally Released As',
+        'Contributors',
+      ]},
+
+      {
+        message: ({'Has Cover Art': hasCoverArt}) =>
+          (hasCoverArt
+            ? `"Has Cover Art: true" is inferred from cover artist credits`
+            : `Tracks without cover art must not have cover artist credits`),
+
+        fields: [
+          'Has Cover Art',
+          'Cover Artists',
+        ],
+      },
+    ],
+  };
+
   [inspect.custom](depth) {
     const parts = [];
 
diff --git a/src/data/things/wiki-info.js b/src/data/things/wiki-info.js
index 112d454f..80793550 100644
--- a/src/data/things/wiki-info.js
+++ b/src/data/things/wiki-info.js
@@ -74,4 +74,22 @@ export class WikiInfo extends Thing {
       class: input.value(Group),
     }),
   });
+
+  static [Thing.yamlDocumentSpec] = {
+    propertyFieldMapping: {
+      name: 'Name',
+      nameShort: 'Short Name',
+      color: 'Color',
+      description: 'Description',
+      footerContent: 'Footer Content',
+      defaultLanguage: 'Default Language',
+      canonicalBase: 'Canonical Base',
+      divideTrackListsByGroups: 'Divide Track Lists By Groups',
+      enableFlashesAndGames: 'Enable Flashes & Games',
+      enableListings: 'Enable Listings',
+      enableNews: 'Enable News',
+      enableArtTagUI: 'Enable Art Tag UI',
+      enableGroupUI: 'Enable Group UI',
+    },
+  };
 }