« get me outta code hell

all-in-one document mode & artist data - 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>2022-01-30 15:01:35 -0400
committer(quasar) nebula <qznebula@protonmail.com>2022-01-30 15:01:35 -0400
commitdfd84ead5bd5dfbcd7c25d08b816b28b4620ee5a (patch)
tree1e6ba1818f12171e3c0b5b37fbf0e8d4d6d14f2c
parentdccb3cd1312cfa9c2ea86eef6833a63b778de157 (diff)
all-in-one document mode & artist data
-rw-r--r--src/thing/artist.js48
-rwxr-xr-xsrc/upd8.js93
-rw-r--r--src/util/sugar.js5
3 files changed, 124 insertions, 22 deletions
diff --git a/src/thing/artist.js b/src/thing/artist.js
new file mode 100644
index 0000000..bbb2a93
--- /dev/null
+++ b/src/thing/artist.js
@@ -0,0 +1,48 @@
+import Thing from './thing.js';
+
+import {
+    isDirectory,
+    isName,
+    isString,
+    isURL,
+    validateArrayItems,
+    validateReferenceList,
+} from './validators.js';
+
+export default class Artist extends Thing {
+    static [Thing.referenceType] = 'artist';
+
+    static propertyDescriptors = {
+        // Update & expose
+
+        name: {
+            flags: {update: true, expose: true},
+
+            update: {
+                default: 'Unnamed Artist',
+                validate: isName
+            }
+        },
+
+        directory: {
+            flags: {update: true, expose: true},
+            update: {validate: isDirectory},
+            expose: Thing.directoryExpose
+        },
+
+        urls: {
+            flags: {update: true, expose: true},
+            update: {validate: validateArrayItems(isURL)}
+        },
+
+        aliasRefs: {
+            flags: {update: true, expose: true},
+            update: {validate: validateReferenceList('artist')}
+        },
+
+        contextNotes: {
+            flags: {update: true, expose: true},
+            update: {validate: isString}
+        },
+    };
+}
diff --git a/src/upd8.js b/src/upd8.js
index 4bf9d43..6df3cc2 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -90,6 +90,7 @@ import * as html from './util/html.js';
 import unbound_link, {getLinkThemeString} from './util/link.js';
 
 import Album, { TrackGroup } from './thing/album.js';
+import Artist from './thing/artist.js';
 import Thing from './thing/thing.js';
 import Track from './thing/track.js';
 
