« get me outta code hell

thing.js « data « src - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/thing.js
blob: 29f50d23e3a9a62f09380cb75ac6b2aedc88bbe9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 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');

  static isThingConstructor = Symbol.for('Thing.isThingConstructor');
  static isThing = Symbol.for('Thing.isThing');

  // To detect:
  // Symbol.for('Thing.isThingConstructor') in constructor
  static [Symbol.for('Thing.isThingConstructor')] = NaN;

  static [CacheableObject.propertyDescriptors] = {
    // To detect:
    // Object.hasOwn(object, Symbol.for('Thing.isThing'))
    [Symbol.for('Thing.isThing')]: {
      flags: {expose: true},
      expose: {compute: () => NaN},
    },
  };

  static [Symbol.for('Thing.selectAll')] = _wikiData => [];

  // 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 constructorName = this.constructor.name;

    let name;
    try {
      if (this.name) {
        name = colors.green(`"${this.name}"`);
      }
    } catch (error) {
      name = colors.yellow(`couldn't get name`);
    }

    let reference;
    try {
      if (this.directory) {
        reference = colors.blue(Thing.getReference(this));
      }
    } catch (error) {
      reference = colors.yellow(`couldn't get reference`);
    }

    return (
      (name ? `${constructorName} ${name}` : `${constructorName}`) +
      (reference ? ` (${reference})` : ''));
  }

  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 ?? [],
      ],
    };
  }
}