« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util/html.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/html.js')
-rw-r--r--src/util/html.js139
1 files changed, 86 insertions, 53 deletions
diff --git a/src/util/html.js b/src/util/html.js
index b5930d06..b75820e8 100644
--- a/src/util/html.js
+++ b/src/util/html.js
@@ -489,7 +489,10 @@ export class Template {
   #slotValues = {};
 
   constructor(description) {
-    Template.validateDescription(description);
+    if (!description[Stationery.validated]) {
+      Template.validateDescription(description);
+    }
+
     this.#description = description;
   }
 
@@ -528,69 +531,79 @@ export class Template {
         break validateSlots;
       }
 
-      const slotErrors = [];
+      try {
+        this.validateSlotsDescription(description.slots);
+      } catch (slotError) {
+        topErrors.push(slotError);
+      }
+    }
 
-      for (const [slotName, slotDescription] of Object.entries(description.slots)) {
-        if (typeof slotDescription !== 'object' || slotDescription === null) {
-          slotErrors.push(new TypeError(`(${slotName}) Expected slot description to be object`));
-          continue;
-        }
+    if (!empty(topErrors)) {
+      throw new AggregateError(topErrors,
+        (typeof description.annotation === 'string'
+          ? `Errors validating template "${description.annotation}" description`
+          : `Errors validating template description`));
+    }
 
-        if ('default' in slotDescription) validateDefault: {
-          if (
-            slotDescription.default === undefined ||
-            slotDescription.default === null
-          ) {
-            slotErrors.push(new TypeError(`(${slotName}) Leave slot default unspecified instead of undefined or null`));
-            break validateDefault;
-          }
+    return true;
+  }
 
-          try {
-            Template.validateSlotValueAgainstDescription(slotDescription.default, slotDescription);
-          } catch (error) {
-            error.message = `(${slotName}) Error validating slot default value: ${error.message}`;
-            slotErrors.push(error);
-          }
+  static validateSlotsDescription(slots) {
+    const slotErrors = [];
+
+    for (const [slotName, slotDescription] of Object.entries(slots)) {
+      if (typeof slotDescription !== 'object' || slotDescription === null) {
+        slotErrors.push(new TypeError(`(${slotName}) Expected slot description to be object`));
+        continue;
+      }
+
+      if ('default' in slotDescription) validateDefault: {
+        if (
+          slotDescription.default === undefined ||
+          slotDescription.default === null
+        ) {
+          slotErrors.push(new TypeError(`(${slotName}) Leave slot default unspecified instead of undefined or null`));
+          break validateDefault;
         }
 
-        if ('validate' in slotDescription && 'type' in slotDescription) {
-          slotErrors.push(new TypeError(`(${slotName}) Don't specify both slot validate and type`));
-        } else if (!('validate' in slotDescription || 'type' in slotDescription)) {
-          slotErrors.push(new TypeError(`(${slotName}) Expected either slot validate or type`));
-        } else if ('validate' in slotDescription) {
-          if (typeof slotDescription.validate !== 'function') {
-            slotErrors.push(new TypeError(`(${slotName}) Expected slot validate to be function`));
-          }
-        } else if ('type' in slotDescription) {
-          const acceptableSlotTypes = [
-            'string',
-            'number',
-            'bigint',
-            'boolean',
-            'symbol',
-            'html',
-          ];
-
-          if (slotDescription.type === 'function') {
-            slotErrors.push(new TypeError(`(${slotName}) Functions shouldn't be provided to slots`));
-          } else if (slotDescription.type === 'object') {
-            slotErrors.push(new TypeError(`(${slotName}) Provide validate function instead of type: object`));
-          } else if (!acceptableSlotTypes.includes(slotDescription.type)) {
-            slotErrors.push(new TypeError(`(${slotName}) Expected slot type to be one of ${acceptableSlotTypes.join(', ')}`));
-          }
+        try {
+          Template.validateSlotValueAgainstDescription(slotDescription.default, slotDescription);
+        } catch (error) {
+          error.message = `(${slotName}) Error validating slot default value: ${error.message}`;
+          slotErrors.push(error);
         }
       }
 
-      if (!empty(slotErrors)) {
-        topErrors.push(new AggregateError(slotErrors, `Errors in slot descriptions`));
+      if ('validate' in slotDescription && 'type' in slotDescription) {
+        slotErrors.push(new TypeError(`(${slotName}) Don't specify both slot validate and type`));
+      } else if (!('validate' in slotDescription || 'type' in slotDescription)) {
+        slotErrors.push(new TypeError(`(${slotName}) Expected either slot validate or type`));
+      } else if ('validate' in slotDescription) {
+        if (typeof slotDescription.validate !== 'function') {
+          slotErrors.push(new TypeError(`(${slotName}) Expected slot validate to be function`));
+        }
+      } else if ('type' in slotDescription) {
+        const acceptableSlotTypes = [
+          'string',
+          'number',
+          'bigint',
+          'boolean',
+          'symbol',
+          'html',
+        ];
+
+        if (slotDescription.type === 'function') {
+          slotErrors.push(new TypeError(`(${slotName}) Functions shouldn't be provided to slots`));
+        } else if (slotDescription.type === 'object') {
+          slotErrors.push(new TypeError(`(${slotName}) Provide validate function instead of type: object`));
+        } else if (!acceptableSlotTypes.includes(slotDescription.type)) {
+          slotErrors.push(new TypeError(`(${slotName}) Expected slot type to be one of ${acceptableSlotTypes.join(', ')}`));
+        }
       }
     }
 
-    if (!empty(topErrors)) {
-      throw new AggregateError(topErrors,
-        (typeof description.annotation === 'string'
-          ? `Errors validating template "${description.annotation}" description`
-          : `Errors validating template description`));
+    if (!empty(slotErrors)) {
+      throw new AggregateError(slotErrors, `Errors in slot descriptions`);
     }
 
     return true;
@@ -769,3 +782,23 @@ export class Template {
     return this.content.toString();
   }
 }
+
+export function stationery(description) {
+  return new Stationery(description);
+}
+
+export class Stationery {
+  #templateDescription = null;
+
+  static validated = Symbol('Stationery.validated');
+
+  constructor(templateDescription) {
+    Template.validateDescription(templateDescription);
+    templateDescription[Stationery.validated] = true;
+    this.#templateDescription = templateDescription;
+  }
+
+  template() {
+    return new Template(this.#templateDescription);
+  }
+}