@@ -161,9 +162,9 @@ import {
 
 import {
     bindOpts,
-    call,
     filterAggregateAsync,
     filterEmptyLines,
+    mapAggregate,
     mapAggregateAsync,
     openAggregate,
     queue,
@@ -193,7 +194,7 @@ const CACHEBUST = 7;
 // MAKE THESE END IN YAML
 const WIKI_INFO_FILE = 'wiki-info.txt';
 const HOMEPAGE_INFO_FILE = 'homepage.txt';
-const ARTIST_DATA_FILE = 'artists.txt';
+const ARTIST_DATA_FILE = 'artists.yaml';
 const FLASH_DATA_FILE = 'flashes.txt';
 const NEWS_DATA_FILE = 'news.txt';
 const TAG_DATA_FILE = 'tags.txt';
@@ -781,9 +782,9 @@ function makeParseDocument(thingClass, {
 
         withAggregate({message: `Errors applying ${color.green(thingClass.name)} properties`}, ({ call }) => {
             for (const [ property, value ] of Object.entries(sourceProperties)) {
-                call(() => {
+                (() => {
                     thing[property] = value;
-                });
+                })();
             }
         });
 
@@ -955,6 +956,21 @@ const parseTrackDocument = makeParseDocument(Track, {
     ignoredFields: ['Sampled Tracks']
 });
 
+const processArtistDocument = makeParseDocument(Artist, {
+    propertyFieldMapping: {
+        name: 'Artist',
+
+        directory: 'Directory',
+        urls: 'URLs',
+
+        aliasRefs: 'Aliases',
+
+        contextNotes: 'Context Notes'
+    },
+
+    ignoredFields: ['Dead URLs']
+});
+
 async function processArtistDataFile(file) {
     let contents;
     try {
@@ -1845,10 +1861,10 @@ writePage.html = (pageFn, {
                     cur.toCurrentPage ? '' :
                     cur.toHome ? to('localized.home') :
                     cur.path ? to(...cur.path) :
-                    cur.href ? call(() => {
+                    cur.href ? (() => {
                         logWarn`Using legacy href format nav link in ${paths.pathname}`;
                         return cur.href;
-                    }) :
+                    })() :
                     null)
             };
             if (attributes.href === null) {
@@ -2421,7 +2437,19 @@ async function main() {
                     trackData.push(...tracks);
                 }
 
-                Object.assign(WD, {albumData, trackData});
+                Object.assign(wikiData, {albumData, trackData});
+            }
+        },
+
+        {
+            title: `Process artist file`,
+            files: [path.join(dataPath, ARTIST_DATA_FILE)],
+
+            documentMode: documentModes.allInOne,
+            processDocument: processArtistDocument,
+
+            save(results) {
+                wikiData.artistData = results;
             }
         }
     ];
@@ -2438,9 +2466,10 @@ async function main() {
             entries: dataStep.processEntryDocuments(documents.slice(1))
         }),
 
-        [documentModes.allInOne]: (documents, dataStep) => (
-            documents.map(dataStep.processDocument)
-        )
+        // All in one is kinda tricky because we don't want errors to invalidate
+        // the entire file - only each document. We just special case it,
+        // handling it completely separately later.
+        [documentModes.allInOne]: documentModes.allInOne
     };
 
     function decorateErrorWithFile(file, fn) {
@@ -2458,13 +2487,38 @@ async function main() {
     for (const dataStep of dataSteps) {
         await processDataAggregate.nestAsync(
             {message: `Errors during data step: ${dataStep.title}`},
-            async ({map, mapAsync}) => {
+            async ({call, map, mapAsync}) => {
                 const processDocuments = documentModeFunctions[dataStep.documentMode];
 
                 if (!processDocuments) {
                     throw new Error(`Invalid documentMode: ${dataStep.documentMode}`);
                 }
 
+                if (processDocuments === documentModes.allInOne) {
+                    if (dataStep.files.length !== 1) {
+                        throw new Error(`Expected 1 file for all-in-one documentMode, not ${files.length}`);
+                    }
+
+                    const file = dataStep.files[0];
+                    const readResult = await readFile(file);
+                    const yamlResult = yaml.loadAll(readResult);
+
+                    const {
+                        result: processResults,
+                        aggregate: processAggregate
+                    } = mapAggregate(
+                        yamlResult,
+                        dataStep.processDocument,
+                        {message: `Errors processing documents`}
+                    );
+
+                    call(processAggregate.close);
+
+                    dataStep.save(processResults);
+
+                    return;
+                }
+
                 const readResults = await mapAsync(
                     dataStep.files,
                     file => (readFile(file, 'utf-8')
@@ -2477,27 +2531,32 @@ async function main() {
                 const yamlResults = map(
                     readResults,
                     ({ file, contents }) => decorateErrorWithFile(file,
-                        () => ({file, documents: yaml.loadAll(contents)})
-                    ),
+                        () => ({file, documents: yaml.loadAll(contents)})),
                     {message: `Errors parsing data files as valid YAML`});
 
                 const processResults = map(
                     yamlResults,
                     ({ file, documents }) => decorateErrorWithFile(file,
-                        () => processDocuments(documents, dataStep)
-                    ),
-                    {message: `Errors processing data files as valid wiki documents`,});
+                        () => processDocuments(documents, dataStep)),
+                    {message: `Errors processing data files as valid wiki documents`});
 
                 dataStep.save(processResults);
             });
     }
 
-    console.log(WD);
+    logInfo`Loaded data and processed objects:`;
+    logInfo` - ${wikiData.albumData.length} albums`;
+    logInfo` - ${wikiData.trackData.length} tracks`;
+    logInfo` - ${wikiData.artistData.length} artists`;
 
     try {
         processDataAggregate.close();
     } catch (error) {
         showAggregate(error, {pathToFile: f => path.relative(__dirname, f)});
+        logWarn`The above errors were detected while processing data files.`;
+        logWarn`If the remaining valid data is complete enough, the wiki will`;
+        logWarn`still build - but all errored data will be skipped.`;
+        logWarn`(Resolve errors for more complete output!)`;
     }
 
     process.exit();
diff --git a/src/util/sugar.js b/src/util/sugar.js
index f5d1d29..774534c 100644
--- a/src/util/sugar.js
+++ b/src/util/sugar.js
@@ -35,11 +35,6 @@ export const unique = arr => Array.from(new Set(arr));
 // Stolen from jq! Which pro8a8ly stole the concept from other places. Nice.
 export const withEntries = (obj, fn) => Object.fromEntries(fn(Object.entries(obj)));
 
-// Nothin' more to it than what it says. Runs a function in-place. Provides an
-// altern8tive syntax to the usual IIFEs (e.g. (() => {})()) when you want to
-// open a scope and run some statements while inside an existing expression.
-export const call = fn => fn();
-
 export function queue(array, max = 50) {
     if (max === 0) {
         return array.map(fn => fn());