From 6dedd8ebbc8a25bcd34669201853861d836d12e6 Mon Sep 17 00:00:00 2001
From: Patrick Kollitsch <83281+davidsneighbour@users.noreply.github.com>
Date: Sun, 17 May 2026 08:08:31 +0000
Subject: [PATCH] feat: theme hooks and filters (#964)

---
 layouts/_partials/hook.html                       |    5 +
 config/_default/params.toml                       |    3 
 layouts/_partials/filter.html                     |  115 ++++++++++++++++++++++++++++
 layouts/_partials/hooks/article/test.html         |    1 
 layouts/_partials/func/debug-cli.html             |   87 +++++++++++++++++++++
 layouts/_partials/func/hooks/collector.html       |    1 
 layouts/_partials/func/hooks/collector-dump.html  |    5 +
 layouts/_partials/hooks/article/section-link.html |    3 
 layouts/baseof.html                               |    3 
 layouts/single.html                               |    8 -
 .vscode/custom-dictionary.txt                     |    4 
 11 files changed, 227 insertions(+), 8 deletions(-)

diff --git a/.vscode/custom-dictionary.txt b/.vscode/custom-dictionary.txt
index d049214..989bcbc 100644
--- a/.vscode/custom-dictionary.txt
+++ b/.vscode/custom-dictionary.txt
@@ -1,6 +1,8 @@
 Ananke
 demosite
 Disqus
+Errorf
 Kitchensink
 licenselink
-Philibert
\ No newline at end of file
+Philibert
+Warnf
diff --git a/config/_default/params.toml b/config/_default/params.toml
index 5b68e47..612f05f 100644
--- a/config/_default/params.toml
+++ b/config/_default/params.toml
@@ -255,3 +255,6 @@
 color = "#cd201f"
 profile = "https://www.youtube.com/@%s"
 icon = "youtube" # font awesome brand icon name
+
+[ananke.hooks]
+verbosity = "error" # debug, info, warning, error
\ No newline at end of file
diff --git a/layouts/_partials/filter.html b/layouts/_partials/filter.html
new file mode 100644
index 0000000..31df5cb
--- /dev/null
+++ b/layouts/_partials/filter.html
@@ -0,0 +1,115 @@
+{{- $input := . -}}
+{{- $context := collections.Dictionary -}}
+{{- $config := site.Params.ananke.hooks | compare.Default (collections.Dictionary) -}}
+{{- $disableMessages := $config.disable_messages | compare.Default (collections.Slice) -}}
+
+{{- $colourHook := "\033[38;2;170;153;255m" -}}
+{{- $colourUnused := "\033[38;2;204;119;102m" -}}
+{{- $colourReset := "\033[0m" -}}
+{{- $hookSlug := "FILTER" -}}
+
+{{- if reflect.IsMap . -}}
+  {{- with collections.Index . "__ananke_hook_slug" -}}
+    {{- $hookSlug = . -}}
+  {{- end -}}
+
+  {{- with collections.Index . "__ananke_hook_input" -}}
+    {{- $input = . -}}
+  {{- end -}}
+{{- end -}}
+
+{{- if reflect.IsMap $input -}}
+  {{- $context = collections.Merge $context $input -}}
+  {{- $context = collections.Merge $context (collections.Dictionary "type" "full") -}}
+
+  {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+      "message" (fmt.Printf "%s>>> %s: %s%s" $colourHook $hookSlug $context.hook $colourReset)
+      "severity" "info"
+  ) -}}
+
+  {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+      "message" "hook is extended"
+      "severity" "debug"
+  ) -}}
+{{- else -}}
+  {{- $context = collections.Merge $context (collections.Dictionary
+      "hook" $input
+      "context" (collections.Dictionary)
+      "type" "simple"
+  ) -}}
+
+  {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+      "message" (fmt.Printf "%s>>> %s: %s%s" $colourHook $hookSlug $context.hook $colourReset)
+      "severity" "info"
+  ) -}}
+
+  {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+      "message" "hook is simple"
+      "severity" "debug"
+  ) -}}
+{{- end -}}
+
+{{- partials.Include "func/hooks/collector.html" (collections.Dictionary
+    "hook" $context.hook
+    "context" $context
+) -}}
+
+{{- $loaded := false -}}
+{{- $output := "" -}}
+
+{{- $partialName := fmt.Printf "hooks/%s.html" $context.hook -}}
+{{- $partialHook := fmt.Printf "_partials/%s" $partialName -}}
+
+{{- partials.Include "func/debug-cli.html" (collections.Dictionary
+    "message" (fmt.Printf "partial Name: %s" $partialName)
+    "severity" "debug"
+) -}}
+
+{{- $cache := false -}}
+
+{{- with $context.cache -}}
+  {{- $cache = . -}}
+{{- else -}}
+  {{- with $context.cached -}}
+    {{- $cache = . -}}
+  {{- end -}}
+{{- end -}}
+
+{{- if templates.Exists $partialHook -}}
+  {{- if compare.Eq true $cache -}}
+    {{- $output = partials.IncludeCached $partialName $context.context $context.hook -}}
+
+    {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+        "message" "included cached"
+        "severity" "debug"
+    ) -}}
+
+    {{- $loaded = true -}}
+  {{- else -}}
+    {{- $output = partials.Include $partialName $context.context -}}
+
+    {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+        "message" "included uncached"
+        "severity" "debug"
+    ) -}}
+
+    {{- $loaded = true -}}
+  {{- end -}}
+{{- end -}}
+
+{{- if compare.Eq $loaded false -}}
+  {{- if not (collections.In $disableMessages "unused_hooks") -}}
+    {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+        "message" (fmt.Printf "%s<<< %s: `%s` %sunused%s" $colourHook $hookSlug $context.hook $colourUnused $colourReset)
+        "context" $input
+        "severity" "info"
+    ) -}}
+  {{- end -}}
+{{- else -}}
+  {{- partials.Include "func/debug-cli.html" (collections.Dictionary
+      "message" (fmt.Printf "%s<<< %s: %s done%s" $colourHook $hookSlug $context.hook $colourReset)
+      "severity" "info"
+  ) -}}
+{{- end -}}
+
+{{- return $output -}}
\ No newline at end of file
diff --git a/layouts/_partials/func/debug-cli.html b/layouts/_partials/func/debug-cli.html
new file mode 100644
index 0000000..acd844d
--- /dev/null
+++ b/layouts/_partials/func/debug-cli.html
@@ -0,0 +1,87 @@
+{{- $config := site.Params.ananke.hooks | compare.Default (collections.Dictionary) -}}
+{{/* 
+
+$config.namespace
+$config.ignoreErrors
+$config.verbosity
+
+*/}}
+{{/*
+[ananke.hooks]
+verbosity = "warning" # debug, info, warning, error
+
+@todo add and document hook ignoreErrors setting via config
+*/}}
+
+{{/*
+| Primary | `#AA99FF` | `170, 153, 255` |
+| Ananke  | `#FF80BF` | `255, 128, 191` |
+| Comment | `#708CA9` | `112, 140, 169` |
+| Error   | `#CC7766` | `204, 119, 102` |
+| Warning | `#CCCC66` | `204, 204, 102` |
+| Info    | `#6ECC66` | `110, 204, 102` |
+*/}}
+
+{{- $colourPrimary := "\033[38;2;170;153;255m" -}}
+{{- $colourAnanke := "\033[38;2;255;128;191m" -}}
+{{- $colourComment := "\033[38;2;112;140;169m" -}}
+{{- $colourError := "\033[38;2;204;119;102m" -}}
+{{- $colourWarning := "\033[38;2;204;204;102m" -}}
+{{- $colourInfo := "\033[38;2;110;204;102m" -}}
+{{- $colourReset := "\033[0m" -}}
+
+{{- $namespace := $config.namespace | compare.Default "ananke" -}}
+{{- $severity := .severity | compare.Default "info" -}}
+{{- $severity = strings.ToLower $severity -}}
+{{- if or (compare.Eq $severity "warnings") (compare.Eq $severity "warn") -}}
+  {{- $severity = "warning" -}}
+{{- else if or (compare.Eq $severity "errors") (compare.Eq $severity "err") -}}
+  {{- $severity = "error" -}}
+{{- else if compare.Eq $severity "infos" -}}
+  {{- $severity = "info" -}}
+{{- end -}}
+
+{{- $verbosity := $config.verbosity | compare.Default "error" -}}
+{{- $verbosity = strings.ToLower $verbosity -}}
+{{- if or (compare.Eq $verbosity "warnings") (compare.Eq $verbosity "warn") -}}
+  {{- $verbosity = "warning" -}}
+{{- else if or (compare.Eq $verbosity "errors") (compare.Eq $verbosity "err") -}}
+  {{- $verbosity = "error" -}}
+{{- else if compare.Eq $verbosity "infos" -}}
+  {{- $verbosity = "info" -}}
+{{- else if not (or (compare.Eq $verbosity "debug") (compare.Eq $verbosity "info") (compare.Eq $verbosity "warning") (compare.Eq $verbosity "error")) -}}
+  {{- $verbosity = "error" -}}
+{{- end -}}
+
+{{- $shouldOutput := false -}}
+{{- if compare.Eq $verbosity "debug" -}}
+  {{- $shouldOutput = true -}}
+{{- else if and (compare.Eq $verbosity "info") (not (compare.Eq $severity "debug")) -}}
+  {{- $shouldOutput = true -}}
+{{- else if and (compare.Eq $verbosity "warning") (or (compare.Eq $severity "warning") (compare.Eq $severity "error")) -}}
+  {{- $shouldOutput = true -}}
+{{- else if and (compare.Eq $verbosity "error") (compare.Eq $severity "error") -}}
+  {{- $shouldOutput = true -}}
+{{- end -}}
+
+{{- $colourLevel := $colourInfo -}}
+{{- $severityLabel := "INFO" -}}
+
+{{- if compare.Eq $severity "warning" -}}
+  {{- $colourLevel = $colourWarning -}}
+  {{- $severityLabel = "WARN" -}}
+{{- else if compare.Eq $severity "error" -}}
+  {{- $colourLevel = $colourError -}}
+  {{- $severityLabel = "!ERR" -}}
+{{- else if compare.Eq $severity "debug" -}}
+  {{- $colourLevel = $colourComment -}}
+  {{- $severityLabel = "DEBG" -}}
+{{- end -}}
+
+{{- if $shouldOutput -}}
+  {{- if compare.Eq $severity "error" -}}
+    {{- fmt.Errorf (fmt.Printf "%s%s%s/%s%s %s%s%s" $colourAnanke $namespace $colourComment $colourLevel $severityLabel $colourComment .message $colourReset) -}}
+  {{- else -}}
+    {{- fmt.Warnf (fmt.Printf "%s%s%s/%s%s %s%s%s" $colourAnanke $namespace $colourComment $colourLevel $severityLabel $colourComment .message $colourReset) -}}
+  {{- end -}}
+{{- end -}}
diff --git a/layouts/_partials/func/hooks/collector-dump.html b/layouts/_partials/func/hooks/collector-dump.html
new file mode 100644
index 0000000..89ba998
--- /dev/null
+++ b/layouts/_partials/func/hooks/collector-dump.html
@@ -0,0 +1,5 @@
+{{- $scratch := page.Store.Get "ananke.hooks" -}}
+{{- partials.Include "func/debug-cli.html" (collections.Dictionary "message" "Hooks Collector" "severity" "debug") -}}
+{{- range $index, $item := $scratch -}}
+    {{- partials.Include "func/debug-cli.html" (collections.Dictionary "message" (printf "%d: %#v" $index $item) "severity" "debug") -}}
+{{- end -}}
diff --git a/layouts/_partials/func/hooks/collector.html b/layouts/_partials/func/hooks/collector.html
new file mode 100644
index 0000000..3fd5ea1
--- /dev/null
+++ b/layouts/_partials/func/hooks/collector.html
@@ -0,0 +1 @@
+{{- page.Store.Add "ananke.hooks" (slice .hook) -}}
diff --git a/layouts/_partials/hook.html b/layouts/_partials/hook.html
new file mode 100644
index 0000000..770b996
--- /dev/null
+++ b/layouts/_partials/hook.html
@@ -0,0 +1,5 @@
+{{/* "A hook is just a filter that outputs immediately!" he said and moved on with his life. */}}
+{{- partials.Include "filter.html" (collections.Dictionary
+    "__ananke_hook_input" .
+    "__ananke_hook_slug" "HOOK"
+) -}}
\ No newline at end of file
diff --git a/layouts/_partials/hooks/article/section-link.html b/layouts/_partials/hooks/article/section-link.html
new file mode 100644
index 0000000..827bf0d
--- /dev/null
+++ b/layouts/_partials/hooks/article/section-link.html
@@ -0,0 +1,3 @@
+<aside class="instapaper_ignoref b helvetica tracked ttu">
+    {{ .CurrentSection.Title }}
+</aside>
\ No newline at end of file
diff --git a/layouts/_partials/hooks/article/test.html b/layouts/_partials/hooks/article/test.html
new file mode 100644
index 0000000..1805de8
--- /dev/null
+++ b/layouts/_partials/hooks/article/test.html
@@ -0,0 +1 @@
+{{ return printf . "World" }}
\ No newline at end of file
diff --git a/layouts/baseof.html b/layouts/baseof.html
index 95d89e6..9932b8e 100644
--- a/layouts/baseof.html
+++ b/layouts/baseof.html
@@ -79,3 +79,6 @@
     {{ block "footer" . }}{{ partials.IncludeCached "site-footer.html" . }}{{ end }}
   </body>
 </html>
+{{- with (templates.Defer (dict "key" "hooks-collector")) -}}
+  {{- partials.Include "func/hooks/collector-dump.html" . }}
+{{ end -}}
\ No newline at end of file
diff --git a/layouts/single.html b/layouts/single.html
index 9afbcf9..7ee50ad 100644
--- a/layouts/single.html
+++ b/layouts/single.html
@@ -17,13 +17,7 @@
 
   <article class="{{ $post_class }} flex-l {{ if $needs_aside }}mw8{{ else }}mw7{{ end }} center ph3 flex-wrap justify-between">
     <header class="mt4 w-100">
-      <aside class="instapaper_ignoref b helvetica tracked ttu">
-          {{/*
-          CurrentSection allows us to use the section title instead of inferring from the folder.
-          https://gohugo.io/variables/page/#section-variables-and-methods
-          */}}
-        {{ .CurrentSection.Title }}
-      </aside>
+      {{- partials.Include "hook.html" ( dict "hook" "article/section-link"  "context" . ) -}}
       {{- partials.IncludeCached "social/share.html" . . -}}
       <h1 class="f1 athelas mt3 mb1">
         {{- .Title -}}

--
Gitblit v1.10.0