« 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/things
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/things')
-rw-r--r--src/data/things/index.js295
-rw-r--r--src/data/things/init.js208
2 files changed, 228 insertions, 275 deletions
diff --git a/src/data/things/index.js b/src/data/things/index.js
index 35cd8cf2..bf3df9a7 100644
--- a/src/data/things/index.js
+++ b/src/data/things/index.js
@@ -1,275 +1,20 @@
-import * as path from 'node:path';
-import {fileURLToPath} from 'node:url';
-
-import {openAggregate, showAggregate} from '#aggregate';
-import CacheableObject from '#cacheable-object';
-import {logError} from '#cli';
-import {compositeFrom} from '#composite';
-import * as serialize from '#serialize';
-import {empty} from '#sugar';
-import Thing from '#thing';
-
-import * as additionalFileClasses from './additional-file.js';
-import * as additionalNameClasses from './additional-name.js';
-import * as albumClasses from './album.js';
-import * as artTagClasses from './art-tag.js';
-import * as artistClasses from './artist.js';
-import * as artworkClasses from './artwork.js';
-import * as contentClasses from './content.js';
-import * as contributionClasses from './contribution.js';
-import * as flashClasses from './flash.js';
-import * as groupClasses from './group.js';
-import * as homepageLayoutClasses from './homepage-layout.js';
-import * as languageClasses from './language.js';
-import * as musicVideoClasses from './music-video.js';
-import * as newsEntryClasses from './news-entry.js';
-import * as sortingRuleClasses from './sorting-rule.js';
-import * as staticPageClasses from './static-page.js';
-import * as trackClasses from './track.js';
-import * as wikiInfoClasses from './wiki-info.js';
-
-const allClassLists = {
-  'additional-file.js': additionalFileClasses,
-  'additional-name.js': additionalNameClasses,
-  'album.js': albumClasses,
-  'art-tag.js': artTagClasses,
-  'artist.js': artistClasses,
-  'artwork.js': artworkClasses,
-  'content.js': contentClasses,
-  'contribution.js': contributionClasses,
-  'flash.js': flashClasses,
-  'group.js': groupClasses,
-  'homepage-layout.js': homepageLayoutClasses,
-  'language.js': languageClasses,
-  'music-video.js': musicVideoClasses,
-  'news-entry.js': newsEntryClasses,
-  'sorting-rule.js': sortingRuleClasses,
-  'static-page.js': staticPageClasses,
-  'track.js': trackClasses,
-  'wiki-info.js': wikiInfoClasses,
-};
-
-let allClasses = Object.create(null);
-
-// src/data/things/index.js -> src/
-const __dirname = path.dirname(
-  path.resolve(
-    fileURLToPath(import.meta.url),
-    '../..'));
-
-function niceShowAggregate(error, ...opts) {
-  showAggregate(error, {
-    pathToFileURL: (f) => path.relative(__dirname, fileURLToPath(f)),
-    showClasses: false,
-    ...opts,
-  });
-}
-
-function errorDuplicateClassNames() {
-  const locationDict = Object.create(null);
-
-  for (const [location, classes] of Object.entries(allClassLists)) {
-    for (const className of Object.keys(classes)) {
-      if (className in locationDict) {
-        locationDict[className].push(location);
-      } else {
-        locationDict[className] = [location];
-      }
-    }
-  }
-
-  let success = true;
-
-  for (const [className, locations] of Object.entries(locationDict)) {
-    if (locations.length === 1) {
-      continue;
-    }
-
-    logError`Thing class name ${`"${className}"`} is defined more than once: ${locations.join(', ')}`;
-    success = false;
-  }
-
-  return success;
-}
-
-function flattenClassLists() {
-  let remaining = [];
-  for (const classes of Object.values(allClassLists)) {
-    for (const constructor of Object.values(classes)) {
-      if (typeof constructor !== 'function') continue;
-      if (!(constructor.prototype instanceof Thing)) continue;
-      remaining.push(constructor);
-    }
-  }
-
-  let sorted = [];
-  while (true) {
-    if (sorted[0]) {
-      const superclass = Object.getPrototypeOf(sorted[0]);
-      if (superclass !== Thing) {
-        if (sorted.includes(superclass)) {
-          sorted.unshift(...sorted.splice(sorted.indexOf(superclass), 1));
-        } else {
-          sorted.unshift(superclass);
-        }
-        continue;
-      }
-    }
-
-    if (!empty(remaining)) {
-      sorted.unshift(remaining.shift());
-    } else {
-      break;
-    }
-  }
-
-  for (const constructor of sorted) {
-    allClasses[constructor.name] = constructor;
-  }
-}
-
-function descriptorAggregateHelper({
-  showFailedClasses,
-  message,
-  op,
-}) {
-  const failureSymbol = Symbol();
-  const aggregate = openAggregate({
-    message,
-    returnOnFail: failureSymbol,
-  });
-
-  const failedClasses = [];
-
-  for (const [name, constructor] of Object.entries(allClasses)) {
-    const result = aggregate.call(op, constructor);
-
-    if (result === failureSymbol) {
-      failedClasses.push(name);
-    }
-  }
-
-  try {
-    aggregate.close();
-    return true;
-  } catch (error) {
-    niceShowAggregate(error);
-    showFailedClasses(failedClasses);
-
-    /*
-    if (error.errors) {
-      for (const sub of error.errors) {
-        console.error(sub);
-      }
-    }
-    */
-
-    return false;
-  }
-}
-
-function evaluatePropertyDescriptors() {
-  const opts = {...allClasses};
-
-  return descriptorAggregateHelper({
-    message: `Errors evaluating Thing class property descriptors`,
-
-    op(constructor) {
-      if (!constructor[Thing.getPropertyDescriptors]) {
-        throw new Error(`Missing [Thing.getPropertyDescriptors] function`);
-      }
-
-      const results = constructor[Thing.getPropertyDescriptors](opts);
-
-      for (const [key, value] of Object.entries(results)) {
-        if (Array.isArray(value)) {
-          results[key] = compositeFrom({
-            annotation: `${constructor.name}.${key}`,
-            compose: false,
-            steps: value,
-          });
-        } else if (value.toResolvedComposition) {
-          results[key] = compositeFrom(value.toResolvedComposition());
-        }
-      }
-
-      constructor[CacheableObject.propertyDescriptors] =
-        Object.create(constructor[CacheableObject.propertyDescriptors] ?? null);
-
-      Object.assign(constructor[CacheableObject.propertyDescriptors], results);
-    },
-
-    showFailedClasses(failedClasses) {
-      logError`Failed to evaluate property descriptors for classes: ${failedClasses.join(', ')}`;
-    },
-  });
-}
-
-function evaluateSerializeDescriptors() {
-  const opts = {...allClasses, serialize};
-
-  return descriptorAggregateHelper({
-    message: `Errors evaluating Thing class serialize descriptors`,
-
-    op(constructor) {
-      if (!constructor[Thing.getSerializeDescriptors]) {
-        return;
-      }
-
-      constructor[serialize.serializeDescriptors] =
-        constructor[Thing.getSerializeDescriptors](opts);
-    },
-
-    showFailedClasses(failedClasses) {
-      logError`Failed to evaluate serialize descriptors for classes: ${failedClasses.join(', ')}`;
-    },
-  });
-}
-
-function finalizeYamlDocumentSpecs() {
-  return descriptorAggregateHelper({
-    message: `Errors finalizing Thing YAML document specs`,
-
-    op(constructor) {
-      const superclass = Object.getPrototypeOf(constructor);
-      if (
-        constructor[Thing.yamlDocumentSpec] &&
-        superclass[Thing.yamlDocumentSpec]
-      ) {
-        constructor[Thing.yamlDocumentSpec] =
-          Thing.extendDocumentSpec(superclass, constructor[Thing.yamlDocumentSpec]);
-      }
-    },
-
-    showFailedClasses(failedClasses) {
-      logError`Failed to finalize YAML document specs for classes: ${failedClasses.join(', ')}`;
-    },
-  });
-}
-
-function finalizeCacheableObjectPrototypes() {
-  return descriptorAggregateHelper({
-    message: `Errors finalizing Thing class prototypes`,
-
-    op(constructor) {
-      constructor.finalizeCacheableObjectPrototype();
-    },
-
-    showFailedClasses(failedClasses) {
-      logError`Failed to finalize cacheable object prototypes for classes: ${failedClasses.join(', ')}`;
-    },
-  });
-}
-
-if (!errorDuplicateClassNames()) process.exit(1);
-
-flattenClassLists();
-
-if (!evaluatePropertyDescriptors()) process.exit(1);
-if (!evaluateSerializeDescriptors()) process.exit(1);
-if (!finalizeYamlDocumentSpecs()) process.exit(1);
-if (!finalizeCacheableObjectPrototypes()) process.exit(1);
-
-Object.assign(allClasses, {Thing});
-
-export default allClasses;
+// Not actually the entry point for #things - that's init.js in this folder.
+
+export * from './additional-file.js';
+export * from './additional-name.js';
+export * from './album.js';
+export * from './art-tag.js';
+export * from './artist.js';
+export * from './artwork.js';
+export * from './content.js';
+export * from './contribution.js';
+export * from './flash.js';
+export * from './group.js';
+export * from './homepage-layout.js';
+export * from './language.js';
+export * from './music-video.js';
+export * from './news-entry.js';
+export * from './sorting-rule.js';
+export * from './static-page.js';
+export * from './track.js';
+export * from './wiki-info.js';
diff --git a/src/data/things/init.js b/src/data/things/init.js
new file mode 100644
index 00000000..e705f626
--- /dev/null
+++ b/src/data/things/init.js
@@ -0,0 +1,208 @@
+// This is the actual entry point for #things.
+
+import * as path from 'node:path';
+import {fileURLToPath} from 'node:url';
+
+import {openAggregate, showAggregate} from '#aggregate';
+import CacheableObject from '#cacheable-object';
+import {logError} from '#cli';
+import {compositeFrom} from '#composite';
+import * as serialize from '#serialize';
+import {empty} from '#sugar';
+import Thing from '#thing';
+
+import * as indexExports from './index.js';
+
+const thingConstructors = Object.create(null);
+
+// src/data/things/index.js -> src/
+const __dirname = path.dirname(
+  path.resolve(
+    fileURLToPath(import.meta.url),
+    '../..'));
+
+function niceShowAggregate(error, ...opts) {
+  showAggregate(error, {
+    pathToFileURL: (f) => path.relative(__dirname, fileURLToPath(f)),
+    showClasses: false,
+    ...opts,
+  });
+}
+
+function sortThingConstructors() {
+  let remaining = [];
+  for (const constructor of Object.values(indexExports)) {
+    if (typeof constructor !== 'function') continue;
+    if (!(constructor.prototype instanceof Thing)) continue;
+    remaining.push(constructor);
+  }
+
+  let sorted = [];
+  while (true) {
+    if (sorted[0]) {
+      const superclass = Object.getPrototypeOf(sorted[0]);
+      if (superclass !== Thing) {
+        if (sorted.includes(superclass)) {
+          sorted.unshift(...sorted.splice(sorted.indexOf(superclass), 1));
+        } else {
+          sorted.unshift(superclass);
+        }
+        continue;
+      }
+    }
+
+    if (!empty(remaining)) {
+      sorted.unshift(remaining.shift());
+    } else {
+      break;
+    }
+  }
+
+  for (const constructor of sorted) {
+    thingConstructors[constructor.name] = constructor;
+  }
+}
+
+function descriptorAggregateHelper({
+  showFailedClasses,
+  message,
+  op,
+}) {
+  const failureSymbol = Symbol();
+  const aggregate = openAggregate({
+    message,
+    returnOnFail: failureSymbol,
+  });
+
+  const failedClasses = [];
+
+  for (const [name, constructor] of Object.entries(thingConstructors)) {
+    const result = aggregate.call(op, constructor);
+
+    if (result === failureSymbol) {
+      failedClasses.push(name);
+    }
+  }
+
+  try {
+    aggregate.close();
+    return true;
+  } catch (error) {
+    niceShowAggregate(error);
+    showFailedClasses(failedClasses);
+
+    /*
+    if (error.errors) {
+      for (const sub of error.errors) {
+        console.error(sub);
+      }
+    }
+    */
+
+    return false;
+  }
+}
+
+function evaluatePropertyDescriptors() {
+  const opts = {...thingConstructors};
+
+  return descriptorAggregateHelper({
+    message: `Errors evaluating Thing class property descriptors`,
+
+    op(constructor) {
+      if (!constructor[Thing.getPropertyDescriptors]) {
+        throw new Error(`Missing [Thing.getPropertyDescriptors] function`);
+      }
+
+      const results = constructor[Thing.getPropertyDescriptors](opts);
+
+      for (const [key, value] of Object.entries(results)) {
+        if (Array.isArray(value)) {
+          results[key] = compositeFrom({
+            annotation: `${constructor.name}.${key}`,
+            compose: false,
+            steps: value,
+          });
+        } else if (value.toResolvedComposition) {
+          results[key] = compositeFrom(value.toResolvedComposition());
+        }
+      }
+
+      constructor[CacheableObject.propertyDescriptors] =
+        Object.create(constructor[CacheableObject.propertyDescriptors] ?? null);
+
+      Object.assign(constructor[CacheableObject.propertyDescriptors], results);
+    },
+
+    showFailedClasses(failedClasses) {
+      logError`Failed to evaluate property descriptors for classes: ${failedClasses.join(', ')}`;
+    },
+  });
+}
+
+function evaluateSerializeDescriptors() {
+  const opts = {...thingConstructors, serialize};
+
+  return descriptorAggregateHelper({
+    message: `Errors evaluating Thing class serialize descriptors`,
+
+    op(constructor) {
+      if (!constructor[Thing.getSerializeDescriptors]) {
+        return;
+      }
+
+      constructor[serialize.serializeDescriptors] =
+        constructor[Thing.getSerializeDescriptors](opts);
+    },
+
+    showFailedClasses(failedClasses) {
+      logError`Failed to evaluate serialize descriptors for classes: ${failedClasses.join(', ')}`;
+    },
+  });
+}
+
+function finalizeYamlDocumentSpecs() {
+  return descriptorAggregateHelper({
+    message: `Errors finalizing Thing YAML document specs`,
+
+    op(constructor) {
+      const superclass = Object.getPrototypeOf(constructor);
+      if (
+        constructor[Thing.yamlDocumentSpec] &&
+        superclass[Thing.yamlDocumentSpec]
+      ) {
+        constructor[Thing.yamlDocumentSpec] =
+          Thing.extendDocumentSpec(superclass, constructor[Thing.yamlDocumentSpec]);
+      }
+    },
+
+    showFailedClasses(failedClasses) {
+      logError`Failed to finalize YAML document specs for classes: ${failedClasses.join(', ')}`;
+    },
+  });
+}
+
+function finalizeCacheableObjectPrototypes() {
+  return descriptorAggregateHelper({
+    message: `Errors finalizing Thing class prototypes`,
+
+    op(constructor) {
+      constructor.finalizeCacheableObjectPrototype();
+    },
+
+    showFailedClasses(failedClasses) {
+      logError`Failed to finalize cacheable object prototypes for classes: ${failedClasses.join(', ')}`;
+    },
+  });
+}
+
+sortThingConstructors();
+
+if (!evaluatePropertyDescriptors()) process.exit(1);
+if (!evaluateSerializeDescriptors()) process.exit(1);
+if (!finalizeYamlDocumentSpecs()) process.exit(1);
+if (!finalizeCacheableObjectPrototypes()) process.exit(1);
+
+Object.assign(thingConstructors, {Thing});
+
+export default thingConstructors;