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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
// 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 findThisThingOnly = Symbol.for('Thing.findThisThingOnly');
static reverseSpecs = Symbol.for('Thing.reverseSpecs');
static yamlDocumentSpec = Symbol.for('Thing.yamlDocumentSpec');
static getYamlLoadingSpec = Symbol.for('Thing.getYamlLoadingSpec');
static yamlSourceFilename = Symbol.for('Thing.yamlSourceFilename');
static yamlSourceDocument = Symbol.for('Thing.yamlSourceDocument');
static yamlSourceDocumentPlacement = Symbol.for('Thing.yamlSourceDocumentPlacement');
[Symbol.for('Thing.yamlSourceFilename')] = null;
[Symbol.for('Thing.yamlSourceDocument')] = null;
[Symbol.for('Thing.yamlSourceDocumentPlacement')] = null;
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;
constructor() {
super({seal: false});
// To detect:
// Object.hasOwn(object, Symbol.for('Thing.isThing'))
this[Symbol.for('Thing.isThing')] = NaN;
Object.seal(this);
}
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 {
name = colors.yellow(`couldn't get name`);
}
let reference;
try {
if (this.directory) {
reference = colors.blue(Thing.getReference(this));
}
} catch {
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) {
if (thing.name) {
throw TypeError(
`Passed ${thing.constructor.name} (named ${inspect(thing.name)}) ` +
`is missing its directory`);
} else {
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 ?? [],
],
};
}
}
|