« get me outta code hell

html, infra: html.inside(), html.findInside() - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2025-10-23 18:13:05 -0300
committer(quasar) nebula <qznebula@protonmail.com>2025-10-23 18:19:16 -0300
commit91214d3e8482e3128f1c7a2d6da240ef0413a59d (patch)
tree05f904e761ec42e9b40f3068a563f9f0d75bc8c3 /src
parentf41f72314b32fd62d940631a310b573d41fd346f (diff)
html, infra: html.inside(), html.findInside()
Ooooo
Diffstat (limited to 'src')
-rw-r--r--src/content-function.js16
-rw-r--r--src/html.js126
2 files changed, 137 insertions, 5 deletions
diff --git a/src/content-function.js b/src/content-function.js
index e141a686..04f2ce90 100644
--- a/src/content-function.js
+++ b/src/content-function.js
@@ -2,7 +2,7 @@ import {inspect as nodeInspect} from 'node:util';
 
 import {decorateError} from '#aggregate';
 import {colors, decorateTime, ENABLE_COLOR} from '#cli';
-import {Template} from '#html';
+import {Tag, Template} from '#html';
 import {empty} from '#sugar';
 
 function inspect(value, opts = {}) {
@@ -103,6 +103,20 @@ function prepareWorkingGenerateFunction(spec, boundExtraDependencies) {
     }
   };
 
+  generate = (baseGenerate => (...args) => {
+    const result = baseGenerate(...args);
+
+    if (result instanceof Template || result instanceof Tag) {
+      if (Object.hasOwn(result, Symbol.for('hsmusic.content.via'))) {
+        result[Symbol.for('hsmusic.contentFunction.via')].push(dependency);
+      } else {
+        result[Symbol.for('hsmusic.contentFunction.via')] = [dependency];
+      }
+    }
+
+    return result;
+  })(generate);
+
   generate = optionalDecorateTime(`generate`, dependency, generate);
 
   if (spec.slots) {
diff --git a/src/html.js b/src/html.js
index 444edd6a..4cac9525 100644
--- a/src/html.js
+++ b/src/html.js
@@ -1698,6 +1698,61 @@ export function smooth(smoothie) {
   return tags(helper(smoothie));
 }
 
+export function inside(insidee) {
+  if (insidee instanceof Template) {
+    return inside(Template.resolve(insidee));
+  }
+
+  if (insidee instanceof Tag) {
+    return Array.from(smooth(tags(insidee.content)).content);
+  }
+
+  return [];
+}
+
+export function findInside(insidee, query) {
+  if (typeof query === 'object' && query.slots) {
+    return findInside(insidee, item =>
+      Template.resolveForSlots(item, query.slots, 'null'));
+  }
+
+  if (typeof query === 'object' && query.annotation) {
+    return findInside(insidee, item =>
+      Template.resolveForAnnotation(item, query.annotation, 'null'));
+  }
+
+  if (typeof query === 'object' && query.tag) {
+    return findInside(insidee, item => {
+      const tag = normalize(item);
+      if (tag.tagName === query) {
+        return tag;
+      } else {
+        return null;
+      }
+    });
+  }
+
+  if (typeof query === 'string') {
+    return findInside(insidee, item =>
+      Template.resolveForContentFunction(item, query, 'null'));
+  }
+
+  if (typeof query !== 'function') {
+    throw new Error(`Expected {slots}, {annotation}, or query function`);
+  }
+
+  for (const item of inside(insidee)) {
+    const result = query(item);
+    if (result && result === true) {
+      return item;
+    } else if (result) {
+      return result;
+    }
+  }
+
+  return null;
+}
+
 export function template(description) {
   return new Template(description);
 }
@@ -2109,7 +2164,7 @@ export class Template {
     return content;
   }
 
-  static resolveForSlots(content, slots) {
+  static resolveForSlots(content, slots, without = 'throw') {
     if (!slots || typeof slots !== 'object') {
       throw new Error(
         `Expected slots to be an object or array, ` +
@@ -2132,9 +2187,72 @@ export class Template {
       }
     }
 
-    throw new Error(
-      `Didn't find slots ${inspect(slots, {compact: true})} ` +
-      `resolving ${inspect(content, {compact: true})}`);
+    if (without === 'throw') {
+      throw new Error(
+        `Didn't find slots ${inspect(slots, {compact: true})} ` +
+        `resolving ${inspect(content, {compact: true})}`);
+    } else {
+      return null;
+    }
+  }
+
+  static resolveForAnnotation(content, annotation, without = 'throw') {
+    if (!annotation || typeof annotation !== 'string') {
+      throw new Error(
+        `Expected annotation to be a string, ` +
+        `got ${typeAppearance(annotation)}`);
+    }
+
+    while (content instanceof Template) {
+      if (content.description.annotation === annotation) {
+        return content;
+      } else {
+        content = content.content;
+      }
+    }
+
+    if (without === 'throw') {
+      throw new Error(
+        `Didn't find annotation ${inspect(annotation, {compact: true})} ` +
+        `resolving ${inspect(content, {compact: true})}`);
+    } else {
+      return null;
+    }
+  }
+
+  static resolveForContentFunction(content, dependency, without = 'throw') {
+    if (!dependency || typeof dependency !== 'string') {
+      throw new Error(
+        `Expected dependency to be a string, ` +
+        `got ${typeAppearance(dependency)}`);
+    }
+
+    const considerContentFunction = () =>
+      (content instanceof Tag || content instanceof Template) &&
+      Object.hasOwn(content, Symbol.for('hsmusic.contentFunction.via')) &&
+      content[Symbol.for('hsmusic.contentFunction.via')].includes(dependency);
+
+    while (content instanceof Template) {
+      if (considerContentFunction()) {
+        return content;
+      } else if (content.description.annotation === dependency) {
+        return content;
+      } else {
+        content = content.content;
+      }
+    }
+
+    if (considerContentFunction()) {
+      return content;
+    }
+
+    if (without === 'throw') {
+      throw new Error(
+        `Didn't find dependency ${inspect(dependency, {compact: true})} ` +
+        `resolving ${inspect(content, {compact: true})}`);
+    } else {
+      return null;
+    }
   }
 
   [inspect.custom]() {