« 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/thing.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/data/thing.js')
-rw-r--r--src/data/thing.js77
1 files changed, 77 insertions, 0 deletions
diff --git a/src/data/thing.js b/src/data/thing.js
new file mode 100644
index 0000000..706e893
--- /dev/null
+++ b/src/data/thing.js
@@ -0,0 +1,77 @@
+// Thing: base class for wiki data types, providing interfaces generally useful
+// to all wiki data objects on top of foundational CacheableObject behavior.
+
+import {inspect} from 'node:util';
+
+import CacheableObject from '#cacheable-object';
+import {colors} from '#cli';
+
+export default class Thing extends CacheableObject {
+  static referenceType = Symbol.for('Thing.referenceType');
+  static friendlyName = Symbol.for('Thing.friendlyName');
+
+  static getPropertyDescriptors = Symbol.for('Thing.getPropertyDescriptors');
+  static getSerializeDescriptors = Symbol.for('Thing.getSerializeDescriptors');
+
+  static findSpecs = Symbol.for('Thing.findSpecs');
+  static yamlDocumentSpec = Symbol.for('Thing.yamlDocumentSpec');
+  static getYamlLoadingSpec = Symbol.for('Thing.getYamlLoadingSpec');
+
+  // 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
+  // identifying the Thing being presented.
+  [inspect.custom]() {
+    const cname = this.constructor.name;
+
+    return (
+      (this.name ? `${cname} ${colors.green(`"${this.name}"`)}` : `${cname}`) +
+      (this.directory ? ` (${colors.blue(Thing.getReference(this))})` : '')
+    );
+  }
+
+  static getReference(thing) {
+    if (!thing.constructor[Thing.referenceType]) {
+      throw TypeError(`Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]`);
+    }
+
+    if (!thing.directory) {
+      throw TypeError(`Passed ${thing.constructor.name} is missing its directory`);
+    }
+
+    return `${thing.constructor[Thing.referenceType]}:${thing.directory}`;
+  }
+
+  static extendDocumentSpec(thingClass, subspec) {
+    const superspec = thingClass[Thing.yamlDocumentSpec];
+
+    const {
+      fields,
+      ignoredFields,
+      invalidFieldCombinations,
+      ...restOfSubspec
+    } = subspec;
+
+    const newFields = Object.keys(fields ?? {});
+
+    return {
+      ...superspec,
+      ...restOfSubspec,
+
+      fields: {
+        ...superspec.fields ?? {},
+        ...fields,
+      },
+
+      ignoredFields:
+        (superspec.ignoredFields ?? [])
+          .filter(field => newFields.includes(field))
+          .concat(ignoredFields ?? []),
+
+      invalidFieldCombinations: [
+        ...superspec.invalidFieldCombinations ?? [],
+        ...invalidFieldCombinations ?? [],
+      ],
+    };
+  }
+}