diff options
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/data/things/index.js | 295 | ||||
| -rw-r--r-- | src/data/things/init.js | 208 |
3 files changed, 229 insertions, 276 deletions
diff --git a/package.json b/package.json index e095ff03..a4154040 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "#sugar": "./src/common-util/sugar.js", "#test-lib": "./test/lib/index.js", "#thing": "./src/data/thing.js", - "#things": "./src/data/things/index.js", + "#things": "./src/data/things/init.js", "#thumbs": "./src/gen-thumbs.js", "#urls": "./src/urls.js", "#validators": "./src/validators.js", 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; |