From 63b1b5b6fd14d3bacdcb979298b4fa669de4f20b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 7 May 2023 17:03:00 -0300 Subject: contracts: highly dysfunctional ContractManager Also some setup in generateAlbumTrackList in particular. None of this works yet! Probably replacing most everything in ContractManager, just putting this in a commit so it's logged. (All written a few days ago.) --- .../dependencies/generateAlbumSidebarGroupBox.js | 68 ++++++------ src/content/dependencies/generateAlbumTrackList.js | 103 ++++++++++++++++++ src/contract.js | 119 ++++++++++++++++++++- 3 files changed, 257 insertions(+), 33 deletions(-) diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js index 4e46c931..b3ee0abe 100644 --- a/src/content/dependencies/generateAlbumSidebarGroupBox.js +++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js @@ -5,45 +5,53 @@ export default { extraDependencies: ['html', 'language', 'transformMultiline'], contracts: { - relations(contract, [album, group]) { - contract.provide({ - group, album, + relations: { + hook(contract, [relation, album, group]) { + contract.provide({ + group, album, + + urls: contract.selectProperty(group, 'urls'), + adjacentAlbums: contract.subcontract('adjacentAlbumsInGroup', album, group), + }); + }, - urls: contract.selectProperty(group, 'urls'), - adjacentAlbums: contract.subcontract('adjacentAlbumsInGroup', album, group), - }); - }, - }, + compute({relation, group, album, urls, adjacentAlbums}) { + const relations = {}; - relations(relation, {group, album, urls, adjacentAlbums}) { - const relations = {}; + relations.groupLink = + relation('linkGroup', group); - relations.groupLink = - relation('linkGroup', group); + relations.externalLinks = + urls.map(url => + relation('linkExternal', urls)); - relations.externalLinks = - urls.map(url => - relation('linkExternal', urls)); + const {previousAlbum, nextAlbum} = adjacentAlbums; - const {previousAlbum, nextAlbum} = adjacentAlbums; + if (previousAlbum) { + relations.previousAlbumLink = + relation('linkAlbum', previousAlbum); + } - if (previousAlbum) { - relations.previousAlbumLink = - relation('linkAlbum', previousAlbum); - } + if (nextAlbum) { + relations.nextAlbumLink = + relation('linkAlbum', nextAlbum); + } - if (nextAlbum) { - relations.nextAlbumLink = - relation('linkAlbum', nextAlbum); - } + return relations; + }, + }, - return relations; - }, + data: { + hook(contract, [album, group]) { + contract.provide({ + description: contract.selectProperty(group, 'descriptionShort'), + }); + }, - data(album, group) { - return { - description: group.descriptionShort, - }; + compute({description}) { + return {description}; + }, + }, }, generate(data, relations, {html, language, transformMultiline}) { diff --git a/src/content/dependencies/generateAlbumTrackList.js b/src/content/dependencies/generateAlbumTrackList.js index f2f2279d..a0fad460 100644 --- a/src/content/dependencies/generateAlbumTrackList.js +++ b/src/content/dependencies/generateAlbumTrackList.js @@ -44,6 +44,109 @@ export default { 'language', ], + contracts: { + length: { + hook(contract, [array]) { + contract.provide({ + length: contract.selectProperty(array, 'length'), + }); + }, + + compute({length}) { + return length; + }, + }, + + isDefault: { + hook(contract, [trackSection]) { + contract.provide({ + isDefault: contract.selectProperty(trackSection, 'isDefaultTrackSection', false), + }); + }, + + compute({isDefault}) { + return isDefault; + }, + }, + + firstIsDefault: { + hook(contract, [trackSections]) { + contract.provide({ + isDefault: contract.subcontract('#isDefault', contract.selectProperty(trackSections, '0')), + }); + }, + + compute({isDefault}) { + return isDefault; + }, + }, + + displayTrackSections: { + hook(contract, [album]) { + contract.provide({ + numTrackSections: contract.subcontract('#length', contract.selectProperty(album, 'trackSections')), + firstIsDefault: contract.subcontract('#firstIsDefault', contract.selectProperty(album, 'trackSections')), + }); + }, + + compute({numTrackSections, firstIsDefault}) { + return numTrackSections >= 2 || firstIsDefault; + }, + }, + + displayTracks: { + hook(contract, [album]) { + contract.provide({ + numTracks: contract.subcontract('#length', contract.selectProperty(album, 'tracks')), + }); + }, + + compute({numTracks}) { + return numTracks >= 1; + }, + }, + + displayMode: { + hook(contract, [album]) { + contract.provide({ + displayTrackSections: contract.subcontract('#displayTrackSections', album), + displayTracks: contract.subcontract('#displayTracks', album), + }); + }, + + compute({displayTrackSections, displayTracks}) { + if (displayTrackSections) { + return 'trackSections'; + } else if (displayTracks) { + return 'tracks'; + } else { + return 'none'; + } + }, + }, + + relations: { + hook(contract, [relation, album]) { + contract.branch({ + subcontract: ['#displayMode', album], + branches: { + trackSections() { + contract.provide({ + trackSections: contract.selectProperty(album, 'trackSections'), + }); + }, + + tracks() { + contract.provide({ + tracks: contract.selectProperty(album, 'tracks'), + }); + }, + }, + }); + }, + }, + }, + relations(relation, album) { const relations = {}; diff --git a/src/contract.js b/src/contract.js index 24a2fd53..737f1bbd 100644 --- a/src/contract.js +++ b/src/contract.js @@ -1,5 +1,118 @@ -export default class Contract { - #caches = {}; +export class ContractManager { + #registeredContracts = Object.create(null); - hire() {}, + registerContract(name, description) { + ContractManager.validateContractDescription(description); + this.#registeredContracts[name] = description; + } + + getContractHooks(name) { + return this.getContractInfo(name).hooks; + } + + getContractInfo(name) { + // todo: cache + return this.#computeContractInfo(name); + } + + #computeContractInfo(name) { + const description = this.#registeredContracts[name]; + if (!description) { + throw new Error(`Contract ${name} not registered`); + } + + let numArguments = 0; + const args = []; + const argumentGenerator = (function*() { + while (true) { + const argument = {type: 'argument', index: numArguments++, of: name} + args.push(argument); + yield argument; + } + })(); + + const hooks = []; + const subcontracts = {}; + const structure = {}; + + const contextualizeHook = (args, {type, ...hook}) => { + switch (type) { + case 'argument': + return {type: 'argument', index: hook.index}; + case 'selectPropertyPath': { + /* + switch (hook.object.type) { + case 'argument': + console.log('select argument', hook.object.index, '=', args[hook.object.index]); + return {type: 'selectPropertyPath', object: args[hook.object.index], path: hook.path}; + case 'selectPropertyPath': + console.log('merge', hook.object.path, 'with', hook.path); + return {type: 'selectPropertyPath', object: args[hook.object.object.index], path: [...hook.object.path, ...hook.path]}; + default: + throw new Error(`Can't contextualize unknown hook type OF OBJECT ${hook.object.type}`); + } + */ + const contextualizedObject = contextualizeHook(args, hook.object); + console.log(`contextualized property object:`, contextualizedObject); + switch (contextualizedObject.type) { + case 'argument': + return {type: 'selectPropertyPath', object: args[contextualizedObject.index], path: hook.path}; + case 'selectPropertyPath': + return {type: 'selectPropertyPath', object: contextualizedObject.object, path: [...contextualizedObject.path, ...hook.path]}; + } + } + default: + throw new Error(`Can't contextualize unknown hook type ${type}`); + } + }; + + const contractUtility = { + subcontract: (name, ...args) => { + const info = this.getContractInfo(name.startsWith('#') ? name.slice(1) : name); + + for (const hook of info.hooks) { + hooks.push(contextualizeHook(args, hook)); + } + + return {type: 'subcontract', name, args}; + }, + + provide: (properties) => { + Object.assign(structure, properties); + }, + + selectProperty: (object, property) => { + hooks.push(contextualizeHook(args, {type: 'selectPropertyPath', object, path: [property]})); + return {type: 'selectPropertyPath', object, path: [property]}; + }, + }; + + description.hook(contractUtility, argumentGenerator); + + return {hooks, subcontracts, structure}; + } + + static validateContractDescription(description) { + // todo + } +} + +const {default: {contracts}} = await import('./content/dependencies/generateAlbumTrackList.js'); +const util = await import('util'); + +const manager = new ContractManager(); +for (const [name, description] of Object.entries(contracts)) { + manager.registerContract(name, description); } + +const testContract = 'displayTrackSections'; + +for (const hook of manager.getContractHooks(testContract)) { + if (hook.type === 'selectPropertyPath') { + console.log(`- (${util.inspect(hook.object, {colors: true})}).${hook.path.join('.')}`); + } else { + console.log(`- ${hook}`); + } +} + +// console.log(util.inspect(manager.getContractInfo(testContract).structure, {colors: true, depth: Infinity})); -- cgit 1.3.0-6-gf8a5