« get me outta code hell

Merge pull request #152 from hsmusic/skeleton-support - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-02-05 09:35:20 -0400
committerGitHub <noreply@github.com>2023-02-05 09:35:20 -0400
commitf71136f0a03d7f3c36651918af2dc18527dd47be (patch)
tree6df9bba79901eed7cc7d1f16eba0375a21f10280
parentf0af4edfa87e9518703b7c9ab35244911f0a3ac2 (diff)
parent7c668d5cc6a107b3613e6bc1ab20c9005590b3cf (diff)
Merge pull request #152 from hsmusic/skeleton-support
Better support "skeleton" and minimal-content wikis
-rw-r--r--src/data/yaml.js62
-rw-r--r--src/page/album.js8
-rw-r--r--src/page/group.js25
-rw-r--r--src/util/io.js4
4 files changed, 72 insertions, 27 deletions
diff --git a/src/data/yaml.js b/src/data/yaml.js
index b00d68ee..13b746f7 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -4,7 +4,7 @@
 import * as path from 'path';
 import yaml from 'js-yaml';
 
-import {readFile} from 'fs/promises';
+import {readFile, stat} from 'fs/promises';
 import {inspect as nodeInspect} from 'util';
 
 import T from './things/index.js';
@@ -241,7 +241,7 @@ export const processTrackGroupDocument = makeProcessDocument(T.TrackGroup, {
 
 export const processTrackDocument = makeProcessDocument(T.Track, {
   fieldTransformations: {
-    'Duration': getDurationInSeconds,
+    'Duration': parseDuration,
 
     'Date First Released': (value) => new Date(value),
     'Cover Art Date': (value) => new Date(value),
@@ -447,13 +447,9 @@ export function processHomepageLayoutRowDocument(document) {
 
 // --> Utilities shared across document parsing functions
 
-export function getDurationInSeconds(string) {
-  if (typeof string === 'number') {
-    return string;
-  }
-
+export function parseDuration(string) {
   if (typeof string !== 'string') {
-    throw new TypeError(`Expected a string or number, got ${string}`);
+    return string;
   }
 
   const parts = string.split(':').map((n) => parseInt(n));
@@ -467,7 +463,6 @@ export function getDurationInSeconds(string) {
 }
 
 export function parseAdditionalFiles(array) {
-  if (!array) return null;
   if (!Array.isArray(array)) {
     // Error will be caught when validating against whatever this value is
     return array;
@@ -493,8 +488,11 @@ export function parseCommentary(text) {
 }
 
 export function parseContributors(contributors) {
-  if (!contributors) {
-    return null;
+  // If this isn't something we can parse, just return it as-is.
+  // The Thing object's validators will handle the data error better
+  // than we're able to here.
+  if (!Array.isArray(contributors)) {
+    return contributors;
   }
 
   if (contributors.length === 1 && contributors[0].startsWith('<i>')) {
@@ -528,8 +526,11 @@ export function parseContributors(contributors) {
 }
 
 function parseDimensions(string) {
-  if (!string) {
-    return null;
+  // It's technically possible to pass an array like [30, 40] through here.
+  // That's not really an issue because if it isn't of the appropriate shape,
+  // the Thing object's validators will handle the error.
+  if (typeof string !== 'string') {
+    return string;
   }
 
   const parts = string.split(/[x,* ]+/g);
@@ -945,6 +946,30 @@ export async function loadAndProcessDataDocuments({dataPath}) {
               ? await callAsync(dataStep.file, dataPath)
               : dataStep.file);
 
+          const statResult = await callAsync(() =>
+            stat(file).then(
+              () => true,
+              error => {
+                if (error.code === 'ENOENT') {
+                  return false;
+                } else {
+                  throw error;
+                }
+              }));
+
+          if (statResult === false) {
+            const saveResult = call(dataStep.save, {
+              [documentModes.allInOne]: [],
+              [documentModes.oneDocumentTotal]: {},
+            }[documentMode]);
+
+            if (!saveResult) return;
+
+            Object.assign(wikiDataResult, saveResult);
+
+            return;
+          }
+
           const readResult = await callAsync(readFile, file, 'utf-8');
 
           if (!readResult) {
@@ -992,7 +1017,16 @@ export async function loadAndProcessDataDocuments({dataPath}) {
 
         let files = (
           typeof dataStep.files === 'function'
-            ? await callAsync(dataStep.files, dataPath)
+            ? await callAsync(() =>
+                dataStep.files(dataPath).then(
+                  files => files,
+                  error => {
+                    if (error.code === 'ENOENT') {
+                      return [];
+                    } else {
+                      throw error;
+                    }
+                  }))
             : dataStep.files
         );
 
diff --git a/src/page/album.js b/src/page/album.js
index 1fc74520..80397065 100644
--- a/src/page/album.js
+++ b/src/page/album.js
@@ -53,7 +53,7 @@ export function write(album, {wikiData}) {
   const displayTrackGroups =
     album.trackGroups &&
       (album.trackGroups.length > 1 ||
-        !album.trackGroups[0].isDefaultTrackGroup);
+        !album.trackGroups[0]?.isDefaultTrackGroup);
 
   const listTag = getAlbumListTag(album);
 
@@ -301,6 +301,7 @@ export function write(album, {wikiData}) {
                 })),
 
             displayTrackGroups &&
+            !empty(album.trackGroups) &&
               html.tag('dl',
                 {class: 'album-group-list'},
                 album.trackGroups.flatMap(({
@@ -323,6 +324,7 @@ export function write(album, {wikiData}) {
                 ])),
 
             !displayTrackGroups &&
+            !empty(album.tracks) &&
               html.tag(listTag,
                 album.tracks.map(trackToListItem)),
 
@@ -759,6 +761,10 @@ export function generateAlbumNavLinks(album, currentTrack, {
     randomLink,
   ].filter(Boolean);
 
+  if (empty(allLinks)) {
+    return '';
+  }
+
   return `(${language.formatUnitList(allLinks)})`;
 }
 
diff --git a/src/page/group.js b/src/page/group.js
index 6bfd1532..f9af8e80 100644
--- a/src/page/group.js
+++ b/src/page/group.js
@@ -16,8 +16,7 @@ export function targets({wikiData}) {
 export function write(group, {wikiData}) {
   const {listingSpec, wikiInfo} = wikiData;
 
-  const {albums} = group;
-  const tracks = albums.flatMap((album) => album.tracks);
+  const tracks = group.albums.flatMap((album) => album.tracks);
   const totalDuration = getTotalDuration(tracks, {originalReleasesOnly: true});
 
   const albumLines = group.albums.map((album) => ({
@@ -65,7 +64,7 @@ export function write(group, {wikiData}) {
               transformMultiline(group.description)),
 
           ...html.fragment(
-            group.albums && [
+            !empty(group.albums) && [
               html.tag('h2',
                 {class: ['content-heading']},
                 language.$('groupInfoPage.albumList.title')),
@@ -123,7 +122,7 @@ export function write(group, {wikiData}) {
     }),
   };
 
-  const galleryPage = {
+  const galleryPage = !empty(group.albums) && {
     type: 'page',
     path: ['groupGallery', group.directory],
     page: ({
@@ -165,7 +164,7 @@ export function write(group, {wikiData}) {
                   unit: true,
                 })),
               albums: html.tag('b',
-                language.countAlbums(albums.length, {
+                language.countAlbums(group.albums.length, {
                   unit: true,
                 })),
               time: html.tag('b',
@@ -222,7 +221,7 @@ export function write(group, {wikiData}) {
     }),
   };
 
-  return [infoPage, galleryPage];
+  return [infoPage, galleryPage].filter(Boolean);
 }
 
 // Utility functions
@@ -240,8 +239,6 @@ function generateGroupSidebar(currentGroup, isGallery, {
     return null;
   }
 
-  const linkKey = isGallery ? 'groupGallery' : 'groupInfo';
-
   return {
     content: [
       html.tag('h1',
@@ -260,15 +257,21 @@ function generateGroupSidebar(currentGroup, isGallery, {
                 category: `<span class="group-name">${category.name}</span>`,
               })),
             html.tag('ul',
-              category.groups.map((group) =>
-                html.tag('li',
+              category.groups.map((group) => {
+                const linkKey = (
+                  isGallery && !empty(group.albums)
+                    ? 'groupGallery'
+                    : 'groupInfo');
+
+                return html.tag('li',
                   {
                     class: group === currentGroup && 'current',
                     style: getLinkThemeString(group.color),
                   },
                   language.$('groupSidebar.groupList.item', {
                     group: link[linkKey](group),
-                  })))),
+                  }));
+              })),
           ])),
     ],
   };
diff --git a/src/util/io.js b/src/util/io.js
index 6cc89b56..12e87f4d 100644
--- a/src/util/io.js
+++ b/src/util/io.js
@@ -12,7 +12,9 @@ export async function findFiles(dataPath, {
   try {
     files = await readdir(dataPath);
   } catch (error) {
-    throw new AggregateError([error], `Failed to list files from ${dataPath}`);
+    throw Object.assign(
+      new AggregateError([error], `Failed to list files from ${dataPath}`),
+      {code: error.code});
   }
 
   return files