1 Commits

Author SHA1 Message Date
Alex Schroeder
faf9198edd Add sitemap command and handler 2026-01-02 22:30:02 +01:00
38 changed files with 336 additions and 1140 deletions

View File

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

View File

@@ -1,8 +1,7 @@
SHELL=/bin/bash
PREFIX=${HOME}/.local
BINARIES=oddmu-linux-amd64.tar.gz oddmu-linux-arm64.tar.gz oddmu-darwin-amd64.tar.gz oddmu-windows-amd64.tar.gz
.PHONY: help build test run upload docs install priv clean dist dist-upload
.PHONY: help build test run upload docs install priv
help:
@echo Help for Oddmu
@@ -39,13 +38,10 @@ 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
go-critic check
gocritic check
# go install golang.org/x/tools/cmd/goimports@latest
fix:
goimports -w *.go
@@ -69,10 +65,7 @@ clean:
rm --force oddmu oddmu.exe oddmu-{linux,darwin,windows}-{amd64,arm64}{,.tar.gz}
cd man && make clean
dist-upload: $(BINARIES)
rsync -ai $^ sibirocobombus:alexschroeder.ch/wiki/oddmu/
dist: $(BINARIES)
dist: oddmu-linux-amd64.tar.gz oddmu-linux-arm64.tar.gz oddmu-darwin-amd64.tar.gz oddmu-windows-amd64.tar.gz
oddmu-linux-amd64: *.go
GOOS=linux GOARCH=amd64 go build -o $@

View File

@@ -65,11 +65,6 @@ 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):
@@ -225,13 +220,6 @@ 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. check copyright year in LICENSE
3. make docs
4. make docs
4. Make sure all files are checked in
5. make sure all files are checked in
5. Tag the release and push the tag to all remotes
6. tag the release and push the tag to the remote
6. cd man && make upload
7. make dist
8. make dist-upload
8. create a new release at https://github.com/kensanata/oddmu/releases
9. cd man && make upload
9. upload the four .tar.gz binaries to the GitHub release

11
feed.go
View File

