summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-01 17:39:53 +0200
committerPaul Buetow <paul@buetow.org>2026-03-01 17:39:53 +0200
commit6136b8a79a7f0ce5e35dc07cd88965953bd0e1af (patch)
treea916024f725f8de6e79d85cabfaecfb723ac65b7
parent309c63c420c425a55ee2a91dd03c77138305a05d (diff)
Extract generate::extract_title helper to eliminate duplication
Title extraction ($SED pattern + quote sanitization) was duplicated in generate, atomfeed, gemfeed, notes, and template modules. Now centralized with unit tests for heading extraction and empty files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rw-r--r--lib/atomfeed.source.sh2
-rw-r--r--lib/gemfeed.source.sh3
-rw-r--r--lib/generate.source.sh18
-rw-r--r--lib/notes.source.sh3
-rw-r--r--lib/template.source.sh2
5 files changed, 21 insertions, 7 deletions
diff --git a/lib/atomfeed.source.sh b/lib/atomfeed.source.sh
index 3fe46be..20a87d0 100644
--- a/lib/atomfeed.source.sh
+++ b/lib/atomfeed.source.sh
@@ -121,7 +121,7 @@ atomfeed::_entry () {
assert::not_empty content "$content"
# Extract first heading as post title.
- local title=$($SED -n '/^# / { s/# //; p; q; }' "$gemfeed_dir/$gmi_file" | tr '"' "'")
+ local title=$(generate::extract_title "$gemfeed_dir/$gmi_file")
assert::not_empty title "$title"
# Extract first paragraph from Gemtext as the summary.
diff --git a/lib/gemfeed.source.sh b/lib/gemfeed.source.sh
index 40792cc..8700ac4 100644
--- a/lib/gemfeed.source.sh
+++ b/lib/gemfeed.source.sh
@@ -47,8 +47,7 @@ GEMFEED
while read -r gmi_file; do
# Extract first heading as post title.
- local title=$($SED -n '/^# / { s/# //; p; q; }' \
- "$gemfeed_dir/$gmi_file" | tr '"' "'")
+ local title=$(generate::extract_title "$gemfeed_dir/$gmi_file")
# Extract the date from the file name, and also get the word count.
local filename_date=$(basename "$gemfeed_dir/$gmi_file" | cut -d- -f1,2,3)
diff --git a/lib/generate.source.sh b/lib/generate.source.sh
index 544ae08..17322e7 100644
--- a/lib/generate.source.sh
+++ b/lib/generate.source.sh
@@ -53,6 +53,13 @@ generate::safe_overwrite () {
fi
}
+# Extract the first heading from a .gmi file as a title, with double quotes
+# replaced by single quotes for safe embedding in feeds and HTML attributes.
+generate::extract_title () {
+ local -r file="$1"; shift
+ $SED -n '/^# / { s/# //; p; q; }' "$file" | tr '"' "'"
+}
+
# Add other docs (e.g. images, videos) from Gemtext to output format.
# Skips copying if the output file already exists and is newer than the source.
generate::fromgmi_add_docs () {
@@ -119,7 +126,7 @@ generate::_to_output_format () {
dest=${dest/.gmi/.$format}
local dest_dir=$(dirname "$dest")
- local title=$($SED -n '/^# / { s/# //; p; q; }' "$src" | tr '"' "'")
+ local title=$(generate::extract_title "$src")
if [[ -z "$title" ]]; then
title="$SUBTITLE"
fi
@@ -337,5 +344,14 @@ generate::test () {
assert::equals "$(cat "$tmp_dir/file")" 'different content'
assert::equals "$(test -f "$tmp_dir/file.tmp" && echo exists || echo gone)" 'gone'
+ # Test generate::extract_title: extracts first heading and sanitizes quotes
+ echo '# My "Great" Title' > "$tmp_dir/test.gmi"
+ echo '## Not this one' >> "$tmp_dir/test.gmi"
+ assert::equals "$(generate::extract_title "$tmp_dir/test.gmi")" "My 'Great' Title"
+
+ # Test generate::extract_title: file with no heading returns empty
+ echo 'Just a paragraph' > "$tmp_dir/noheading.gmi"
+ assert::equals "$(generate::extract_title "$tmp_dir/noheading.gmi")" ''
+
rm -rf "$tmp_dir"
}
diff --git a/lib/notes.source.sh b/lib/notes.source.sh
index dc62caf..64625b4 100644
--- a/lib/notes.source.sh
+++ b/lib/notes.source.sh
@@ -27,8 +27,7 @@ NOTES
while read -r gmi_file; do
# Extract first heading as post title.
- local title=$($SED -n '/^# / { s/# //; p; q; }' \
- "$notes_dir/$gmi_file" | tr '"' "'")
+ local title=$(generate::extract_title "$notes_dir/$gmi_file")
echo "=> ./$gmi_file $title" >> "$notes_dir/index.gmi.tmp"
done < <(notes::_get_notes)
diff --git a/lib/template.source.sh b/lib/template.source.sh
index b93303e..0e34a87 100644
--- a/lib/template.source.sh
+++ b/lib/template.source.sh
@@ -143,7 +143,7 @@ template::inline::_index () {
for topic in "$@"; do
while read -r gmi_file; do
local date=$(cut -d- -f1,2,3 <<< "$gmi_file")
- local title=$($SED -n "/^# / { s/# //; p; q; }" "$gmi_file")
+ local title=$(generate::extract_title "$gmi_file")
local current=''
if [ "$gmi_file" = "$CURRENT_GMI" ]; then