« get me outta code hell

contracts: highly dysfunctional ContractManager - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-05-07 17:03:00 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-05-07 17:03:00 -0300
commit63b1b5b6fd14d3bacdcb979298b4fa669de4f20b (patch)
tree224ee910247a3b2ca3336e97c52101daf36866bb
parent4280c6240b88dadc8e5ea187b78c10aca9dfc163 (diff)
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.)
-rw-r--r--src/content/dependencies/generateAlbumSidebarGroupBox.js68
-rw-r--r--src/content/dependencies/generateAlbumTrackList.js103
-rw-r--r--src/contract.js119
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}));