@@ -16,9 +16,7 @@ import (
type dateSource int
const (
// ModTime means that the feed item date is based on the page file's last modification date.
ModTime dateSource = iota
// URL means that the feed item date is based on the ISO date contained in the page name.
URL
)
@@ -69,12 +67,11 @@ func feed(p *Page, ti time.Time, from, n int, source dateSource) *Feed {
feed.Date = ti.Format(time.RFC1123Z)
feed.From = from
feed.N = n
switch {
case n == 0:
if n == 0 {
feed.Complete = true
case from > n:
} else if from > n {
feed.Prev = from - n
default:
} else {
year, err := p.BlogYear()
if err == nil && p.ArchiveExists(year+1) {
feed.PrevYear = year + 1
@@ -153,7 +150,7 @@ func (p *Page) Date(source dateSource) (time.Time, error) {
return p.ModTime()
}
// BlogYear returns the current year if the page name is "index". If the page name is a number such as "2026" then
// BLogYear returns the current year if the page name is "index". If the page name is a number such as "2026" then
// this is parsed as an integer and returned.
func (p *Page) BlogYear() (int, error) {
name := path.Base(p.Name)

View File

@@ -11,7 +11,6 @@
<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
}
initTemplates()
loadTemplates()
templates.RLock()
defer templates.RUnlock()
err = templates.template["feed.html"].Execute(w, f)

41
go.mod
View File

@@ -1,40 +1,41 @@
module src.alexschroeder.ch/oddmu
go 1.25
go 1.22
toolchain go1.22.3
require (
github.com/disintegration/imaging v1.6.2
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/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/google/subcommands v1.2.0
github.com/hexops/gotextdiff v1.0.3
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microcosm-cc/bluemonday v1.0.26
github.com/muesli/reflow v0.3.0
github.com/pemistahl/lingua-go v1.4.0
github.com/sergi/go-diff v1.4.0
github.com/sergi/go-diff v1.3.1
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
)
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.9.1 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // 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.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
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
gopkg.in/yaml.v3 v3.0.1 // indirect
)

78
go.sum
View File

@@ -1,30 +1,26 @@
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.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/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/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=
@@ -37,10 +33,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.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/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/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=
@@ -49,30 +45,32 @@ 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.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/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/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.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=
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=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-APACHE" "5" "2026-01-29"
.TH "ODDMU-APACHE" "5" "2025-07-16"
.PP
.SH NAME
.PP
@@ -48,7 +48,7 @@ ServerAdmin alex@alexschroeder\&.ch
<VirtualHost *:443>
ServerName transjovian\&.org
SSLEngine on
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)?$"
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))?$"
"http://localhost:8080/$1"
</VirtualHost>
.fi
@@ -126,13 +126,13 @@ ServerAdmin alex@alexschroeder\&.ch
ServerName transjovian\&.org
ProxyPassMatch "^/((view|diff|search|archive)/(\&.*))?$"
"http://localhost:8080/$1"
RedirectMatch "^/((edit|save|add|append|upload|drop)/(\&.*)|sitemap.xml)?$"
RedirectMatch "^/((edit|save|add|append|upload|drop)/(\&.*))?$"
"https://transjovian\&.org/$1"
</VirtualHost>
<VirtualHost *:443>
ServerName transjovian\&.org
SSLEngine on
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)?$"
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))?$"
"http://localhost:8080/$1"
</VirtualHost>
.fi
@@ -170,7 +170,7 @@ In that case, you need to use the ProxyPassMatch directive.\&
.PP
.nf
.RS 4
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)?$"
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))?$"
"unix:/run/oddmu/oddmu\&.sock|http://localhost/$1"
.fi
.RE
@@ -189,7 +189,7 @@ A workaround is to add the redirect manually and drop the question-mark:
.nf
.RS 4
RedirectMatch "^/$" "/view/index"
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)$"
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))$"
"unix:/run/oddmu/oddmu\&.sock|http://localhost/$1"
.fi
.RE
@@ -248,74 +248,12 @@ to your "<VirtualHost *:443>" section:
.fi
.RE
.PP
.SS Actual usernames and passwords for authentication
.PP
On a community server where the users have accounts, wiki editing can be limited
to the system'\&s users.\&
.PP
In order to do this, install the \fBmod-authnz-external\fR module for Apache and the
\fBpwauth\fR binary.\& The module allows the password checking normally done inside
Apache to be done by an separate external program running outside of Apache.\&
.PP
Here'\&s an example configuration:
.PP
.nf
.RS 4
AddExternalAuth pwauth /usr/sbin/pwauth
SetExternalAuthMethod pwauth pipe
<LocationMatch "^/(edit|save|add|append|upload|drop)/">
AuthType Basic
AuthName "Password Required"
AuthBasicProvider external
AuthExternal pwauth
Require valid-user
</LocationMatch>
.fi
.RE
.PP
.SS Different logins for different access rights
.PP
What if you have a site with various subdirectories and each subdirectory is for
a different group of friends?\& You can set this up using your webserver.\& One way
to do this is to require specific usernames (which must have a password in the
password file mentioned above.\&
.PP
This requires a valid login by the user "alex" or "berta":
.PP
.nf
.RS 4
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
Require user alex berta
</LocationMatch>
.fi
.RE
.PP
.SS Private wikis
.PP
Based on the above, you can prevent people from \fIreading\fR the wiki.\& The location
must cover all the URLs in order to protect everything.\&
.PP
.nf
.RS 4
<Location />
AuthType Basic
AuthName "Password Required"
AuthUserFile /home/oddmu/\&.htpasswd
Require valid-user
</Location>
.fi
.RE
.PP
.SS Subdirectories as separate sites
.PP
The way Oddmu handles subdirectories is that all files and directories are
visible, except for "hidden" files and directories (whose name starts with a
period).\& Specifically, do not rely on Apache to hide locations in subdirectories
from public view.\& Search reveals the existence of these pages and produces an
extract, even if users cannot follow the links.\& The Sitemap lists all pages,
including subdirectories.\& Archive links pack all the subdirectories, including
locations you may have hidden from view using Apache.\&
extract, even if users cannot follow the links.\& Archive links pack all the
subdirectories, including locations you may have hidden from view using Apache.\&
.PP
If you to treat subdirectories as separate sites, you need to set the
environment variable ODDMU_FILTER to a regular expression matching the those
@@ -400,13 +338,46 @@ In this case, "/css/oddmu-2023.\&css" would be the name of your stylesheet.\& If
your document root is "/home/oddmu", then the filename of your stylesheet would
have to be "/home/oddmu/css/oddmu-2023.\&css" for this to work.\&
.PP
.SS Different logins for different access rights
.PP
What if you have a site with various subdirectories and each subdirectory is for
a different group of friends?\& You can set this up using your webserver.\& One way
to do this is to require specific usernames (which must have a password in the
password file mentioned above.\&
.PP
This requires a valid login by the user "alex" or "berta":
.PP
.nf
.RS 4
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
Require user alex berta
</LocationMatch>
.fi
.RE
.PP
.SS Private wikis
.PP
Based on the above, you can prevent people from \fIreading\fR the wiki.\& The location
must cover all the URLs in order to protect everything.\&
.PP
.nf
.RS 4
<Location />
AuthType Basic
AuthName "Password Required"
AuthUserFile /home/oddmu/\&.htpasswd
Require valid-user
</Location>
.fi
.RE
.PP
.SS Virtual hosting
.PP
Virtual hosting in this context means that the program serves two different
sites for two different domains from the same machine.\& Oddmu doesn'\&t support
that, but your webserver does.\& Therefore, start an Oddmu instance for every
domain name, each listening on a different port.\& Then set up your web server
such that every domain proxies for a different Oddmu instance.\&
such that ever domain acts as a reverse proxy to a different Oddmu instance.\&
.PP
.SH SEE ALSO
.PP

View File

@@ -209,61 +209,6 @@ to your "<VirtualHost \*:443>" section:
</LocationMatch>
```
## Actual usernames and passwords for authentication
On a community server where the users have accounts, wiki editing can be limited
to the system's users.
In order to do this, install the *mod-authnz-external* module for Apache and the
*pwauth* binary. The module allows the password checking normally done inside
Apache to be done by an separate external program running outside of Apache.
Here's an example configuration:
```
AddExternalAuth pwauth /usr/sbin/pwauth
SetExternalAuthMethod pwauth pipe
<LocationMatch "^/(edit|save|add|append|upload|drop)/">
AuthType Basic
AuthName "Password Required"
AuthBasicProvider external
AuthExternal pwauth
Require valid-user
</LocationMatch>
```
## Different logins for different access rights
What if you have a site with various subdirectories and each subdirectory is for
a different group of friends? You can set this up using your webserver. One way
to do this is to require specific usernames (which must have a password in the
password file mentioned above.
This requires a valid login by the user "alex" or "berta":
```
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
Require user alex berta
</LocationMatch>
```
## Private wikis
Based on the above, you can prevent people from _reading_ the wiki. The location
must cover all the URLs in order to protect everything.
```
<Location />
AuthType Basic
AuthName "Password Required"
AuthUserFile /home/oddmu/.htpasswd
Require valid-user
</Location>
```
## Subdirectories as separate sites
The way Oddmu handles subdirectories is that all files and directories are
visible, except for "hidden" files and directories (whose name starts with a
period). Specifically, do not rely on Apache to hide locations in subdirectories
@@ -347,13 +292,42 @@ In this case, "/css/oddmu-2023.css" would be the name of your stylesheet. If
your document root is "/home/oddmu", then the filename of your stylesheet would
have to be "/home/oddmu/css/oddmu-2023.css" for this to work.
## Different logins for different access rights
What if you have a site with various subdirectories and each subdirectory is for
a different group of friends? You can set this up using your webserver. One way
to do this is to require specific usernames (which must have a password in the
password file mentioned above.
This requires a valid login by the user "alex" or "berta":
```
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
Require user alex berta
</LocationMatch>
```
## Private wikis
Based on the above, you can prevent people from _reading_ the wiki. The location
must cover all the URLs in order to protect everything.
```
<Location />
AuthType Basic
AuthName "Password Required"
AuthUserFile /home/oddmu/.htpasswd
Require valid-user
</Location>
```
## Virtual hosting
Virtual hosting in this context means that the program serves two different
sites for two different domains from the same machine. Oddmu doesn't support
that, but your webserver does. Therefore, start an Oddmu instance for every
domain name, each listening on a different port. Then set up your web server
such that every domain proxies for a different Oddmu instance.
such that ever domain acts as a reverse proxy to a different Oddmu instance.
# SEE ALSO

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-EXPORT" "1" "2026-01-03"
.TH "ODDMU-EXPORT" "1" "2025-08-31"
.PP
.SH NAME
.PP
@@ -31,8 +31,7 @@ XML preamble is printed and appropriate escaping rules are used.\&
.PP
.SH FILES
.PP
By default, the export uses the feed template ("feed.\&html") in the current
directory.\&
By default, the export uses the \fB\fRfeed.\&html\fB\fR template in the current directory.\&
.PP
.SH EXAMPLES
.PP

View File

@@ -24,8 +24,7 @@ XML preamble is printed and appropriate escaping rules are used.
# FILES
By default, the export uses the feed template ("feed.html") in the current
directory.
By default, the export uses the **feed.html** template in the current directory.
# EXAMPLES

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-FILTER" "7" "2026-01-03"
.TH "ODDMU-FILTER" "7" "2024-09-30"
.PP
.SH NAME
.PP
@@ -13,13 +13,13 @@ oddmu-filter - keeping subdirectories separate
.PP
.SH DESCRIPTION
.PP
There are actions such as producing the sitemap, searching and archiving that
act on multiple pages, not just a single page.\& These actions walk the directory
tree, including all subdirectories.\& In some cases, this is not desirable.\&
There are actions such as searching and archiving that act on multiple pages,
not just a single page.\& These actions walk the directory tree, including all
subdirectories.\& In some cases, this is not desirable.\&
.PP
Sometimes, subdirectories are separate sites, like the sites of other projects
or different people.\& Depending on how you think about it, you might not want to
include those "sites" in searches, sitemaps or archives of the whole site.\&
include those "sites" in searches or archives of the whole site.\&
.PP
Since directory tree actions always start in the directory the visitor is
currently looking at, directory tree actions starting in a "separate site"

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-HTML" "1" "2026-01-03"
.TH "ODDMU-HTML" "1" "2025-04-05"
.PP
.SH NAME
.PP
@@ -13,7 +13,7 @@ oddmu-html - render Oddmu page HTML
.PP
.SH SYNOPSIS
.PP
\fBoddmu html\fR [\fB-template\fR \fItemplate-name\fR] \fIpage-name\fR
\fBoddmu html\fR [\fB\fR-template\fB\fR \fItemplate-name\fR] \fIpage-name\fR
.PP
.SH DESCRIPTION
.PP
@@ -23,7 +23,7 @@ name if you want to read Markdown from \fBstdin\fR.\&
.PP
.SH OPTIONS
.PP
\fB-template\fR \fItemplate-name\fR
\fB\fR-template\fB\fR \fItemplate-name\fR
.RS 4
Use the given template to render the page.\& Without this, the HTML lacks
html and body tags.\& The only two options that make sense are "view.\&html"

View File

@@ -6,7 +6,7 @@ oddmu-html - render Oddmu page HTML
# SYNOPSIS
*oddmu html* [*-template* _template-name_] _page-name_
*oddmu html* [**-template** _template-name_] _page-name_
# DESCRIPTION
@@ -16,7 +16,7 @@ name if you want to read Markdown from *stdin*.
# OPTIONS
*-template* _template-name_
**-template** _template-name_
Use the given template to render the page. Without this, the HTML lacks
html and body tags. The only two options that make sense are "view.html"
and "static.html".

View File

@@ -1,39 +0,0 @@
.\" 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>.\&

View File

@@ -1,30 +0,0 @@
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-NGINX" "5" "2026-01-03"
.TH "ODDMU-NGINX" "5" "2025-07-16"
.PP
.SH NAME
.PP
@@ -27,7 +27,7 @@ section.\& Add a new \fIlocation\fR section after the existing \fIlocation\fR se
.PP
.nf
.RS 4
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|sitemap|archive)/ {
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|archive)/ {
proxy_pass http://localhost:8080;
}
.fi
@@ -97,7 +97,7 @@ server configuration.\& On a Debian system, that'\&d be in
.PP
.nf
.RS 4
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|sitemap|archive)/ {
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|archive)/ {
proxy_pass http://unix:/run/oddmu/oddmu\&.sock:;
}
.fi

View File

@@ -1,11 +1,11 @@
.\" Generated by scdoc 1.11.3
.\" Generated by scdoc 1.11.4
.\" 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-RELEASES" "7" "2026-02-11"
.TH "ODDMU-RELEASES" "7" "2026-01-01"
.PP
.SH NAME
.PP
@@ -15,18 +15,9 @@ oddmu-releases - what'\&s new?\&
.PP
This page lists user-visible features and template changes to consider.\&
.PP
.SS 1.21 (unreleased)
.SS 1.20 (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)
for more.\&
Add -shrink and -glob options to the \fIstatic\fR subcommand.\&
.PP
Some tools were used to check the code (goimports, golint, gocritic).\&
Unfortunately, the resulting changes necessitates a change in the templates
@@ -49,8 +40,8 @@ faster than any of the feeds for hashtag pages, presumably, an extra features
was added: on the first and on the last page of the feed, a link to the next or
the previous year is added, if such a page exists.\& This works if at beginning of
every year, you move all the entries on to a dedicated year page.\& You need to
add the necessary links to the "feed.\&html" template.\& See \fIoddmu-templates\fR(5)
for more.\&
add the necessary links to the feed template ("feed.\&html").\& See
\fIoddmu-templates\fR(5) for more.\&
.PP
Example:
.PP
@@ -71,11 +62,6 @@ Example:
.fi
.RE
.PP
Add \fIsitemap\fR subcommand and handler.\& See \fIoddmu-sitemap\fR(1) for more.\& If you
want to make it available for search engines and the like, you most likely have
to add it to your proxy configuration.\& See \fIoddmu-apache\fR(5) or \fIoddmu-nginx\fR(5)
for more.\&
.PP
.SS 1.19 (2025)
.PP
Add \fIfeed\fR subcommand.\& This produces a "complete" feed.\&
@@ -85,8 +71,8 @@ Add feed pagination for the \fIfeed\fR action.\& This produces a "paginated" fee
See RFC 5005 for more information.\&
.PP
If you like the idea of feed pagination (not a given since that also helps bots
scrape your site!\&) you need to add the necessary links to the "feed.\&html"
template.\& See \fIoddmu-templates\fR(5) for more.\&
scrape your site!\&) you need to add the necessary links to the feed template
("feed.\&html").\& See \fIoddmu-templates\fR(5) for more.\&
.PP
Example, adding the feed history namespace:
.PP
@@ -118,15 +104,16 @@ In an effort to remove features that can be handled by the web server, the
for a better solution.\&
.PP
You probably need to remove a sentence linking to the list action from the
"upload.\&html" template.\&
upload template ("upload.\&html").\&
.PP
.SS 1.17 (2025)
.PP
You need to update the "upload.\&html" template.\& Many things have changed!\& See
\fIoddmu-templates\fR(5) for more.\&
You need to update the upload template ("upload.\&html").\& Many things have
changed!\& See \fIoddmu-templates\fR(5) for more.\&
.PP
You probably want to ensure that the upload link on the "view.\&html" template and
others, if you added it, has a \fIfilename\fR and \fIpagename\fR parameters.\&
You probably want to ensure that the upload link on the view template
("view.\&html") and others, if you added it, has a \fIfilename\fR and \fIpagename\fR
parameters.\&
.PP
Example:
.PP
@@ -136,8 +123,8 @@ Example:
.fi
.RE
.PP
You need to change {{.\&Name}} to {{.\&Path}} when it is used in URLs, in the
"list.\&html" template.\& If you don'\&t do this, file deleting and rename may not
You need to change {{.\&Name}} to {{.\&Path}} when it is used in URLs, in the list
template ("list.\&html").\& If you don'\&t do this, file deleting and rename may not
work on files containing a comma, a semicolon, a questionmark or a hash
character.\& This fix was necessary because URLs for files containing a
questionmark or a hash character would end the path at this character and treat
@@ -192,7 +179,7 @@ together with appropriate permission checks.\&
See \fIoddmu-apache\fR(5) or \fIoddmu-nginx\fR(5) for example.\&
.PP
In addition to that, you might want a link to the \fIlist\fR action from one of the
existing templates.\& For example, from the "upload.\&html" template:
existing templates.\& For example, from upload.\&html:
.PP
.nf
.RS 4
@@ -229,9 +216,10 @@ These are the quotation marks currently supported: '\&foo'\& "foo" foo
“foo” „foo“ ”foo” «foo» »foo« foo foo 「foo」 「foo」 『foo』 any such
quoted text is searched as-is, including whitespace.\&
.PP
Add loading="lazy" for images in the search template.\& If you want to take
advantage of this, you'\&ll need to adapt your "search.\&html" template accordingly.\&
Use like this, for example:
Add loading="lazy" for images in search.\&html
.PP
If you want to take advantage of this, you'\&ll need to adapt your "search.\&html"
template accordingly.\& Use like this, for example:
.PP
.nf
.RS 4
@@ -291,10 +279,10 @@ If you want to take advantage of this, you'\&ll need to adapt your templates
accordingly.\& The "preview.\&html" template is a mix of "view.\&html" and
"edit.\&html".\&
.PP
There is an optional change to make to copies of the "upload.\&html" template if
you upload multiple images at a time.\& Instead of showing just the link to the
last upload, you can now show the link (and the images or links, if you want to)
to all the files uploaded.\& Use like this, for example:
There is an optional change to make to copies of \fIupload.\&html\fR if you upload
multiple images at a time.\& Instead of showing just the link to the last upload,
you can now show the link (and the images or links, if you want to) to all the
files uploaded.\& Use like this, for example:
.PP
.nf
.RS 4
@@ -304,9 +292,9 @@ Links:<tt>{{range \&.Actual}}<br>![]({{\&.}}){{end}}</tt>
.PP
.SS 1.9 (2024)
.PP
There is a change to make to copies of the "upload.\&html" template if
subdirectories are being used.\& The \fILast\fR property no longer contains the
directory.\& It has to be added to the template as follows:
There is a change to make to copies of \fIupload.\&html\fR if subdirectories are being
used.\& The \fILast\fR property no longer contains the directory.\& It has to be added
to the template as follows:
.PP
.nf
.RS 4
@@ -334,7 +322,7 @@ The upload template can use the \fIToday\fR property.\&
The upload template comes with JavaScript that allows users to paste images or
drag and drop files.\&
.PP
The upload template changed the id for the filename field from "text" to "name".\&
The upload template changed the id for the filename field from `text` to `name`.\&
.PP
The source repository now comes with example templates.\&
.PP
@@ -344,7 +332,7 @@ No user-visible changes.\& Documentation and code comments got better.\&
.PP
.SS 1.7 (2024)
.PP
Allow upload of multiple files.\& This requires an update to the "upload.\&html"
Allow upload of multiple files.\& This requires an update to the \fIupload.\&html\fR
template: Add the \fImultiple\fR attribute to the file input element and change the
label from "file" to "files".\&
.PP

View File

@@ -8,18 +8,9 @@ oddmu-releases - what's new?
This page lists user-visible features and template changes to consider.
## 1.21 (unreleased)
## 1.20 (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)
for more.
Add -shrink and -glob options to the _static_ subcommand.
Some tools were used to check the code (goimports, golint, gocritic).
Unfortunately, the resulting changes necessitates a change in the templates
@@ -40,8 +31,8 @@ faster than any of the feeds for hashtag pages, presumably, an extra features
was added: on the first and on the last page of the feed, a link to the next or
the previous year is added, if such a page exists. This works if at beginning of
every year, you move all the entries on to a dedicated year page. You need to
add the necessary links to the "feed.html" template. See _oddmu-templates_(5)
for more.
add the necessary links to the feed template ("feed.html"). See
_oddmu-templates_(5) for more.
Example:
@@ -60,11 +51,6 @@ Example:
```
Add _sitemap_ subcommand and handler. See _oddmu-sitemap_(1) for more. If you
want to make it available for search engines and the like, you most likely have
to add it to your proxy configuration. See _oddmu-apache_(5) or _oddmu-nginx_(5)
for more.
## 1.19 (2025)
Add _feed_ subcommand. This produces a "complete" feed.
@@ -74,8 +60,8 @@ Add feed pagination for the _feed_ action. This produces a "paginated" feed.
See RFC 5005 for more information.
If you like the idea of feed pagination (not a given since that also helps bots
scrape your site!) you need to add the necessary links to the "feed.html"
template. See _oddmu-templates_(5) for more.
scrape your site!) you need to add the necessary links to the feed template
("feed.html"). See _oddmu-templates_(5) for more.
Example, adding the feed history namespace:
@@ -105,15 +91,16 @@ _list_, _delete_ and _rename_ actions were removed again. See _oddmu-webdav_(5)
for a better solution.
You probably need to remove a sentence linking to the list action from the
"upload.html" template.
upload template ("upload.html").
## 1.17 (2025)
You need to update the "upload.html" template. Many things have changed! See
_oddmu-templates_(5) for more.
You need to update the upload template ("upload.html"). Many things have
changed! See _oddmu-templates_(5) for more.
You probably want to ensure that the upload link on the "view.html" template and
others, if you added it, has a _filename_ and _pagename_ parameters.
You probably want to ensure that the upload link on the view template
("view.html") and others, if you added it, has a _filename_ and _pagename_
parameters.
Example:
@@ -121,8 +108,8 @@ Example:
<a href="/upload/{{.Dir}}?filename={{.Base}}-1.jpg&pagename={{.Base}}">Upload</a>
```
You need to change {{.Name}} to {{.Path}} when it is used in URLs, in the
"list.html" template. If you don't do this, file deleting and rename may not
You need to change {{.Name}} to {{.Path}} when it is used in URLs, in the list
template ("list.html"). If you don't do this, file deleting and rename may not
work on files containing a comma, a semicolon, a questionmark or a hash
character. This fix was necessary because URLs for files containing a
questionmark or a hash character would end the path at this character and treat
@@ -177,7 +164,7 @@ together with appropriate permission checks.
See _oddmu-apache_(5) or _oddmu-nginx_(5) for example.
In addition to that, you might want a link to the _list_ action from one of the
existing templates. For example, from the "upload.html" template:
existing templates. For example, from upload.html:
```
<p>You can rename and delete files <a href="/list/{{.Dir}}">from the file list</a>.
@@ -210,9 +197,10 @@ These are the quotation marks currently supported: 'foo' "foo" foo foo
“foo” „foo“ ”foo” «foo» »foo« foo foo 「foo」 「foo」 『foo』 any such
quoted text is searched as-is, including whitespace.
Add loading="lazy" for images in the search template. If you want to take
advantage of this, you'll need to adapt your "search.html" template accordingly.
Use like this, for example:
Add loading="lazy" for images in search.html
If you want to take advantage of this, you'll need to adapt your "search.html"
template accordingly. Use like this, for example:
```
{{range .Items}}
@@ -264,10 +252,10 @@ If you want to take advantage of this, you'll need to adapt your templates
accordingly. The "preview.html" template is a mix of "view.html" and
"edit.html".
There is an optional change to make to copies of the "upload.html" template if
you upload multiple images at a time. Instead of showing just the link to the
last upload, you can now show the link (and the images or links, if you want to)
to all the files uploaded. Use like this, for example:
There is an optional change to make to copies of _upload.html_ if you upload
multiple images at a time. Instead of showing just the link to the last upload,
you can now show the link (and the images or links, if you want to) to all the
files uploaded. Use like this, for example:
```
Links:<tt>{{range .Actual}}<br>![]({{.}}){{end}}</tt>
@@ -275,9 +263,9 @@ Links:<tt>{{range .Actual}}<br>![]({{.}}){{end}}</tt>
## 1.9 (2024)
There is a change to make to copies of the "upload.html" template if
subdirectories are being used. The _Last_ property no longer contains the
directory. It has to be added to the template as follows:
There is a change to make to copies of _upload.html_ if subdirectories are being
used. The _Last_ property no longer contains the directory. It has to be added
to the template as follows:
```
{{if ne .Last ""}}
@@ -301,7 +289,7 @@ The upload template can use the _Today_ property.
The upload template comes with JavaScript that allows users to paste images or
drag and drop files.
The upload template changed the id for the filename field from "text" to "name".
The upload template changed the id for the filename field from `text` to `name`.
The source repository now comes with example templates.
@@ -311,7 +299,7 @@ No user-visible changes. Documentation and code comments got better.
## 1.7 (2024)
Allow upload of multiple files. This requires an update to the "upload.html"
Allow upload of multiple files. This requires an update to the _upload.html_
template: Add the _multiple_ attribute to the file input element and change the
label from "file" to "files".

View File

@@ -1,49 +0,0 @@
.\" 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-SITEMAP" "1" "2026-01-03"
.PP
.SH NAME
.PP
oddmu-sitemap - print static sitemap.\&xml
.PP
.SH SYNOPSIS
.PP
\fBoddmu sitemap\fR [\fB-base\fR \fIURL\fR] [\fB-filter\fR \fIregexp\fR]
.PP
.SH DESCRIPTION
.PP
The "sitemap" subcommand prints the list of all pages in Sitemap format.\& Oddmu
already serves the sitemap at the URL "/sitemap.\&xml" but if you'\&d prefer to
provide a static file, use this command and redirect the output to a file called
"sitemap.\&xml" in your document root at regular intervals.\&
.PP
If you do this, don'\&t proxy the "/sitemap" URL in the web server configuration.\&
.PP
Your "robots.\&txt" file, if you have one, should point at the sitemap you
provide.\&
.PP
.SH OPTIONS
.PP
\fB-base\fR \fIURL\fR
.RS 4
The base URL is something like "https://example.\&org/view/".\&
.RE
\fB-filter\fR \fIregexp\fR
.RS 4
A regular expression matching the pages to exclude from the sitemap.\&
This emulates the effect of the ODDMU_FILTER environment variable.\&
.PP
.RE
.SH SEE ALSO
.PP
\fIoddmu\fR(1), \fIoddmu-filter\fR(7), \fIoddmu-apache\fR(1), \fIoddmu-nginx\fR(1),
https://www.\&sitemaps.\&org/
.PP
.SH AUTHORS
.PP
Maintained by Alex Schroeder <alex@gnu.\&org>.\&

View File

@@ -6,7 +6,7 @@ oddmu-sitemap - print static sitemap.xml
# SYNOPSIS
*oddmu sitemap* [*-base* _URL_] [*-filter* _regexp_]
*oddmu sitemap* [-base URL]
# DESCRIPTION

View File

@@ -1,11 +1,11 @@
.\" Generated by scdoc 1.11.3
.\" Generated by scdoc 1.11.4
.\" 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-STATIC" "1" "2026-02-06"
.TH "ODDMU-STATIC" "1" "2025-12-05"
.PP
.SH NAME
.PP
@@ -13,7 +13,7 @@ oddmu-static - create a static copy of the site
.PP
.SH SYNOPSIS
.PP
\fBoddmu static\fR [\fB-jobs\fR \fIn\fR] [\fB-glob\fR \fIpattern\fR] [\fB-shrink\fR] \fIdir-name\fR
\fBoddmu static\fR [\fB\fR-jobs\fB\fR \fIn\fR] [\fB\fR-glob\fB\fR \fIpattern\fR] [\fB\fR-shrink\fB\fR] \fIdir-name\fR
.PP
.SH DESCRIPTION
.PP
@@ -32,7 +32,7 @@ no feed items are found, no feed is written.\& The feed is limited to the ten mo
recent items.\&
.PP
Hidden files and directories (starting with a ".\&") and backup files (ending with
a "~") are skipped.\&
a "\(ti") are skipped.\&
.PP
All other files are \fIhard linked\fR.\& This is done to save space: on a typical blog
the images take a lot more space than the text.\& On my blog in 2023 I had 2.\&62
@@ -53,18 +53,18 @@ original, too.\&
.PP
.SH OPTIONS
.PP
\fB-jobs\fR \fIn\fR
\fB\fR-jobs\fB\fR \fIn\fR
.RS 4
By default, two jobs are used to process the files.\& If your machine has
more cores, you can increase the number of jobs.\&
.PP
.RE
\fB-glob\fR \fIpattern\fR
\fB\fR-glob\fB\fR \fIpattern\fR
.RS 4
By default, all files are used for the static export.\& You can limit the
files used by providing a shell file name pattern.\& A "*" matches any
number of characters; a "?\&" matches exactly one character; "[a-z]"
matches a character listed, including ranges; "[^a-z]" matches a
matches a character listed, including ranges; "[\(haa-z]" matches a
character not listed, including ranges; "\e" a backslash escapes the
following character.\& You must use quotes around the pattern if you are
using a shell as the shell would otherwise expand the pattern, resulting
@@ -72,12 +72,12 @@ in the error "Exactly one target directory is required".\&
.PP
.PP
.RE
\fB-shrink\fR
\fB\fR-shrink\fB\fR
.RS 4
By default, images are linked or copied.\& With this option, JPEG, PNG and
WebP files are scaled down if more than 800 pixels wide and the quality
is set to 30% for JPEG and WebP files.\& This is \fIbad quality\fR but the
result is that these image files are very small.\&
is set to 10% for JPEG and WebP files.\& This is \fIvery bad quality\fR but
the result is that these image files are very small.\&
.PP
.RE
.SH EXAMPLES

View File

@@ -6,7 +6,7 @@ oddmu-static - create a static copy of the site
# SYNOPSIS
*oddmu static* [*-jobs* _n_] [*-glob* _pattern_] [*-shrink*] _dir-name_
*oddmu static* [**-jobs** _n_] [**-glob** _pattern_] [**-shrink**] _dir-name_
# DESCRIPTION
@@ -46,11 +46,11 @@ original, too.
# OPTIONS
*-jobs* _n_
**-jobs** _n_
By default, two jobs are used to process the files. If your machine has
more cores, you can increase the number of jobs.
*-glob* _pattern_
**-glob** _pattern_
By default, all files are used for the static export. You can limit the
files used by providing a shell file name pattern. A "\*" matches any
number of characters; a "?" matches exactly one character; "[a-z]"
@@ -61,11 +61,11 @@ original, too.
in the error "Exactly one target directory is required".
*-shrink*
**-shrink**
By default, images are linked or copied. With this option, JPEG, PNG and
WebP files are scaled down if more than 800 pixels wide and the quality
is set to 30% for JPEG and WebP files. This is _bad quality_ but the
result is that these image files are very small.
is set to 10% for JPEG and WebP files. This is _very bad quality_ but
the result is that these image files are very small.
# EXAMPLES

View File

@@ -1,11 +1,11 @@
.\" Generated by scdoc 1.11.3
.\" Generated by scdoc 1.11.4
.\" 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-TEMPLATES" "5" "2026-01-03" "File Formats Manual"
.TH "ODDMU-TEMPLATES" "5" "2026-01-01" "File Formats Manual"
.PP
.SH NAME
.PP
@@ -31,12 +31,12 @@ placeholders.\&
.IP \(bu 4
\fIfeed.\&html\fR uses a \fIfeed\fR
.IP \(bu 4
\fIlist.\&html\fR uses a \fIlist\fR
.IP \(bu 4
\fIpreview.\&html\fR uses a \fIpage\fR
.IP \(bu 4
\fIsearch.\&html\fR uses a \fIsearch\fR
.IP \(bu 4
\fIsitemap.\&html\fR uses a \fIsitemap\fR
.IP \(bu 4
\fIstatic.\&html\fR uses a \fIpage\fR
.IP \(bu 4
\fIupload.\&html\fR uses an \fIupload\fR
@@ -172,6 +172,32 @@ explanation.\& The next year is one lower than the year currently shown (if on a
year page) or the current year (if looking at the index).\& If it isn'\&t set, it'\&s
value is 0.\&
.PP
.SS List
.PP
The list contains a directory name and an array of files.\&
.PP
\fI{{.\&Dir}}\fR is the directory name that is being listed, percent-encoded.\&
.PP
\fI{{.\&Files}}\fR is the array of files.\& To refer to them, you need to use a \fI{{range
Files}}\fR\fI{{end}}\fR construct.\&
.PP
Each file has the following attributes:
.PP
\fI{{.\&Name}}\fR is the filename.\& The ".\&md" suffix for Markdown files is part of the
name (unlike page names).\&
.PP
\fI{{.\&Path}}\fR is the page name, percent-encoded.\&
.PP
\fI{{.\&Title}}\fR is the page title, if the file in question is a Markdown file.\&
.PP
\fI{{.\&IsDir}}\fR is a boolean used to indicate that this file is a directory.\&
.PP
\fI{{.\&IsUp}}\fR is a boolean used to indicate the entry for the parent directory
(the first file in the array, unless the directory being listed is the top
directory).\& The filename of this file is ".\&.\&".\&
.PP
\fI{{.\&Date}}\fR is the last modification date of the file.\&
.PP
.SS Search
.PP
\fI{{.\&Query}}\fR is the query string.\&
@@ -210,16 +236,6 @@ are only listed if a search term matches.\&
\fI{{.\&Html}}\fR the image alt-text with a bold tag used to highlight the first
search term that matched.\&
.PP
.SS Sitemap
.PP
The sitemap contains a list of URLs, each with its location:
.PP
\fI{{.\&URL}}\fR is the list of URLs.\&
.PP
Each URL has the following attributes:
.PP
\fI{{.\&Loc}}\fR with the actual page URL.\&
.PP
.SS Upload
.PP
\fI{{.\&Dir}}\fR is the directory where the uploaded file ends up, based on the URL

View File

@@ -1,443 +0,0 @@
.\" 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" "1" "2026-02-11"
.PP
.SH NAME
.PP
oddmu - a wiki server
.PP
Oddmu is sometimes written Oddμ because μ is the letter mu.\&
.PP
.SH SYNOPSIS
.PP
\fBoddmu\fR
.PP
\fBoddmu\fR \fIsubcommand\fR [\fIarguments\fR.\&.\&.\&]
.PP
.SH DESCRIPTION
.PP
Oddmu can be used as a static site generator, turning Markdown files into HTML
files, or it can be used as a public or a private wiki server.\& If it runs as a
public wiki server, a regular webserver should be used as reverse proxy.\&
.PP
Run Oddmu without any arguments to serve the current working directory as a wiki
on port 8080.\& Point your browser to http://localhost:8080/ to use it.\& This
redirects you to http://localhost:8080/view/index the first page you'\&ll
create, most likely.\&
.PP
See \fIoddmu\fR(5) for details about the page formatting.\&
.PP
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.\&
.PP
Every file can be viewed as feed by using the extension ".\&rss".\& The
feed items are based on links in bullet lists using the asterix
("*").\&
.PP
Subdirectories are created as necessary.\&
.PP
The wiki knows the following actions for a given page name and (optional)
directory:
.PP
.PD 0
.IP \(bu 4
\fI/\fR redirects to /view/index
.IP \(bu 4
\fI/view/dir/\fR redirects to /view/dir/index
.IP \(bu 4
\fI/view/dir/name\fR shows a page
.IP \(bu 4
\fI/view/dir/name.\&md\fR shows the source text of a page
.IP \(bu 4
\fI/view/dir/name.\&rss\fR shows the RSS feed for the pages linked
.IP \(bu 4
\fI/diff/dir/name\fR shows the last change to a page
.IP \(bu 4
\fI/edit/dir/name\fR shows a form to edit a page
.IP \(bu 4
\fI/preview/dir/name\fR shows a preview of a page edit and the form to edit it
.IP \(bu 4
\fI/save/dir/name\fR saves an edit
.IP \(bu 4
\fI/add/dir/name\fR shows a form to add to a page
.IP \(bu 4
\fI/append/dir/name\fR appends an addition to a page
.IP \(bu 4
\fI/upload/dir/name\fR shows a form to upload a file
.IP \(bu 4
\fI/drop/dir/name\fR saves an upload
.IP \(bu 4
\fI/search/dir/?\&q=term\fR to search for a term
.IP \(bu 4
\fI/sitemap.\&xml\fR to list the links to all the pages
.IP \(bu 4
\fI/archive/dir/name.\&zip\fR to download a zip file of a directory
.PD
.PP
When calling the \fIsave\fR and \fIappend\fR action, the page name is taken from the URL
path and the page content is taken from the \fIbody\fR form parameter.\& To
illustrate, here'\&s how to edit the "welcome" page using \fIcurl\fR:
.PP
.nf
.RS 4
curl --form body="Did you bring a towel?"
http://localhost:8080/save/welcome
.fi
.RE
.PP
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, 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
for f in *\&.jpg; do
curl --form name="$f" --form file=@"$f" --form maxwidth=100
http://localhost:8080/drop/
done
.fi
.RE
.PP
When calling the \fIsearch\fR action, the search terms are taken from the query
parameter \fIq\fR.\&
.PP
.nf
.RS 4
curl \&'http://localhost:8080/search/?q=towel\&'
.fi
.RE
.PP
The page name to act upon is optionally taken from the query parameter \fIid\fR.\& In
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/man/?id=oddmu\&.1\&.txt\&'
.fi
.RE
.PP
The base name for the \fIarchive\fR action is used by the browser to save the
downloaded file.\& For Oddmu, only the directory is important.\& The following zips
the \fIman\fR directory and saves it as \fIman.\&zip\fR.\&
.PP
.nf
.RS 4
curl --remote-name \&'http://localhost:8080/archive/man/man\&.zip
.fi
.RE
.PP
.SH CONFIGURATION
.PP
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".\&
.PP
The second change you should make is to replace the name, email address and
domain name in "feed.\&html".\& Look for "Your Name" and "example.\&org".\&
.PP
See \fIoddmu-templates\fR(5) for more.\&
.PP
.SH ENVIRONMENT
.PP
You can change the port served by setting the ODDMU_PORT environment variable.\&
.PP
You can change the address served by setting the ODDMU_ADDRESS environment
variable to either an IPv4 address or an IPv6 address.\& If ODDMU_ADDRESS is
unset, then the program listens on all available unicast addresses, both IPv4
and IPv6.\& Here are a few example addresses:
.PP
.nf
.RS 4
ODDMU_ADDRESS=127\&.0\&.0\&.1 # The loopback IPv4 address\&.
ODDMU_ADDRESS=2001:db8::3:1 # An IPv6 address\&.
.fi
.RE
.PP
See the Socket Activation section for an alternative method of listening which
supports Unix-domain sockets.\&
.PP
In order to limit language-detection to the languages you actually use, set the
environment variable ODDMU_LANGUAGES to a comma-separated list of ISO 639-1
codes, e.\&g.\& "en" or "en,de,fr,pt".\&
.PP
You can enable webfinger to link fediverse accounts to their correct profile
pages by setting ODDMU_WEBFINGER to "1".\& See \fIoddmu\fR(5).\&
.PP
If you use secret subdirectories, you cannot rely on the web server to hide
those pages because some actions such as searching and archiving include
subdirectories.\& They act upon a whole tree of pages, not just a single page.\& The
ODDMU_FILTER can be used to exclude subdirectories from such tree actions.\& See
\fIoddmu-filter\fR(7) and \fIoddmu-apache\fR(5).\&
.PP
.SH Socket Activation
.PP
Instead of specifying ODDMU_ADDRESS or ODDMU_PORT, you can start the service
through socket activation.\& The advantage of this method is that you can use a
Unix-domain socket instead of a TCP socket, and the permissions and ownership of
the socket are set before the program starts.\& See \fIoddmu.\&service\fR(5),
\fIoddmu-apache\fR(5) and \fIoddmu-nginx\fR(5) for an example of how to use socket
activation with a Unix-domain socket under systemd and Apache.\&
.PP
.SH SECURITY
.PP
If the machine you are running Oddmu on is accessible from the Internet, you
must secure your installation.\& The best way to do this is use a regular web
server as a reverse proxy.\& See \fIoddmu-apache\fR(5) and \fIoddmu-nginx\fR(5) for
example configurations.\&
.PP
Oddmu assumes that all the users that can edit pages or upload files are trusted
users and therefore their content is trusted.\& Oddmu does not perform HTML
sanitization!\&
.PP
For an extra dose of security, consider using a Unix-domain socket.\&
.PP
.SH OPTIONS
.PP
Oddmu can be run on the command-line using various subcommands.\&
.PP
.PD 0
.IP \(bu 4
to generate the HTML for a single page, see \fIoddmu-html\fR(1)
.IP \(bu 4
to generate the HTML for the entire site, using Oddmu as a static site
generator, see \fIoddmu-static\fR(1)
.IP \(bu 4
to export the HTML for the entire site in one big feed, see \fIoddmu-export\fR(1)
.IP \(bu 4
to emulate a search of the files, see \fIoddmu-search\fR(1); to understand how the
search engine indexes pages and how it sorts and scores results, see
\fIoddmu-search\fR(7)
.IP \(bu 4
to search a regular expression and replace it across all files, see
\fIoddmu-replace\fR(1)
.IP \(bu 4
to learn what the most popular hashtags are, see \fIoddmu-hashtags\fR(1)
.IP \(bu 4
to print a table of contents (TOC) for a page, see \fIoddmu-toc\fR(1)
.IP \(bu 4
to list the outgoing links for a page, see \fIoddmu-links\fR(1)
.IP \(bu 4
to find missing pages (local links that go nowhere), see \fIoddmu-missing\fR(1)
.IP \(bu 4
to list all the pages with name and title, see \fIoddmu-list\fR(1)
.IP \(bu 4
to add links to changes, index and hashtag pages to pages you created locally,
see \fIoddmu-notify\fR(1)
.IP \(bu 4
to display build information, see \fIoddmu-version\fR(1)
.PD
.PP
.SH EXAMPLES
.PP
When saving a page, the page name is take from the URL and the page content is
taken from the "body" form parameter.\& To illustrate, here'\&s how to edit a page
using \fIcurl\fR(1):
.PP
.nf
.RS 4
curl --form body="Did you bring a towel?"
http://localhost:8080/save/welcome
.fi
.RE
.PP
To compute the space used by your setup, use regular tools:
.PP
.nf
.RS 4
du --exclude=\&'*/.*\&' --exclude \&'*~\&' --block-size=M
.fi
.RE
.PP
.SH DESIGN
.PP
This is a minimal wiki.\& There is no version history.\& It'\&s well suited as a
\fIsecondary\fR medium: collaboration and conversation happens elsewhere, in chat,
on social media.\& The wiki serves as the text repository that results from these
discussions.\&
.PP
The idea is that the webserver handles as many tasks as possible.\& It logs
requests, does rate limiting, handles encryption, gets the certificates, and so
on.\& The web server acts as a reverse proxy and the wiki ends up being a content
management system with almost no structure or endless malleability, depending
on your point of view.\& See \fIoddmu-apache\fR(5).\&
.PP
.SH NOTES
.PP
Page names are filenames with ".\&md" appended.\& If your filesystem cannot handle
it, it can'\&t be a page name.\& Filenames can contain slashes and Oddmu creates
subdirectories as necessary.\&
.PP
Files may not end with a tilde ('\&~'\&) these are backup files.\& When saving pages
and file uploads, the old file is renamed to the backup file unless the backup
file is less than an hour old, thus collapsing all edits made in an hour into a
single diff when comparing backup and current version.\& The backup also gets an
updated timestamp so that subsequent edits don'\&t immediately overwrite it.\&
.PP
The \fBindex\fR page is the default page.\& People visiting the "root" of the site are
redirected to "/view/index".\&
.PP
The \fBchanges\fR page is where links to new and changed files are added.\& As an
author, you can prevent this from happening by deselecting the checkbox "Add
link to the list of changes.\&" The changes page can be edited like every other
page, so it'\&s easy to undo mistakes.\&
.PP
Links on the changes page are grouped by date.\& When new links are added, the
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
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 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.\&
.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.\&
.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 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.\& Oddmu watches
the working directory and any subdirectories for changes made to page files and
updates this cache when necessary.\&
.PP
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.\& 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
.PD 0
.IP \(bu 4
\fIoddmu\fR(5), about the markup syntax and how feeds are generated based on link
lists
.IP \(bu 4
\fIoddmu-releases\fR(7), on what features are part of the latest release
.IP \(bu 4
\fIoddmu-filter\fR(7), on how to treat subdirectories as separate sites
.IP \(bu 4
\fIoddmu-search\fR(7), on how search works
.IP \(bu 4
\fIoddmu-templates\fR(5), on how to write the HTML templates
.PD
.PP
If you run Oddmu as a web server:
.PP
.PD 0
.IP \(bu 4
\fIoddmu-apache\fR(5), on how to set up Apache as a reverse proxy
.IP \(bu 4
\fIoddmu-nginx\fR(5), on how to set up freenginx as a reverse proxy
.IP \(bu 4
\fIoddmu-webdav\fR(5), on how to set up Apache as a Web-DAV server
.IP \(bu 4
\fIoddmu.\&service\fR(5), on how to run the service under systemd
.PD
.PP
If you run Oddmu as a static site generator or pages offline and sync them with
Oddmu running as a webserver:
.PP
.PD 0
.IP \(bu 4
\fIoddmu-hashtags\fR(1), on working with hashtags
.IP \(bu 4
\fIoddmu-html\fR(1), on how to render a page
.IP \(bu 4
\fIoddmu-feed\fR(1), on how to render a feed
.IP \(bu 4
\fIoddmu-list\fR(1), on how to list pages and titles
.IP \(bu 4
\fIoddmu-links\fR(1), on how to list the outgoing links for a page
.IP \(bu 4
\fIoddmu-missing\fR(1), on how to find broken local links
.IP \(bu 4
\fIoddmu-notify\fR(1), on updating index, changes and hashtag pages
.IP \(bu 4
\fIoddmu-replace\fR(1), on how to search and replace text
.IP \(bu 4
\fIoddmu-search\fR(1), on how to run a search
.IP \(bu 4
\fIoddmu-sitemap\fR(1), on generating a static sitemap.\&xml
.IP \(bu 4
\fIoddmu-static\fR(1), on generating a static site
.IP \(bu 4
\fIoddmu-toc\fR(1), on how to list the table of contents (toc) a page
.IP \(bu 4
\fIoddmu-version\fR(1), on how to get all the build information from the binary
.PD
.PP
If you want to stop using Oddmu:
.PP
.PD 0
.IP \(bu 4
\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,11 +25,10 @@ create, most likely.
See _oddmu_(5) for details about the page formatting.
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 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 your files don't provide their own title ("# title"), the file name (without
".md") is used for the page title.
@@ -72,9 +71,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, 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:
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:
```
for f in *.jpg; do
@@ -91,12 +90,11 @@ 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 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.
this case, the directory must also be part of the query parameter and not of the
URL path.
```
curl 'http://localhost:8080/view/man/?id=oddmu.1.txt'
curl 'http://localhost:8080/view/?id=man/oddmu.1.txt'
```
The base name for the _archive_ action is used by the browser to save the
@@ -109,9 +107,8 @@ curl --remote-name 'http://localhost:8080/archive/man/man.zip
# CONFIGURATION
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 template files are the HTML files in the working directory. 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".
@@ -250,53 +247,55 @@ 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 as follows:
from other pages.
- 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 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.
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.
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. Oddmu watches
the working directory and any subdirectories for changes made to page files and
updates this cache when necessary.
starts. If you have a lot of pages, this takes a lot of memory.
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.
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.
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 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.
_oddmu-templates_(5) for their names and their use.
# SEE ALSO
@@ -335,11 +334,6 @@ 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>.

View File

@@ -1,81 +0,0 @@
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 template attributes?
func TestManTemplateAttributes(t *testing.T) {
// Does oddmu-templates(5) mention all the templates?
func TestManTemplateAttributess(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(`mux\.HandleFunc\("(/[a-z]+/)", makeHandler\([a-z]+Handler, (true|false)(, http\.Method(Get|Post))+\)\)`)
re := regexp.MustCompile(`\.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,27 +122,6 @@ 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.Fprint(w, name)
fmt.Fprintf(w, name)
}
default:
for _, p := range items {

View File

@@ -1,20 +1,16 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"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
}
@@ -25,12 +21,8 @@ func sitemapHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/sitemap.xml" {
http.NotFound(w, r)
} else {
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>` + "\n"))
scheme := "http"
if r.TLS != nil {
scheme += "s"
}
base := fmt.Sprintf("%s://%s/view/", scheme, r.Host)
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
base := r.URL.Scheme + "://" + r.URL.Host + "/view/"
filter := os.Getenv("ODDMU_FILTER")
renderTemplate(w, ".", "sitemap", sitemap(&index, base, filter))
}
@@ -48,7 +40,7 @@ func sitemap(idx *indexStore, base, filter string) Sitemap {
idx.RLock()
defer idx.RUnlock()
for name := range idx.titles {
if filter == "" || !re.MatchString(name) {
if !re.MatchString(name) {
url = append(url, &SitemapURL{Loc: base + name})
}
}

View File

@@ -2,8 +2,8 @@ package main
import (
"context"
"flag"
"fmt"
"flag"
"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 {
initTemplates()
loadTemplates()
template := "sitemap.html"
t := templates.template[template]
if t == nil {

View File

@@ -4,7 +4,6 @@
<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

@@ -24,9 +24,6 @@ import (
"github.com/google/subcommands"
)
var shrinkWidth = 800
var shrinkQuality = 30
type staticCmd struct {
jobs int
shrink bool
@@ -72,7 +69,7 @@ func staticCli(source, target string, jobs int, glob string, shrink, verbose, qu
index.RLock()
defer index.RUnlock()
loadLanguages()
initTemplates()
loadTemplates()
tasks := make(chan args, 10000)
results := make(chan error, jobs)
done := make(chan bool, jobs)
@@ -262,6 +259,9 @@ func staticFile(source, target string, info fs.FileInfo, shrink bool) error {
return err
}
var shrinkWidth = 800
var shrinkQuality = 10
// shrink Image shrinks images down and reduces the quality dramatically.
func shrinkImage(source, target string, info fs.FileInfo) error {
file, err := os.Open(source)

View File

@@ -1,80 +1,44 @@
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{}
var templateFiles = []string{"edit.html", "add.html", "view.html", "preview.html",
"diff.html", "search.html", "static.html", "upload.html", "feed.html",
"sitemap.html"}
// templateStore controls access to the map of parsed HTML templates. Make sure to lock and unlock as appropriate.
// See renderTemplate and loadTemplates.
// templateStore controls access to 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". This is a
// map because we need to add and remove templates as time passes.
// 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 map[string]*template.Template
}
var templates templateStore
// initTemplates loads the templates and writes them to disk if they are missing. If templates have already been
// loaded, return immediately.
func initTemplates() {
// loadTemplates loads the templates. If templates have already been loaded, return immediately.
func loadTemplates() {
if templates.template != nil {
return
}
templates.Lock()
defer templates.Unlock()
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
templates.template = make(map[string]*template.Template)
filepath.Walk(".", loadTemplate)
log.Println(len(templates.template), "templates loaded")
}
@@ -90,6 +54,7 @@ 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
}
@@ -127,7 +92,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) {
initTemplates()
loadTemplates()
base := tmpl + ".html"
templates.RLock()
defer templates.RUnlock()

View File

@@ -4,7 +4,6 @@
<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

@@ -208,7 +208,7 @@ func serve() {
mux.HandleFunc("/upload/", makeHandler(uploadHandler, false, http.MethodGet))
mux.HandleFunc("/drop/", makeHandler(dropHandler, false, http.MethodPost))
mux.HandleFunc("/search/", makeHandler(searchHandler, false, http.MethodGet, http.MethodPost))
mux.HandleFunc("/sitemap.xml", sitemapHandler)
mux.HandleFunc("/sitemap", sitemapHandler)
srv := &http.Server{
ReadTimeout: 2 * time.Minute,
WriteTimeout: 5 * time.Minute,
@@ -230,7 +230,6 @@ func commands() {
subcommands.Register(&exportCmd{}, "")
subcommands.Register(&hashtagsCmd{}, "")
subcommands.Register(&feedCmd{}, "")
subcommands.Register(&manCmd{}, "")
subcommands.Register(&htmlCmd{}, "")
subcommands.Register(&listCmd{}, "")
subcommands.Register(&linksCmd{}, "")