13 Commits
v1.20 ... main

Author SHA1 Message Date
Alex Schroeder
f0d814b8f3 Document that webp images can be encoded 2026-02-22 23:16:43 +01:00
Alex Schroeder
41f6d9e48e Run go mod tidy 2026-02-20 13:38:12 +01:00
Alex Schroeder
7beaf3e375 Mention the man subcommand in the README 2026-02-20 13:37:38 +01:00
Alex Schroeder
8f277714d7 Note how to install into /usr/local 2026-02-12 20:41:54 +01:00
Alex Schroeder
11eace1b3c Update license copyright
Add the check to the RELEASE file. Add "generator" with link to the
sources to view, feed and static templates.
2026-02-12 13:30:39 +01:00
Alex Schroeder
ad778a3068 Embed the man pages 2026-02-11 16:52:30 +01:00
Alex Schroeder
6ec69d27eb Note that the templates are written when missing 2026-02-10 12:55:18 +01:00
Alex Schroeder
0a093182e9 Embed the default templates
If any templates are missing when templates are initialized, an
embedded version is written into the working directory.
2026-02-10 12:46:43 +01:00
Alex Schroeder
9952f2363b Note where to get golint, go-critic and goimports from 2026-02-10 12:45:35 +01:00
Alex Schroeder
4d14517668 Add comments to exported types 2026-02-10 12:40:11 +01:00
Alex Schroeder
0051a5ca66 Add embedded templates 2026-02-10 00:37:32 +01:00
Alex Schroeder
2436cc1114 Improve man oddmu 2026-02-08 16:25:13 +01:00
Alex Schroeder
96993b794a Upgrade from Go 1.22 to Go 1.25 2026-02-06 16:14:43 +01:00
24 changed files with 436 additions and 167 deletions

View File

@@ -1,4 +1,4 @@
This software is Copyright (c) 20152024 by Alex Schroeder.
This software is Copyright (c) 20152026 by Alex Schroeder.
This is free software, licensed under:

View File

