aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--config/_default/hugo.toml7
-rw-r--r--docs/adr-word-count.md112
-rw-r--r--layouts/_default/home.wordstats.json11
-rw-r--r--layouts/_default/single.html2
-rw-r--r--scripts/stats.py23
6 files changed, 157 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 40c3b9c..0b6adee 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,9 @@ build:
rm -rf public/
hugo
+stats: build
+ @python3 scripts/stats.py public/wordstats.json
+
deploy: build
rsync --rsync-path="sudo rsync" -avz --delete public/ ubuntu@feed.kagamino.dev:/var/www/kagamino_blog/
diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml
index 59787e5..d04c42c 100644
--- a/config/_default/hugo.toml
+++ b/config/_default/hugo.toml
@@ -22,7 +22,14 @@ tabWidth = 4
noClasses = true
style = "monokai"
+[outputFormats.wordstats]
+mediaType = "application/json"
+baseName = "wordstats"
+isPlainText = true
+notAlternative = true
+
[outputs]
+home = ["html", "rss", "wordstats"]
section = ["html", "rss"]
[author]
diff --git a/docs/adr-word-count.md b/docs/adr-word-count.md
new file mode 100644
index 0000000..17945f2
--- /dev/null
+++ b/docs/adr-word-count.md
@@ -0,0 +1,112 @@
+# ADR: Word Count Indication for Blog Posts
+
+## 0. Solution
+
+Two changes:
+
+1. **Per-post word count in Hugo template** — add `{{ .WordCount }}` to `layouts/_default/single.html` next to the existing reading time display.
+2. **Post-build stats command** — add a `make stats` target that runs `hugo list all` + `hugo` to compute and print total, mean, and variance of word counts across published (non-draft) posts to the terminal.
+
+## 1. Context & Scope
+
+This is a Hugo v0.155.2 blog with 4 posts (total ~5,500 raw words across markdown files). Hugo already provides built-in `.WordCount` and `.ReadingTime` page variables — `.ReadingTime` is already displayed in `layouts/_default/single.html`.
+
+The goal is twofold:
+- **Per-post word count** — visible to the author (and optionally readers)
+- **Aggregate word count** — total words across all blog posts, for personal tracking
+
+Key files:
+- `layouts/_default/single.html` — single post template (already shows reading time)
+- `layouts/partials/list/default.html` — post card in list view
+- `layouts/posts/list.html` — posts list page
+- `content/posts/*/index.md` — post content
+
+Note: Hugo's `.WordCount` counts rendered content words (excluding front matter, code fences, HTML tags), which differs from raw `wc -w` on the markdown file.
+
+## 2. Goals & Non-Goals
+
+**Goals:**
+- Show word count per blog post
+- Track total word count across all posts
+- Low maintenance — leverage Hugo built-ins where possible
+
+**Non-Goals:**
+- Real-time / live-updating word count during writing (IDE concern)
+- Per-section or per-heading word counts
+- Historical word count tracking over time (git can do this)
+
+## 3. Design
+
+```mermaid
+graph TD
+ subgraph "Hugo Built-in"
+ WC[".WordCount per page"]
+ end
+
+ subgraph "Per-Post Display"
+ single["single.html — show word count in header meta"]
+ end
+
+ subgraph "CLI Stats"
+ make-stats["make stats — shell script"]
+ make-stats --> hugo-build["hugo (build site)"]
+ hugo-build --> grep["grep/parse word counts from output or generated files"]
+ grep --> print["print total, mean, variance"]
+ end
+
+ WC --> single
+```
+
+Two independent pieces:
+- **Template change**: one line in `single.html` to show `.WordCount`.
+- **Makefile target**: a `make stats` command that builds the site, extracts per-post word counts (non-draft), and prints total/mean/variance. Hugo can output word counts via a custom layout that writes to a known file, or we use a shell script that calls Hugo's template engine to dump counts.
+
+## 4. Alternatives (for the stats command)
+
+### A. Hugo custom output format (recommended)
+
+Add a custom output format (e.g. `stats`) that produces a plain-text or JSON file with word counts per post during `hugo build`. A shell script in `make stats` parses that file and computes total/mean/variance.
+
+| Pros | Cons |
+|------|------|
+| Word counts from Hugo's own `.WordCount` — consistent with what's displayed | Requires a custom output format + template |
+| Single source of truth | Slightly more Hugo config |
+
+### B. Shell script with `wc -w`
+
+`make stats` runs `wc -w` on all non-draft `index.md` files and computes stats with `awk`.
+
+| Pros | Cons |
+|------|------|
+| No Hugo config changes | Counts differ from Hugo's `.WordCount` (includes front matter, URLs, markdown syntax) |
+| Very simple | Must parse front matter to detect `draft: true` |
+
+### C. Python script
+
+Use Python (already in the project for tests) to parse markdown, strip front matter, count words, compute stats.
+
+| Pros | Cons |
+|------|------|
+| Full control, can match Hugo's counting closely | Overkill, another dependency on counting logic |
+| Could reuse pyproject.toml setup | |
+
+## 5. Open Questions
+
+All resolved:
+
+1. ~~Where to show per-post word count?~~ → **Single post page only**, in header meta.
+2. ~~Where to show aggregate?~~ → **Not displayed on site**. CLI `make stats` for total/mean/variance.
+3. ~~Include drafts in aggregate?~~ → **No**, exclude drafts.
+4. ~~CLI command?~~ → **Yes**, `make stats`.
+5. ~~Hugo vs raw word count?~~ → **Hugo's `.WordCount`** is fine.
+
+**Remaining:** None — Alternative A selected for stats command.
+
+## 6. Parties Involved
+
+- **Author:** Kagamino (Martin Lehoux)
+
+## 7. Cross Concerns
+
+- **Performance:** `.WordCount` and `range` are negligible for a small blog.
+- **Accuracy:** Hugo's `.WordCount` strips HTML/shortcodes and counts rendered text words. Code blocks are included in the count. This is generally more meaningful than raw `wc -w` which includes front matter YAML, URLs, and markdown syntax.
diff --git a/layouts/_default/home.wordstats.json b/layouts/_default/home.wordstats.json
new file mode 100644
index 0000000..b227510
--- /dev/null
+++ b/layouts/_default/home.wordstats.json
@@ -0,0 +1,11 @@
+[
+{{- $pages := where site.RegularPages "Section" "posts" -}}
+{{- $first := true -}}
+{{- range $pages -}}
+{{- if not .Draft -}}
+{{- if not $first }},{{ end -}}
+{"title":{{ .Title | jsonify }},"wordCount":{{ .WordCount }}}
+{{- $first = false -}}
+{{- end -}}
+{{- end -}}
+]
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index 271ddd8..d20343b 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -7,7 +7,7 @@
<div class="meta">
{{/* Publish date */}}
<time datetime="{{ .PublishDate }}" title='{{ .PublishDate.Format "Mon, Jan 2, 2006, 3:04 PM MST" }}'>
- {{ .PublishDate.Format "02/01/2006" }} - Estimated reading time: {{ .ReadingTime }} minutes
+ {{ .PublishDate.Format "02/01/2006" }} - {{ .WordCount }} words - Estimated reading time: {{ .ReadingTime }} minutes
</time>
{{/* Categories */}}
diff --git a/scripts/stats.py b/scripts/stats.py
new file mode 100644
index 0000000..5cef3b6
--- /dev/null
+++ b/scripts/stats.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+import json
+import math
+import sys
+
+with open(sys.argv[1]) as f:
+ posts = json.load(f)
+
+words = [p["wordCount"] for p in posts]
+n = len(words)
+total = sum(words)
+mean = total / n
+stddev = math.sqrt(sum(w * w for w in words) / n - mean * mean)
+
+print(f"Posts: {n}")
+print(f"Total: {total} words")
+print(f"Mean: {mean:.0f} words")
+print(f"Std Dev: {stddev:.0f} words")
+print()
+print(f"{'Title':<60s} {'Words'}")
+print(f"{'-----':<60s} {'-----'}")
+for p in posts:
+ print(f"{p['title']:<60s} {p['wordCount']}")