@@ -39,10 +39,13 @@ test:
rm -rf testdata/*
go test -shuffle on .
# go install golang.org/x/lint/golint@latest
# go install github.com/go-critic/go-critic/cmd/go-critic@latest
check:
golint
gocritic check
go-critic check
# go install golang.org/x/tools/cmd/goimports@latest
fix:
goimports -w *.go

View File

@@ -65,6 +65,11 @@ changes.
This man page documents the "version" subcommand which you can use to
get the installed Oddmu version.
[oddmu-man(1)](https://alexschroeder.ch/view/oddmu/oddmu-man.1): Oddmu
comes with a "man" subcommand to print the manual pages. This man page
documents the subcommand, but I guess if you can read the man page,
you don't need the "man" subcommand.
Working locally:
[oddmu-links(1)](https://alexschroeder.ch/view/oddmu/oddmu-links.1):
@@ -220,6 +225,13 @@ into `$HOME/.local/share/man/`.
make install
```
This installs `oddmu` into `/usr/local/bin` and the manual pages into
`/usr/local/share/man`:
```sh
sudo make install PREFIX=/usr/local
```
Here's an example using [GNU Stow](https://www.gnu.org/software/stow/)
to install it into `/usr/local/stow` in a way that allows you to
uninstall it later:

16
RELEASE
View File

@@ -1,22 +1,22 @@
When preparing a new release
----------------------------
1. Run tests
1. run tests
2. Update man/oddmu-releases.7.txt
2. update man/oddmu-releases.7.txt
- add missing items
- change "(unreleased)"
3. make docs
3. check copyright year in LICENSE
4. Make sure all files are checked in
4. make docs
5. Tag the release and push the tag to all remotes
5. make sure all files are checked in
6. cd man && make upload
6. tag the release and push the tag to the remote
7. make dist
8. create a new release at https://github.com/kensanata/oddmu/releases
8. make dist-upload
9. upload the four .tar.gz binaries to the GitHub release
9. cd man && make upload

View File

@@ -11,6 +11,7 @@
<atom:link href="https://example.org/view/{{.Dir}}{{.PrevYear}}.rss?n={{.N}}" rel="previous" type="application/rss+xml"/>{{end}}{{if .Next}}
<atom:link href="https://example.org/view/{{.Path}}.rss?from={{.Next}}&amp;n={{.N}}" rel="next" type="application/rss+xml"/>{{end}}{{if .NextYear}}
<atom:link href="https://example.org/view/{{.Dir}}{{.NextYear}}.rss?n={{.N}}" rel="next" type="application/rss+xml"/>{{end}}{{if .Complete}}
<generator>Oddμ https://src.alexschroeder.ch/oddmu.git/</generator>
<fh:complete/>{{end}}
<description>This is the digital garden of Your Name.</description>
<image>

View File

@@ -78,7 +78,7 @@ func (p *Page) printFeed(w io.Writer, ti time.Time) subcommands.ExitStatus {
fmt.Fprintf(os.Stderr, "Cannot write prefix: %s\n", err)
return subcommands.ExitFailure
}
loadTemplates()
initTemplates()
templates.RLock()
defer templates.RUnlock()
err = templates.template["feed.html"].Execute(w, f)

41
go.mod
View File

@@ -1,41 +1,40 @@
module src.alexschroeder.ch/oddmu
go 1.22
toolchain go1.22.3
go 1.25
require (
github.com/disintegration/imaging v1.6.2
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f
github.com/fsnotify/fsnotify v1.7.0
github.com/gabriel-vasile/mimetype v1.4.3
github.com/gen2brain/heic v0.3.1
github.com/gen2brain/webp v0.5.2
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be
github.com/fsnotify/fsnotify v1.9.0
github.com/gabriel-vasile/mimetype v1.4.13
github.com/gen2brain/heic v0.4.9
github.com/gen2brain/webp v0.5.5
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
github.com/google/subcommands v1.2.0
github.com/hexops/gotextdiff v1.0.3
github.com/microcosm-cc/bluemonday v1.0.26
github.com/microcosm-cc/bluemonday v1.0.27
github.com/muesli/reflow v0.3.0
github.com/pemistahl/lingua-go v1.4.0
github.com/sergi/go-diff v1.3.1
github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
)
require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.6 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/tetratelabs/wazero v1.8.1 // indirect
golang.org/x/image v0.15.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.21.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
golang.org/x/image v0.35.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

78
go.sum
View File

@@ -1,26 +1,30 @@
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f h1:RMnUwTnNR070mFAEIoqMYjNirHj8i0h79VXTYyBCyVA=
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gen2brain/heic v0.3.1 h1:ClY5YTdXdIanw7pe9ZVUM9XcsqH6CCCa5CZBlm58qOs=
github.com/gen2brain/heic v0.3.1/go.mod h1:m2sVIf02O7wfO8mJm+PvE91lnq4QYJy2hseUon7So10=
github.com/gen2brain/webp v0.5.2 h1:aYdjbU/2L98m+bqUdkYMOIY93YC+EN3HuZLMaqgMD9U=
github.com/gen2brain/webp v0.5.2/go.mod h1:Nb3xO5sy6MeUAHhru9H3GT7nlOQO5dKRNNlE92CZrJw=
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e h1:ESHlT0RVZphh4JGBz49I5R6nTdC8Qyc08vU25GQHzzQ=
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be h1:FNPYI8/ifKGW7kdBdlogyGGaPXZmOXBbV1uz4Amr3s0=
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be/go.mod h1:G3dK5MziX9e4jUa8PWjowCOPCcyQwxsZ5a0oYA73280=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gen2brain/heic v0.4.9 h1:sHM7kjMV2+AlrSfYNsJiD0NrqAJqIMWAOqMSZ0HXrU8=
github.com/gen2brain/heic v0.4.9/go.mod h1:0/0SrVQnUhOA3ekFY5/lApZYniF/DsgS3g9COWe83dM=
github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg=
github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
@@ -33,10 +37,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/pemistahl/lingua-go v1.4.0 h1:ifYhthrlW7iO4icdubwlduYnmwU37V1sbNrwhKBR4rM=
@@ -45,32 +49,30 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

39
man/oddmu-man.1 Normal file
View File

@@ -0,0 +1,39 @@
.\" Generated by scdoc 1.11.3
.\" Complete documentation for this program is not available as a GNU info page
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-MAN" "1" "2026-02-11"
.PP
.SH NAME
.PP
oddmu-man - print the manual pages
.PP
.SH SYNOPSIS
.PP
\fBoddmu man\fR
.PP
\fBoddmu man\fR \fItopic\fR
.PP
.SH DESCRIPTION
.PP
The "man" subcommand lists the topics available or prints the manual page for
the given topic.\&
.PP
Example:
.PP
.nf
.RS 4
oddmu man apache
.fi
.RE
.PP
.SH SEE ALSO
.PP
\fIoddmu\fR(1)
.PP
.SH AUTHORS
.PP
Maintained by Alex Schroeder <alex@gnu.\&org>.\&

30
man/oddmu-man.1.txt Normal file
View File

@@ -0,0 +1,30 @@
ODDMU-MAN(1)
# NAME
oddmu-man - print the manual pages
# SYNOPSIS
*oddmu man*
*oddmu man* _topic_
# DESCRIPTION
The "man" subcommand lists the topics available or prints the manual page for
the given topic.
Example:
```
oddmu man apache
```
# SEE ALSO
_oddmu_(1)
# AUTHORS
Maintained by Alex Schroeder <alex@gnu.org>.

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-RELEASES" "7" "2026-02-06"
.TH "ODDMU-RELEASES" "7" "2026-02-11"
.PP
.SH NAME
.PP
@@ -15,6 +15,14 @@ oddmu-releases - what'\&s new?\&
.PP
This page lists user-visible features and template changes to consider.\&
.PP
.SS 1.21 (unreleased)
.PP
Write any missing templates when Oddmu starts up.\&
.PP
Add man subcommand to print manual pages.\&
.PP
Both of these features make it possible to distribute just the binary.\&
.PP
.SS 1.20 (2026)
.PP
Add -shrink and -glob options to the \fIstatic\fR subcommand.\& See \fIoddmu-static\fR(1)

View File

@@ -8,6 +8,14 @@ oddmu-releases - what's new?
This page lists user-visible features and template changes to consider.
## 1.21 (unreleased)
Write any missing templates when Oddmu starts up.
Add man subcommand to print manual pages.
Both of these features make it possible to distribute just the binary.
## 1.20 (2026)
Add -shrink and -glob options to the _static_ subcommand. See _oddmu-static_(1)

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU" "1" "2026-01-03"
.TH "ODDMU" "1" "2026-02-11"
.PP
.SH NAME
.PP
@@ -32,10 +32,11 @@ create, most likely.\&
.PP
See \fIoddmu\fR(5) for details about the page formatting.\&
.PP
If you request a page that doesn'\&t exist, Oddmu tries to find a matching
Markdown file by appending the extension ".\&md" to the page name.\& In the example
above, the page name requested is "index" and the file name Oddmu tries to read
is "index.\&md".\& If no such file exists, Oddmu offers you to create the page.\&
If you request a file that exists, like "index.\&md", Oddmu serves it as-is.\& If
you request a file that doesn'\&t exist, like "index", Oddmu checks if a matching
Markdown file exists by appending the extension ".\&md".\& If such a file is found,
it is turned into HTML and shown.\& If no such file exists, Oddmu offers you to
create the page.\&
.PP
If your files don'\&t provide their own title ("# title"), the file name (without
".\&md") is used for the page title.\&
@@ -98,9 +99,9 @@ curl --form body="Did you bring a towel?"
When calling the \fIdrop\fR action, the query parameters used are \fIname\fR for the
target filename and \fIfile\fR for the file to upload.\& If the query parameter
\fImaxwidth\fR is set, an attempt is made to decode and resize the image.\& JPG, PNG,
WEBP and HEIC files can be decoded.\& Only JPG and PNG files can be encoded,
however.\& If the target name ends in \fI.\&jpg\fR, the \fIquality\fR query parameter is
also taken into account.\& To upload some thumbnails:
WEBP and HEIC files can be decoded.\& Only JPG, PNG and WEBP files can be encoded,
however.\& If the target name ends in \fI.\&jpg\fR or \fI.\&png\fR, the \fIquality\fR query
parameter is also taken into account.\& To upload some thumbnails:
.PP
.nf
.RS 4
@@ -121,12 +122,13 @@ curl \&'http://localhost:8080/search/?q=towel\&'
.RE
.PP
The page name to act upon is optionally taken from the query parameter \fIid\fR.\& In
this case, the directory must also be part of the query parameter and not of the
URL path.\&
this case, the directory must still be part of the path and may not be part of
the \fIid\fR.\& This is enforced so that the path can be used by a webserver for
access control.\&
.PP
.nf
.RS 4
curl \&'http://localhost:8080/view/?id=man/oddmu\&.1\&.txt\&'
curl \&'http://localhost:8080/view/man/?id=oddmu\&.1\&.txt\&'
.fi
.RE
.PP
@@ -142,8 +144,9 @@ curl --remote-name \&'http://localhost:8080/archive/man/man\&.zip
.PP
.SH CONFIGURATION
.PP
The template files are the HTML files in the working directory.\& Please change
these templates!\&
The template files are the HTML files in the working directory.\& If they are
missing, the default files are written to disk as soon as they are required.\&
Please change these templates!\&
.PP
The first change you should make is to replace the name and email address in the
footer of \fIview.\&html\fR.\& Look for "Your Name" and "example.\&org".\&
@@ -302,55 +305,62 @@ current date of the machine Oddmu is running on is used.\& If a link already
exists on the changes page, it is moved up to the current date.\& If that leaves
an old date without any links, that date heading is removed.\&
.PP
If you want to link to the changes page, you need to do this yourself.\& Add a
link from the index, for example.\& The "view.\&html" template currently doesn'\&t do
it.\& See \fIoddmu-templates\fR(5) if you want to add the link to the template.\&
.PP
A page whose name starts with an ISO date (YYYY-MM-DD, e.\&g.\& "2023-10-28") is
called a \fBblog\fR page.\& When creating or editing blog pages, links to it are added
from other pages.\&
from other pages as follows:
.PP
.PD 0
.IP \(bu 4
If the blog page name starts with the current year, a link is created from the
index page back to the blog page being created or edited.\& Again, you can prevent
this from happening by deselecting the checkbox "Add link to the list of
changes.\&" The index page can be edited like every other page, so it'\&s easy to
undo mistakes.\&
index page back to the blog page being created or edited.\& Again, you can
prevent this from happening by deselecting the checkbox "Add link to the list
of changes.\&" The index page can be edited like every other page, so it'\&s easy
to undo mistakes.\&
.PD
.PP
.PD 0
.IP \(bu 4
For every \fBhashtag\fR used, another link might be created.\& If a page named like
the hashtag exists, a backlink is added to it, linking to the new or edited blog
page.\&
the hashtag exists, a backlink is added to it, linking to the new or edited
blog page.\&
.PD
.PP
.PD 0
.IP \(bu 4
If a link to the new or edited blog page already exists but it'\&s title is no
longer correct, it is updated.\&
.PD
.PP
New links added for blog pages are added at the top of the first unnumbered list
using the asterisk ('\&*'\&).\& If no such list exists, a new one is started at the
bottom of the page.\& This allows you to have a different unnumbered list further
up on the page, as long as it uses the minus for items ('\&-'\&).\&
.PP
Changes made locally do not create any links on the changes page, the index page
or on any hashtag pages.\& See \fIoddmu-notify\fR(1) for a way to add the necessary
links to the changes page and possibly to the index and hashtag pages.\&
Changes made locally to the source files (using an editor) do not create any
links on the changes page, the index page or on any hashtag pages.\& See
\fIoddmu-notify\fR(1) for a way to add the necessary links to the changes page and
possibly to the index and hashtag pages.\&
.PP
A hashtag consists of a number sign ('\&#'\&) followed by Unicode letters, numbers
or the underscore ('\&_'\&).\& Thus, a hashtag ends with punctuation or whitespace.\&
.PP
The page names, titles and hashtags are loaded into memory when the server
starts.\& If you have a lot of pages, this takes a lot of memory.\&
starts.\& If you have a lot of pages, this takes a lot of memory.\& Oddmu watches
the working directory and any subdirectories for changes made to page files and
updates this cache when necessary.\&
.PP
Oddmu watches the working directory and any subdirectories for changes made
directly.\& Thus, in theory, it'\&s not necessary to restart it after making such
changes.\&
.PP
You cannot edit uploaded files.\& If you upload a file called "hello.\&txt" and
attempt to edit it by using "/edit/hello.\&txt" you create a page with the name
"hello.\&txt.\&md" instead.\&
Uploaded files cannot be edited unless they end with ".\&md".\& If you upload a file
called "hello.\&txt" and attempt to edit it by using "/edit/hello.\&txt" you create
a page with the name "hello.\&txt.\&md" instead.\&
.PP
In order to delete uploaded files via the web, create an empty file and upload
it.\& In order to delete a wiki page, save an empty page.\&
.PP
Note that some HTML file names are special: they act as templates.\& See
\fIoddmu-templates\fR(5) for their names and their use.\&
\fIoddmu-templates\fR(5) for their names and their use.\& Oddmu watches the working
directory and any subdirectories for changes made to template files and reloads
them.\& There is no need to restart the server after making changes to the
templates.\&
.PP
.SH SEE ALSO
.PP
@@ -420,6 +430,14 @@ If you want to stop using Oddmu:
\fIoddmu-export\fR(1), on how to export all the files as one big RSS file
.PD
.PP
And finally, if you don'\&t have the man pages, you can still read the original
documents:
.PP
.PD 0
.IP \(bu 4
\fIoddmu-man\fR(1), to get help even if you don'\&t have the man pages installed
.PD
.PP
.SH AUTHORS
.PP
Maintained by Alex Schroeder <alex@gnu.\&org>.\&

View File

@@ -25,10 +25,11 @@ create, most likely.
See _oddmu_(5) for details about the page formatting.
If you request a page that doesn't exist, Oddmu tries to find a matching
Markdown file by appending the extension ".md" to the page name. In the example
above, the page name requested is "index" and the file name Oddmu tries to read
is "index.md". If no such file exists, Oddmu offers you to create the page.
If you request a file that exists, like "index.md", Oddmu serves it as-is. If
you request a file that doesn't exist, like "index", Oddmu checks if a matching
Markdown file exists by appending the extension ".md". If such a file is found,
it is turned into HTML and shown. If no such file exists, Oddmu offers you to
create the page.
If your files don't provide their own title ("# title"), the file name (without
".md") is used for the page title.
@@ -71,9 +72,9 @@ curl --form body="Did you bring a towel?" \
When calling the _drop_ action, the query parameters used are _name_ for the
target filename and _file_ for the file to upload. If the query parameter
_maxwidth_ is set, an attempt is made to decode and resize the image. JPG, PNG,
WEBP and HEIC files can be decoded. Only JPG and PNG files can be encoded,
however. If the target name ends in _.jpg_, the _quality_ query parameter is
also taken into account. To upload some thumbnails:
WEBP and HEIC files can be decoded. Only JPG, PNG and WEBP files can be encoded,
however. If the target name ends in _.jpg_ or _.png_, the _quality_ query
parameter is also taken into account. To upload some thumbnails:
```
for f in *.jpg; do
@@ -90,11 +91,12 @@ curl 'http://localhost:8080/search/?q=towel'
```
The page name to act upon is optionally taken from the query parameter _id_. In
this case, the directory must also be part of the query parameter and not of the
URL path.
this case, the directory must still be part of the path and may not be part of
the _id_. This is enforced so that the path can be used by a webserver for
access control.
```
curl 'http://localhost:8080/view/?id=man/oddmu.1.txt'
curl 'http://localhost:8080/view/man/?id=oddmu.1.txt'
```
The base name for the _archive_ action is used by the browser to save the
@@ -107,8 +109,9 @@ curl --remote-name 'http://localhost:8080/archive/man/man.zip
# CONFIGURATION
The template files are the HTML files in the working directory. Please change
these templates!
The template files are the HTML files in the working directory. If they are
missing, the default files are written to disk as soon as they are required.
Please change these templates!
The first change you should make is to replace the name and email address in the
footer of _view.html_. Look for "Your Name" and "example.org".
@@ -247,55 +250,53 @@ current date of the machine Oddmu is running on is used. If a link already
exists on the changes page, it is moved up to the current date. If that leaves
an old date without any links, that date heading is removed.
If you want to link to the changes page, you need to do this yourself. Add a
link from the index, for example. The "view.html" template currently doesn't do
it. See _oddmu-templates_(5) if you want to add the link to the template.
A page whose name starts with an ISO date (YYYY-MM-DD, e.g. "2023-10-28") is
called a *blog* page. When creating or editing blog pages, links to it are added
from other pages.
from other pages as follows:
If the blog page name starts with the current year, a link is created from the
index page back to the blog page being created or edited. Again, you can prevent
this from happening by deselecting the checkbox "Add link to the list of
changes." The index page can be edited like every other page, so it's easy to
undo mistakes.
- If the blog page name starts with the current year, a link is created from the
index page back to the blog page being created or edited. Again, you can
prevent this from happening by deselecting the checkbox "Add link to the list
of changes." The index page can be edited like every other page, so it's easy
to undo mistakes.
For every *hashtag* used, another link might be created. If a page named like
the hashtag exists, a backlink is added to it, linking to the new or edited blog
page.
- For every *hashtag* used, another link might be created. If a page named like
the hashtag exists, a backlink is added to it, linking to the new or edited
blog page.
If a link to the new or edited blog page already exists but it's title is no
longer correct, it is updated.
- If a link to the new or edited blog page already exists but it's title is no
longer correct, it is updated.
New links added for blog pages are added at the top of the first unnumbered list
using the asterisk ('\*'). If no such list exists, a new one is started at the
bottom of the page. This allows you to have a different unnumbered list further
up on the page, as long as it uses the minus for items ('-').
Changes made locally do not create any links on the changes page, the index page
or on any hashtag pages. See _oddmu-notify_(1) for a way to add the necessary
links to the changes page and possibly to the index and hashtag pages.
Changes made locally to the source files (using an editor) do not create any
links on the changes page, the index page or on any hashtag pages. See
_oddmu-notify_(1) for a way to add the necessary links to the changes page and
possibly to the index and hashtag pages.
A hashtag consists of a number sign ('#') followed by Unicode letters, numbers
or the underscore ('\_'). Thus, a hashtag ends with punctuation or whitespace.
The page names, titles and hashtags are loaded into memory when the server
starts. If you have a lot of pages, this takes a lot of memory.
starts. If you have a lot of pages, this takes a lot of memory. Oddmu watches
the working directory and any subdirectories for changes made to page files and
updates this cache when necessary.
Oddmu watches the working directory and any subdirectories for changes made
directly. Thus, in theory, it's not necessary to restart it after making such
changes.
You cannot edit uploaded files. If you upload a file called "hello.txt" and
attempt to edit it by using "/edit/hello.txt" you create a page with the name
"hello.txt.md" instead.
Uploaded files cannot be edited unless they end with ".md". If you upload a file
called "hello.txt" and attempt to edit it by using "/edit/hello.txt" you create
a page with the name "hello.txt.md" instead.
In order to delete uploaded files via the web, create an empty file and upload
it. In order to delete a wiki page, save an empty page.
Note that some HTML file names are special: they act as templates. See
_oddmu-templates_(5) for their names and their use.
_oddmu-templates_(5) for their names and their use. Oddmu watches the working
directory and any subdirectories for changes made to template files and reloads
them. There is no need to restart the server after making changes to the
templates.
# SEE ALSO
@@ -334,6 +335,11 @@ If you want to stop using Oddmu:
- _oddmu-export_(1), on how to export all the files as one big RSS file
And finally, if you don't have the man pages, you can still read the original
documents:
- _oddmu-man_(1), to get help even if you don't have the man pages installed
# AUTHORS
Maintained by Alex Schroeder <alex@gnu.org>.

81
man_cmd.go Normal file
View File

@@ -0,0 +1,81 @@
package main
import (
"context"
"embed"
"fmt"
"flag"
"io"
"os"
"slices"
"strings"
"github.com/google/subcommands"
)
type manCmd struct {
}
func (cmd *manCmd) SetFlags(f *flag.FlagSet) {
}
func (*manCmd) Name() string { return "man" }
func (*manCmd) Synopsis() string { return "show a manual page" }
func (*manCmd) Usage() string {
return `man <topic>:
Print a manual page on a topic. If no topic is given, the
available topics are listed. Substrings are possible.
`
}
// A filesystem with a read-only copy of the man pages at build time.
//
//go:embed man/*.txt
var manFiles embed.FS
func (cmd *manCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
topic := strings.Join(f.Args(), " ")
return manCli(os.Stdout, topic)
}
func manCli(w io.Writer, topic string) subcommands.ExitStatus {
entries, err := manFiles.ReadDir("man")
if err != nil {
fmt.Println("An error in the build resulted in unreadable manual pages:", err)
return subcommands.ExitFailure
}
if (topic == "") {
fmt.Println("Topics:")
names := []string{}
for _, entry := range entries {
names = append(names, entry.Name())
}
slices.Sort(names)
for _, name := range names {
fmt.Println(name)
}
return subcommands.ExitSuccess
}
var candidate string
for _, entry := range entries {
name := entry.Name()
if strings.Contains(name, topic) {
if candidate != "" {
fmt.Printf("The topic '%s' matches both %s and %s, maybe more.\n", topic, candidate, name)
fmt.Println("Please be more specific.")
return subcommands.ExitFailure
}
candidate = name
}
}
if candidate == "" {
fmt.Printf("No manual page matching topic '%s' found\n", topic)
return subcommands.ExitFailure
}
b, err := manFiles.ReadFile("man/" + candidate)
if err != nil {
return subcommands.ExitFailure
}
os.Stdout.Write(b)
return subcommands.ExitSuccess
}

View File

@@ -61,8 +61,8 @@ func TestManTemplates(t *testing.T) {
assert.Greater(t, count, 0, "no templates were found")
}
// Does oddmu-templates(5) mention all the templates?
func TestManTemplateAttributess(t *testing.T) {
// Does oddmu-templates(5) mention all the template attributes?
func TestManTemplateAttributes(t *testing.T) {
mfp := "man/oddmu-templates.5.txt"
b, err := os.ReadFile(mfp)
man := string(b)
@@ -106,7 +106,7 @@ func TestManActions(t *testing.T) {
wiki := string(b)
count := 0
// this doesn't match the root handler
re := regexp.MustCompile(`\.HandleFunc\("(/[a-z]+/)", makeHandler\([a-z]+Handler, (true|false)(, http\.Method(Get|Post))+\)\)`)
re := regexp.MustCompile(`mux\.HandleFunc\("(/[a-z]+/)", makeHandler\([a-z]+Handler, (true|false)(, http\.Method(Get|Post))+\)\)`)
for _, match := range re.FindAllStringSubmatch(wiki, -1) {
count++
var path string
@@ -122,6 +122,27 @@ func TestManActions(t *testing.T) {
assert.Contains(t, main, "\n- _/_", "root")
}
// Does oddmu(1) mention all the commands?
func TestManCommands(t *testing.T) {
b, err := os.ReadFile("man/oddmu.1.txt")
assert.NoError(t, err)
main := string(b)
b, err = os.ReadFile("wiki.go")
assert.NoError(t, err)
wiki := string(b)
count := 0
re := regexp.MustCompile(`subcommands\.Register\(&([a-z]+)Cmd`)
for _, match := range re.FindAllStringSubmatch(wiki, -1) {
count++
command := match[1]
ref := "_oddmu-" + command + "_(1)"
assert.Contains(t, main, ref, "link to the '%s' command", command)
}
assert.Greater(t, count, 0, "no commands were found")
// root handler is manual
assert.Contains(t, main, "\n- _/_", "root")
}
// Does the README link to all the man pages and all the Go source files,
// excluding the command and test files?
func TestReadme(t *testing.T) {

View File

@@ -77,7 +77,7 @@ func searchCli(w io.Writer, cmd *searchCmd, args []string) subcommands.ExitStatu
case cmd.files:
for _, p := range items {
name := filepath.FromSlash(p.Name) + ".md\n"
fmt.Fprintf(w, name)
fmt.Fprint(w, name)
}
default:
for _, p := range items {

View File

@@ -8,10 +8,13 @@ import (
"regexp"
)
// SitemapURL is a URL of the sitemap.
type SitemapURL struct {
// Loc is the actual location.
Loc string
}
// Sitemap is the sitemap itself, containing a list of URLs.
type Sitemap struct {
URL []*SitemapURL
}

View File

@@ -2,8 +2,8 @@ package main
import (
"context"
"fmt"
"flag"
"fmt"
"io"
"log"
"os"
@@ -12,7 +12,7 @@ import (
)
type sitemapCmd struct {
base string
base string
filter string
}
@@ -45,7 +45,7 @@ func (cmd *sitemapCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfac
// is important so that test code can ensure no other test running in parallel can interfere with the list of known
// pages (by adding or deleting pages).
func sitemapCli(w io.Writer, idx *indexStore, base, filter string) subcommands.ExitStatus {
loadTemplates()
initTemplates()
template := "sitemap.html"
t := templates.template[template]
if t == nil {

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="generator" content="Oddμ <https://src.alexschroeder.ch/oddmu.git/>"/>
<title>{{.Title}}</title>
<style>
html { max-width: 65ch; padding: 1ch; margin: auto; color: #111; background-color: #ffe }

View File

@@ -72,7 +72,7 @@ func staticCli(source, target string, jobs int, glob string, shrink, verbose, qu
index.RLock()
defer index.RUnlock()
loadLanguages()
loadTemplates()
initTemplates()
tasks := make(chan args, 10000)
results := make(chan error, jobs)
done := make(chan bool, jobs)

View File

@@ -1,44 +1,80 @@
package main
import (
"embed"
"html/template"
"io/fs"
"log"
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"sync"
)
// A filesystem with a read-only copy of the templates at build time.
//
//go:embed *.html
var templateDefaults embed.FS
// templateFiles are the various HTML template files used. These files must exist in the root directory for Oddmu
// to be able to generate HTML output. This always requires a template.
var templateFiles = []string{"edit.html", "add.html", "view.html", "preview.html",
"diff.html", "search.html", "static.html", "upload.html", "feed.html",
"sitemap.html"}
var templateFiles = []string{}
// templateStore controls access to map of parsed HTML templates. Make sure to lock and unlock as appropriate. See
// renderTemplate and loadTemplates.
// templateStore controls access to the map of parsed HTML templates. Make sure to lock and unlock as appropriate.
// See renderTemplate and loadTemplates.
type templateStore struct {
sync.RWMutex
// template is a map of parsed HTML templates. The key is their filepath name. By default, the map only contains
// top-level templates like "view.html". Subdirectories may contain their own templates which override the
// templates in the root directory. If so, they are filepaths like "dir/view.html".
// template is a map of parsed HTML templates. The key is their filepath name. By default, the map only
// contains top-level templates like "view.html". Subdirectories may contain their own templates which
// override the templates in the root directory. If so, they are filepaths like "dir/view.html". This is a
// map because we need to add and remove templates as time passes.
template map[string]*template.Template
}
var templates templateStore
// loadTemplates loads the templates. If templates have already been loaded, return immediately.
func loadTemplates() {
// initTemplates loads the templates and writes them to disk if they are missing. If templates have already been
// loaded, return immediately.
func initTemplates() {
if templates.template != nil {
return
}
templates.Lock()
defer templates.Unlock()
// walk the directory, load templates and add directories
templates.template = make(map[string]*template.Template)
// load the defaults and make a list of the default names
entries, err := templateDefaults.ReadDir(".")
if err != nil {
log.Println("An error in the build resulted in unreadable default templates:", err)
}
// if loading or parsing fails, continue as perhaps the files exist in the file-system
for _, entry := range entries {
name := entry.Name()
templateFiles = append(templateFiles, name)
b, err := fs.ReadFile(templateDefaults, name)
if err != nil {
log.Printf("Cannot read built-in default template %s: %s\n", name, err)
continue
}
t := template.New(name)
templates.template[name], err = t.Parse(string(b))
if err != nil {
log.Printf("Cannot parse built-in default template %s: %s\n", name, err)
}
_, err = os.Stat(name)
if err != nil {
err = os.WriteFile(name, b, 0644)
if err == nil {
log.Printf("Wrote built-in default template %s\n", name)
} else {
log.Printf("Cannot write built-in default template %s: %s\n", name, err)
}
}
}
// walk the directory, load templates and add directories
filepath.Walk(".", loadTemplate)
log.Println(len(templates.template), "templates loaded")
}
@@ -54,7 +90,6 @@ func loadTemplate(fp string, info fs.FileInfo, err error) error {
t, err := template.ParseFiles(fp)
if err != nil {
log.Println("Cannot parse template:", fp, err)
// ignore error
} else {
templates.template[fp] = t
}
@@ -92,7 +127,7 @@ func removeTemplate(fp string) {
// renderTemplate is the helper that is used to render the templates with data.
// A template in the same directory is preferred, if it exists.
func renderTemplate(w http.ResponseWriter, dir, tmpl string, data any) {
loadTemplates()
initTemplates()
base := tmpl + ".html"
templates.RLock()
defer templates.RUnlock()

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<meta name="generator" content="Oddμ <https://src.alexschroeder.ch/oddmu.git/>"/>
<title>{{.Title}}</title>
<style>
html { max-width: 70ch; padding: 1ch; margin: auto; color: #111; background-color: #ffe }

View File

@@ -230,6 +230,7 @@ func commands() {
subcommands.Register(&exportCmd{}, "")
subcommands.Register(&hashtagsCmd{}, "")
subcommands.Register(&feedCmd{}, "")
subcommands.Register(&manCmd{}, "")
subcommands.Register(&htmlCmd{}, "")
subcommands.Register(&listCmd{}, "")
subcommands.Register(&linksCmd{}, "")