20 Commits
v1.12 ... v1.13

Author SHA1 Message Date
Alex Schroeder
486c3f8620 Release 1.13 2024-08-24 14:16:03 +02:00
Alex Schroeder
5b0fcdd69f Update Makefiles
Help is more compact.
docs/Makefile comes with help.
Fixed make install.
Added clean target.
Use long options for tar and rm.
2024-08-24 11:52:19 +02:00
Alex Schroeder
bb99d05a0d Add more cross-compilation targets
- oddmu-darwin-amd64.tar.gz
- oddmu-windows-amd64.tar.gz
- oddmu-linux-arm64.tar.gz
2024-08-24 11:38:04 +02:00
Alex Schroeder
98358a008b Switched HEIC decoder again
This time I'm using github.com/gen2brain/heic. "Based on libheif and
libde265 compiled to WASM and used with wazero runtime (CGo-free)."
Amazing.
2024-08-23 17:37:54 +02:00
Alex Schroeder
51c8348ef7 Switched HEIC decoder fork
Moving from github.com/bashdrew/goheif v0.0.0-20230406184952-7a08ca9c9bdd
to github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786.

There is no cross-compiling available. If only somebody wrote a HEIC
decoder in pure Go.
2024-08-23 17:24:23 +02:00
Alex Schroeder
5e77f1332e Tiny typo in the README 2024-08-23 12:55:15 +02:00
Alex Schroeder
fbbb4a543f Split a section in oddmuse.service
Moved the part about the privileged port 80 from the "A personal wiki"
section in oddmuse.service(5) into its own section.
2024-08-23 12:44:45 +02:00
Alex Schroeder
ccc7c0bc8f Run go fmt 2024-08-22 08:05:58 +02:00
Alex Schroeder
aae2ae1265 The "html" subcommand takes - for stdin 2024-08-21 18:05:09 +02:00
Alex Schroeder
8929d72acd Fix export subcommand by actually loading the index
Without this fix, only the tests load the index.
2024-08-21 18:04:28 +02:00
Alex Schroeder
014507ce4e Move sed expressions to Perl script
This simplifies the Makefile inside the man directory.
2024-08-18 00:26:15 +02:00
Alex Schroeder
554a929bf5 Document use of ODDMU_LANGUAGES for export 2024-08-16 18:37:26 +02:00
Alex Schroeder
5f8e006594 Fixing export and missing subcommands
Both of these subcommands rely on the global index. Loading this
global index and acting upon it while tests are running is
problematic, however. Walking that index results in being unable to
load files that are being created and deleted in other tests.

Therefore, these tests get their own minimal index that only indexes
the Markdown files in the original checkout: README.md, index.md, and
the Markdown files in the the "themes" tree.

This also means that missingCli and exportCli must accept a pointer to
an index and use that instead of the global index.

Add a test for language in the export feed.

Add a test for a JSON feed.

Both of these tests used to fail because the exportCli funtion did not
copy p.Body to it.Body so when the template calls .Languages the
result was always empty.
2024-08-16 18:27:34 +02:00
Alex Schroeder
e347a59603 Fix documentation of renderHtml
Page.Language hasn't been a member of the Page type for a long time.
2024-08-16 18:24:54 +02:00
Alex Schroeder
964dc3bf4a Ensure cleanup of testdata before running tests 2024-08-16 18:24:28 +02:00
Alex Schroeder
d5f8b280ac Support for JSON exports 2024-08-16 13:41:21 +02:00
Alex Schroeder
8ee5705ae7 Wording for man-pages
Remove "from the command-line" in a few places.
2024-08-16 12:33:11 +02:00
Alex Schroeder
43bf1574c9 Add export subcommand 2024-08-16 12:32:58 +02:00
Alex Schroeder
1c8af9fcdb missing subcommand no longer walks the file-system
Instead, rely on the code that loads the index. Delete the
existingPages function.
2024-08-16 11:23:34 +02:00
Alex Schroeder
f6fa76bd5f Update alexschroeder.ch theme 2024-08-16 11:13:31 +02:00
39 changed files with 668 additions and 209 deletions

View File

@@ -1,29 +1,27 @@
SHELL=/bin/bash
PREFIX=${HOME}/.local
.PHONY: help build test run upload docs install missing
.PHONY: help build test run upload docs install priv
help:
@echo Help for Oddmu
@echo =====================
@echo
@echo ==============
@echo make run
@echo " runs program, offline"
@echo
@echo make test
@echo " runs the tests without log output"
@echo
@echo make docs
@echo " create man pages from text files"
@echo
@echo make build
@echo " just build it"
@echo
@echo make install
@echo " install the files to ~/.local"
@echo
@echo make upload
@echo " this is how I upgrade my server"
@echo make dist
@echo " cross compile for other systems"
@echo make clean
@echo " remove built files"
build: oddmu
@@ -31,6 +29,7 @@ oddmu: *.go
go build
test:
rm -rf testdata/*
go test -shuffle on .
run:
@@ -42,20 +41,37 @@ upload: build
@echo Changes to the template files need careful consideration
docs:
cd man; make
cd man; make man
install:
for n in 1 5 7; do install -D -t ${PREFIX}/share/man/man$$n man/*.$$n; done
install -D -t ${PREFIX}/.local/bin oddmu
install -D -t ${PREFIX}/bin oddmu
# More could be added, of course!
dist: oddmu-linux-amd64.tar.gz
clean:
rm --force oddmu oddmu.exe oddmu-{linux,darwin,windows}-{amd64,arm64}{,.tar.gz}
cd man && make clean
dist: oddmu-linux-amd64.tar.gz oddmu-linux-arm64 oddmu-darwin-amd64.tar.gz oddmu-windows-amd64.tar.gz
oddmu-linux-amd64: *.go
GOOS=linux GOARCH=amd64 go build -o $@
oddmu-linux-arm64: *.go
env GOOS=linux GOARCH=arm64 GOARM=5 go build -o $@
oddmu-darwin-amd64: *.go
GOOS=darwin GOARCH=arm64 go build -o $@
oddmu.exe: *.go
GOOS=windows GOARCH=amd64 go build -o $@
oddmu-windows-amd64.tar.gz: oddmu.exe
cd man && make html
tar --create --file $@ --transform='s/^/oddmu\//' --exclude='*~' \
$< *.md man/*.[157].{html,md} themes/
%.tar.gz: %
tar czf $@ --transform='s/^$</oddmu/' --transform='s/^/oddmu\//' --exclude='*~' \
tar --create --file $@ --transform='s/^$</oddmu/' --transform='s/^/oddmu\//' --exclude='*~' \
$< Makefile *.socket *.service *.md man/Makefile man/*.1 man/*.5 man/*.7 themes/
priv:

View File

@@ -31,7 +31,7 @@ If your pages don't provide their own title (`# title`), the file name
necessary.
Other files can be uploaded and images (ending in `.jpg`, `.jpeg`,
`.png`, `.heic` or `webp`) can be resized when they are uploaded
`.png`, `.heic` or `.webp`) can be resized when they are uploaded
(resulting in `.jpg` or `.png` files).
## Documentation
@@ -138,6 +138,13 @@ edit.
This man page documents how to setup a systemd unit and have it manage
Oddmu. “Great configurability brings great burdens.”
Leaving:
[oddmu-export(1)](https://alexschroeder.ch/view/oddmu/oddmu-export.1):
This man page documents how to export all the pages as one RSS feed so
that you can import them all into a new platform that doesn't use
Markdown files.
## Building
To build the binary:
@@ -321,7 +328,7 @@ in turn can be used by browsers to get hyphenation right. Apache-2.0.
is used to sniff the MIME type of files with unknown filename
extensions. MIT.
[github.com/bashdrew/goheif](https://github.com/bashdrew/goheif) is
[github.com/gen2brain/heic](https://github.com/gen2brain/heic) is
used to decode HEIC files (the new default file format for photos on
iPhones). LGPL-3.0-only.

112
export_cmd.go Normal file
View File

@@ -0,0 +1,112 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/google/subcommands"
htmlTemplate "html/template"
"io"
"os"
"strings"
textTemplate "text/template"
"time"
)
type exportCmd struct {
templateName string
}
func (cmd *exportCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&cmd.templateName, "template", "feed.html", "template filename")
}
func (*exportCmd) Name() string { return "export" }
func (*exportCmd) Synopsis() string { return "export the whole site as one big RSS feed" }
func (*exportCmd) Usage() string {
return `export:
Export the entire site as one big RSS feed. This may allow you to
import the whole site into a different content management system.
The feed contains every page, in HTML format, so the Markdown files
are part of the feed, but none of the other files.
The RSS feed is printed to stdout so you probably want to redirect
it:
oddmu export > /tmp/export.rss
Options:
-template "filename" specifies the template to use (default: feed.html)
`
}
func (cmd *exportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
index.load()
return exportCli(os.Stdout, cmd.templateName, &index)
}
// exportCli runs the export command on the command line. In order to make testing easier, it takes a Writer and an
// indexStore. The Writer is important so that test code can provide a buffer instead of os.Stdout; the indexStore 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 exportCli(w io.Writer, templateName string, idx *indexStore) subcommands.ExitStatus {
loadLanguages()
feed := new(Feed)
items := []Item{}
// feed.Name remains unset
feed.Date = time.Now().Format(time.RFC3339)
for name, title := range idx.titles {
if name == "index" {
feed.Title = title
}
p, err := loadPage(name)
if err != nil {
fmt.Fprintf(os.Stderr, "Loading %s: %s\n", name, err)
return subcommands.ExitFailure
}
p.handleTitle(false)
p.renderHtml()
fi, err := os.Stat(name + ".md")
if err != nil {
fmt.Fprintf(os.Stderr, "Stat %s: %s\n", name, err)
return subcommands.ExitFailure
}
it := Item{Date: fi.ModTime().Format(time.RFC3339)}
it.Title = p.Title
it.Name = p.Name
it.Body = p.Body
it.Html = htmlTemplate.HTML(htmlTemplate.HTMLEscaper(p.Html))
it.Hashtags = p.Hashtags
items = append(items, it)
}
feed.Items = items
// No effort is made to work with the templates var.
if strings.HasSuffix(templateName, ".html") ||
strings.HasSuffix(templateName, ".xml") ||
strings.HasSuffix(templateName, ".rss") {
w.Write([]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"))
t, err := htmlTemplate.ParseFiles(templateName)
if err != nil {
fmt.Fprintf(os.Stderr, "Parsing %s: %s\n", templateName, err)
return subcommands.ExitFailure
}
err = t.Execute(w, feed)
if err != nil {
fmt.Fprintf(os.Stderr, "Writing feed: %s\n", err)
return subcommands.ExitFailure
}
} else {
t, err := textTemplate.ParseFiles(templateName)
if err != nil {
fmt.Fprintf(os.Stderr, "Parsing %s: %s\n", templateName, err)
return subcommands.ExitFailure
}
err = t.Execute(w, feed)
if err != nil {
fmt.Fprintf(os.Stderr, "Writing feed: %s\n", err)
return subcommands.ExitFailure
}
}
return subcommands.ExitSuccess
}

55
export_cmd_test.go Normal file
View File

@@ -0,0 +1,55 @@
package main
import (
"bytes"
"github.com/google/subcommands"
"github.com/stretchr/testify/assert"
"os"
"regexp"
"testing"
)
func TestExportCmd(t *testing.T) {
b := new(bytes.Buffer)
s := exportCli(b, "feed.html", minimalIndex(t))
assert.Equal(t, subcommands.ExitSuccess, s)
assert.Contains(t, b.String(), "<title>Oddµ: A minimal wiki</title>")
assert.Contains(t, b.String(), "<title>Welcome to Oddµ</title>")
}
func TestExportCmdLanguage(t *testing.T) {
os.Setenv("ODDMU_LANGUAGES", "de,en")
loadLanguages()
p := Page{Body: []byte("This is an English text. All right then!")}
it := Item{Page: p}
assert.Equal(t, "en", it.Language())
}
func TestExportCmdJsonFeed(t *testing.T) {
cleanup(t, "testdata/json")
os.Mkdir("testdata/json", 0755)
assert.NoError(t, os.WriteFile("testdata/json/template.json", []byte(`{
"version": "https://jsonfeed.org/version/1.1",
"title": "{{.Title}}",
"home_page_url": "https://alexschroeder.ch",
"others": [],
"items": [{{range .Items}}
{
"id": "{{.Name}}",
"url": "https://alexschroeder.ch/view/{{.Name}}",
"title": "{{.Title}}",
"language": "{{.Language}}"
"date_modified": "{{.Date}}",
"content_html": "{{.Html}}",
"tags": [{{range .Hashtags}}"{{.}}",{{end}}""],
},{{end}}
{}
]
}
`), 0644))
b := new(bytes.Buffer)
s := exportCli(b, "testdata/json/template.json", minimalIndex(t))
assert.Equal(t, subcommands.ExitSuccess, s)
assert.Contains(t, b.String(), `"title": "Oddµ: A minimal wiki"`)
assert.Regexp(t, regexp.MustCompile("&lt;h1.*&gt;Welcome to Oddµ&lt;/h1&gt;"), b.String()) // skip id
}

10
go.mod
View File

@@ -1,9 +1,10 @@
module alexschroeder.ch/cgit/oddmu
go 1.21.0
go 1.22
toolchain go1.22.3
require (
github.com/bashdrew/goheif v0.0.0-20230406184952-7a08ca9c9bdd
github.com/disintegration/imaging v1.6.2
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f
github.com/fsnotify/fsnotify v1.7.0
@@ -22,15 +23,18 @@ require (
require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.7.1 // indirect
github.com/gen2brain/heic v0.3.1 // indirect
github.com/gorilla/css v1.0.1 // 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.3.1 // indirect
github.com/tetratelabs/wazero v1.7.3 // indirect
golang.org/x/image v0.15.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.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
)

12
go.sum
View File

@@ -1,18 +1,22 @@
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/bashdrew/goheif v0.0.0-20230406184952-7a08ca9c9bdd h1:SxkQeH4jjXT0zMgiRgkiIQjIvWfe9vXuTAmE3cfcQrU=
github.com/bashdrew/goheif v0.0.0-20230406184952-7a08ca9c9bdd/go.mod h1:p1sbxRy+MY71fEWHcfRmerC8WUYXDFCExF9A7aXwp98=
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.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
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.0.0-20230113233934-ca402e77a786 h1:zvgtcRb2B5gynWjm+Fc9oJZPHXwmcgyH0xCcNm6Rmo4=
github.com/gen2brain/heic v0.0.0-20230113233934-ca402e77a786/go.mod h1:aKVJoQ0cc9K5Xb058XSnnAxXLliR97qbSqWBlm5ca1E=
github.com/gen2brain/heic v0.3.1 h1:ClY5YTdXdIanw7pe9ZVUM9XcsqH6CCCa5CZBlm58qOs=
github.com/gen2brain/heic v0.3.1/go.mod h1:m2sVIf02O7wfO8mJm+PvE91lnq4QYJy2hseUon7So10=
github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6 h1:ZPy+2XJ8u0bB3sNFi+I72gMEMS7MTg7aZCCXPOjV8iw=
github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
@@ -53,6 +57,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
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.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw=
github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
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=
@@ -62,6 +68,8 @@ 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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=

View File

@@ -18,6 +18,7 @@ func (*htmlCmd) Synopsis() string { return "render a page as HTML" }
func (*htmlCmd) Usage() string {
return `html [-view] <page name> ...:
Render one or more pages as HTML.
Use a single - to read Markdown from stdin.
`
}
@@ -30,27 +31,44 @@ func (cmd *htmlCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
}
func htmlCli(w io.Writer, useTemplate bool, args []string) subcommands.ExitStatus {
if len(args) == 1 && args[0] == "-" {
body, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintf(w, "Cannot read from stdin: %s\n", err)
return subcommands.ExitFailure
}
p := &Page{Name: "stdin", Body: body}
return p.printHtml(w, useTemplate)
}
for _, arg := range args {
p, err := loadPage(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot load %s: %s\n", arg, err)
return subcommands.ExitFailure
}
if useTemplate {
p.handleTitle(true)
p.renderHtml()
t := "view.html"
loadTemplates()
err := templates.template[t].Execute(w, p)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot execute %s template for %s: %s\n", t, arg, err)
return subcommands.ExitFailure
}
} else {
// do not handle title
p.renderHtml()
fmt.Fprintln(w, p.Html)
status := p.printHtml(w, useTemplate)
if status != subcommands.ExitSuccess {
return status
}
}
return subcommands.ExitSuccess
}
func (p *Page) printHtml(w io.Writer, useTemplate bool) subcommands.ExitStatus {
if useTemplate {
t := "view.html"
loadTemplates()
p.handleTitle(true)
p.renderHtml()
err := templates.template[t].Execute(w, p)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot execute %s template for %s: %s\n", t, p.Name, err)
return subcommands.ExitFailure
}
} else {
// do not handle title
p.renderHtml()
fmt.Fprintln(w, p.Html)
}
return subcommands.ExitSuccess
}

View File

@@ -3,6 +3,20 @@ MAN=$(patsubst %.txt,%,${TEXT})
HTML=$(patsubst %.txt,%.html,${TEXT})
MD=$(patsubst %.txt,%.md,${TEXT})
help:
@echo Help for Oddmu Documentation
@echo ============================
@echo make man
@echo " regenerate man pages"
@echo make html
@echo " generate HTML pages"
@echo make md
@echo " generate Markdown pages"
@echo make clean
@echo " delete HTML and Markdown pages"
@echo make realclean
@echo " delete HTML, Markdown and man pages"
man: ${MAN}
%: %.txt
@@ -20,16 +34,7 @@ md: ${MD}
%.md: %.txt
@echo Making $@
@sed --regexp-extended \
-e 's/\*([^*]+)\*/**\1**/g' \
-e 's/_(oddmu[a-z.-]*)_\(([1-9])\)/[\1(\2)](\1.\2)/g' \
-e 's/\b_([^_]+)_\b/*\1*/g' \
-e 's/^# /## /' \
-e 's/#([^ #])/\\#\1/' \
-e 's/"(http.*?)"/`\1`/' \
-e 's/"(\[.*?\]\(.*?\))"/`\1`/' \
-e 's/^([A-Z.-]*\([1-9]\))( ".*")?$$/# \1/' \
< $< > $@
@perl scdoc-to-markdown < $< > $@
README.md: ../README.md
@echo Making $@
@@ -42,7 +47,9 @@ upload: ${MD} README.md
make clean
clean:
rm --force ${HTML} ${MD} README.md
@echo Removing HTML and Markdown files
@rm --force ${HTML} ${MD} README.md
realclean: clean
rm --force ${MAN}
@echo Removing man pages
@rm --force ${MAN}

79
man/oddmu-export.1 Normal file
View File

@@ -0,0 +1,79 @@
.\" 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-EXPORT" "1" "2024-08-16"
.PP
.SH NAME
.PP
oddmu-export - export all pages into one file
.PP
.SH SYNOPSIS
.PP
\fBoddmu export\fR [\fB-template\fR \fIfilename\fR]
.PP
.SH DESCRIPTION
.PP
The "export" subcommand prints a RSS file containing all the pages to stdout.\&
You probably want to redirect this into a file so that you can upload and import
it somewhere.\&
.PP
Note that this only handles pages (Markdown files).\& All other files (images,
PDFs, whatever else you uploaded) are not part of the feed and has to be
uploaded to the new platform in some other way.\&
.PP
The \fB-template\fR option specifies the template to use.\& If the template filename
ends in \fI.\&xml\fR, \fI.\&html\fR or \fI.\&rss\fR, it is assumed to contain XML and the optional
XML preamble is printed and appropriate escaping rules are used.\&
.PP
.SH FILES
.PP
By default, the export uses the \fB\fRfeed.\&html\fB\fR template in the current directory.\&
.PP
.SH EXAMPLE
.PP
Export all the pages into a big XML file:
.PP
.nf
.RS 4
env ODDMU_LANGUAGES=de,en oddmu export > /tmp/export\&.xml
.fi
.RE
.PP
Alternatively, consider a template file like the following, to generate a JSON
feed.\& The rule to disallow a comma at the end of arrays means that we need to
add an empty tag and an empty item, unfortunately:
.PP
.nf
.RS 4
{
"version": "https://jsonfeed\&.org/version/1\&.1",
"title": "{{\&.Title}}",
"home_page_url": "https://alexschroeder\&.ch",
"others": [],
"items": [{{range \&.Items}}
{
"id": "{{\&.Name}}",
"url": "https://alexschroeder\&.ch/view/{{\&.Name}}",
"title": "{{\&.Title}}",
"content_html": "{{\&.Html}}",
"date_modified": "{{\&.Date}}",
"tags": [{{range \&.Hashtags}}"{{\&.}}",{{end}}""],
"language": "{{\&.Language}}"
},{{end}}
{}
]
}
.fi
.RE
.PP
.SH SEE ALSO
.PP
\fIoddmu\fR(1), \fIoddmu-templates\fR(5), \fIoddmu-static\fR(1)
.PP
.SH AUTHORS
.PP
Maintained by Alex Schroeder <alex@gnu.\&org>.\&

68
man/oddmu-export.1.txt Normal file
View File

@@ -0,0 +1,68 @@
ODDMU-EXPORT(1)
# NAME
oddmu-export - export all pages into one file
# SYNOPSIS
*oddmu export* [*-template* _filename_]
# DESCRIPTION
The "export" subcommand prints a RSS file containing all the pages to stdout.
You probably want to redirect this into a file so that you can upload and import
it somewhere.
Note that this only handles pages (Markdown files). All other files (images,
PDFs, whatever else you uploaded) are not part of the feed and has to be
uploaded to the new platform in some other way.
The *-template* option specifies the template to use. If the template filename
ends in _.xml_, _.html_ or _.rss_, it is assumed to contain XML and the optional
XML preamble is printed and appropriate escaping rules are used.
# FILES
By default, the export uses the **feed.html** template in the current directory.
# EXAMPLE
Export all the pages into a big XML file:
```
env ODDMU_LANGUAGES=de,en oddmu export > /tmp/export.xml
```
Alternatively, consider a template file like the following, to generate a JSON
feed. The rule to disallow a comma at the end of arrays means that we need to
add an empty tag and an empty item, unfortunately:
```
{
"version": "https://jsonfeed.org/version/1.1",
"title": "{{.Title}}",
"home_page_url": "https://alexschroeder.ch",
"others": [],
"items": [{{range .Items}}
{
"id": "{{.Name}}",
"url": "https://alexschroeder.ch/view/{{.Name}}",
"title": "{{.Title}}",
"content_html": "{{.Html}}",
"date_modified": "{{.Date}}",
"tags": [{{range .Hashtags}}"{{.}}",{{end}}""],
"language": "{{.Language}}"
},{{end}}
{}
]
}
```
# SEE ALSO
_oddmu_(1), _oddmu-templates_(5), _oddmu-static_(1)
# AUTHORS
Maintained by Alex Schroeder <alex@gnu.org>.

View File

@@ -5,11 +5,11 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-HASHTAGS" "1" "2024-07-31"
.TH "ODDMU-HASHTAGS" "1" "2024-08-16"
.PP
.SH NAME
.PP
oddmu-hashtags - count the hashtags used from the command-line
oddmu-hashtags - count the hashtags used
.PP
.SH SYNOPSIS
.PP

View File

@@ -2,7 +2,7 @@ ODDMU-HASHTAGS(1)
# NAME
oddmu-hashtags - count the hashtags used from the command-line
oddmu-hashtags - count the hashtags used
# SYNOPSIS

View File

@@ -5,11 +5,11 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-HTML" "1" "2024-02-26"
.TH "ODDMU-HTML" "1" "2024-08-21"
.PP
.SH NAME
.PP
oddmu-html - render Oddmu page HTML from the command-line
oddmu-html - render Oddmu page HTML
.PP
.SH SYNOPSIS
.PP
@@ -19,7 +19,8 @@ oddmu-html - render Oddmu page HTML from the command-line
.PP
The "html" subcommand opens the Markdown file for the given page name (appending
the ".\&md" extension) and prints the HTML to STDOUT without invoking the
"view.\&html" template.\&
"view.\&html" template.\& Use "-" as the page name if you want to read Markdown from
\fBstdin\fR.\&
.PP
.SH OPTIONS
.PP
@@ -31,14 +32,23 @@ lacks html and body tags.\&
.RE
.SH EXAMPLE
.PP
Generate the HTML for "README.\&md":
Generate "README.\&html" from "README.\&md":
.PP
.nf
.RS 4
oddmu html README
oddmu html README > README\&.html
.fi
.RE
.PP
Alternatively:
.PP
.nf
.RS 4
oddmu html - < README\&.md > README\&.html
.fi
.RE
.PP
.PP
.SH ENVIRONMENT
.PP
The ODDMU_WEBFINGER environment variable has no effect in this situation.\&

View File

@@ -2,7 +2,7 @@ ODDMU-HTML(1)
# NAME
oddmu-html - render Oddmu page HTML from the command-line
oddmu-html - render Oddmu page HTML
# SYNOPSIS
@@ -12,7 +12,8 @@ oddmu-html - render Oddmu page HTML from the command-line
The "html" subcommand opens the Markdown file for the given page name (appending
the ".md" extension) and prints the HTML to STDOUT without invoking the
"view.html" template.
"view.html" template. Use "-" as the page name if you want to read Markdown from
*stdin*.
# OPTIONS
@@ -22,12 +23,19 @@ the ".md" extension) and prints the HTML to STDOUT without invoking the
# EXAMPLE
Generate the HTML for "README.md":
Generate "README.html" from "README.md":
```
oddmu html README
oddmu html README > README.html
```
Alternatively:
```
oddmu html - < README.md > README.html
```
# ENVIRONMENT
The ODDMU_WEBFINGER environment variable has no effect in this situation.

View File

@@ -5,11 +5,11 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-LIST" "1" "2024-02-24"
.TH "ODDMU-LIST" "1" "2024-08-16"
.PP
.SH NAME
.PP
oddmu-list - list page names and titles from the command-line
oddmu-list - list page names and titles
.PP
.SH SYNOPSIS
.PP

View File

@@ -2,7 +2,7 @@ ODDMU-LIST(1)
# NAME
oddmu-list - list page names and titles from the command-line
oddmu-list - list page names and titles
# SYNOPSIS

View File

@@ -5,11 +5,11 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-MISSING" "1" "2024-02-17"
.TH "ODDMU-MISSING" "1" "2024-08-16"
.PP
.SH NAME
.PP
oddmu-missing - list missing pages from the command-line
oddmu-missing - list missing pages
.PP
.SH SYNOPSIS
.PP

View File

@@ -2,7 +2,7 @@ ODDMU-MISSING(1)
# NAME
oddmu-missing - list missing pages from the command-line
oddmu-missing - list missing pages
# SYNOPSIS

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-RELEASES" "7" "2024-08-15"
.TH "ODDMU-RELEASES" "7" "2024-08-24"
.PP
.SH NAME
.PP
@@ -15,6 +15,10 @@ oddmu-releases - what'\&s new?\&
.PP
This page lists user-visible features and template changes to consider.\&
.PP
.SS 1.13 (2024)
.PP
Add \fIexport\fR subcommand.\&
.PP
.SS 1.12 (2024)
.PP
Add \fIhashtags\fR, \fIlinks\fR and \fItoc\fR subcommands.\&

View File

@@ -8,6 +8,10 @@ oddmu-releases - what's new?
This page lists user-visible features and template changes to consider.
## 1.13 (2024)
Add _export_ subcommand.
## 1.12 (2024)
Add _hashtags_, _links_ and _toc_ subcommands.

View File

@@ -5,11 +5,11 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-REPLACE" "1" "2024-02-17"
.TH "ODDMU-REPLACE" "1" "2024-08-16"
.PP
.SH NAME
.PP
oddmu-replace - replace text in Oddmu pages from the command-line
oddmu-replace - replace text in Oddmu pages
.PP
.SH SYNOPSIS
.PP

View File

@@ -2,7 +2,7 @@ ODDMU-REPLACE(1)
# NAME
oddmu-replace - replace text in Oddmu pages from the command-line
oddmu-replace - replace text in Oddmu pages
# SYNOPSIS

View File

@@ -5,11 +5,11 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU-SEARCH" "1" "2024-07-30"
.TH "ODDMU-SEARCH" "1" "2024-08-16"
.PP
.SH NAME
.PP
oddmu-search - search the Oddmu pages from the command-line
oddmu-search - search the Oddmu pages
.PP
.SH SYNOPSIS
.PP
@@ -42,7 +42,7 @@ Limit search to a particular directory.\&
.RE
\fB-extract\fR
.RS 4
Print search extracts for interactive use from the command-line.\&
Print search extracts for interactive use
.RE
\fB-page\fR \fIn\fR
.RS 4

View File

@@ -2,7 +2,7 @@ ODDMU-SEARCH(1)
# NAME
oddmu-search - search the Oddmu pages from the command-line
oddmu-search - search the Oddmu pages
# SYNOPSIS
@@ -32,7 +32,7 @@ scored.
*-dir* _string_
Limit search to a particular directory.
*-extract*
Print search extracts for interactive use from the command-line.
Print search extracts for interactive use
*-page* _n_
Search results are paginated and by default only the first page is
shown. This option allows you to view other pages.

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU" "1" "2024-08-15"
.TH "ODDMU" "1" "2024-08-16"
.PP
.SH NAME
.PP
@@ -367,32 +367,36 @@ Oddmu running as a webserver:
.PP
.PD 0
.IP \(bu 4
\fIoddmu-hashtags\fR(1), on how to count the hashtags used from the command-line
\fIoddmu-hashtags\fR(1), on how to count the hashtags used
.IP \(bu 4
\fIoddmu-html\fR(1), on how to render a page from the command-line
\fIoddmu-html\fR(1), on how to render a page
.IP \(bu 4
\fIoddmu-list\fR(1), on how to list pages and titles from the command-line
\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 from the
command-line
\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 from the command-line
\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 from the
command-line
\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 from the command-line
\fIoddmu-replace\fR(1), on how to search and replace text
.IP \(bu 4
\fIoddmu-search\fR(1), on how to run a search from the command-line
\fIoddmu-search\fR(1), on how to run a search
.IP \(bu 4
\fIoddmu-static\fR(1), on generating a static site from the command-line
\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 from the
command-line
\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
.SH AUTHORS
.PP
Maintained by Alex Schroeder <alex@gnu.\&org>.\&

View File

@@ -305,21 +305,22 @@ If you run Oddmu as a web server:
If you run Oddmu as a static site generator or pages offline and sync them with
Oddmu running as a webserver:
- _oddmu-hashtags_(1), on how to count the hashtags used from the command-line
- _oddmu-html_(1), on how to render a page from the command-line
- _oddmu-list_(1), on how to list pages and titles from the command-line
- _oddmu-links_(1), on how to list the outgoing links for a page from the
command-line
- _oddmu-missing_(1), on how to find broken local links from the command-line
- _oddmu-notify_(1), on updating index, changes and hashtag pages from the
command-line
- _oddmu-replace_(1), on how to search and replace text from the command-line
- _oddmu-search_(1), on how to run a search from the command-line
- _oddmu-static_(1), on generating a static site from the command-line
- _oddmu-toc_(1), on how to list the table of contents (toc) a page from the
command-line
- _oddmu-hashtags_(1), on how to count the hashtags used
- _oddmu-html_(1), on how to render a page
- _oddmu-list_(1), on how to list pages and titles
- _oddmu-links_(1), on how to list the outgoing links for a page
- _oddmu-missing_(1), on how to find broken local links
- _oddmu-notify_(1), on updating index, changes and hashtag pages
- _oddmu-replace_(1), on how to search and replace text
- _oddmu-search_(1), on how to run a search
- _oddmu-static_(1), on generating a static site
- _oddmu-toc_(1), on how to list the table of contents (toc) a page
- _oddmu-version_(1), on how to get all the build information from the binary
If you want to stop using Oddmu:
- _oddmu-export_(1), on how to export all the files as one big RSS file
# AUTHORS
Maintained by Alex Schroeder <alex@gnu.org>.

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU" "5" "2024-07-31" "File Formats Manual"
.TH "ODDMU" "5" "2024-08-17" "File Formats Manual"
.PP
.SH NAME
.PP
@@ -34,10 +34,9 @@ most importantly tables and definition lists.\&
.PP
.SS Local links
.PP
Local links use double square brackets [[like this]].\& Oddmu does not treat
underscores like spaces, so [[like this]] and [[like_this]] link to different
destinations and are served by different files: "like this.\&md" and
"like_this.\&md".\&
Local links use double square brackets.\& Oddmu does not treat underscores like
spaces, so "[[like this]]" and "[[like_this]]" link to different destinations
and are served by different files: "like this.\&md" and "like_this.\&md".\&
.PP
.SS Hashtags
.PP

View File

@@ -25,10 +25,9 @@ most importantly tables and definition lists.
## Local links
Local links use double square brackets [[like this]]. Oddmu does not treat
underscores like spaces, so [[like this]] and [[like_this]] link to different
destinations and are served by different files: "like this.md" and
"like_this.md".
Local links use double square brackets. Oddmu does not treat underscores like
spaces, so "[[like this]]" and "[[like_this]]" link to different destinations
and are served by different files: "like this.md" and "like_this.md".
## Hashtags

View File

@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "ODDMU.SERVICE" "5" "2024-07-24"
.TH "ODDMU.SERVICE" "5" "2024-08-23"
.PP
.SH NAME
.PP
@@ -49,8 +49,7 @@ Install the service file and enable it:
.PP
.nf
.RS 4
ln -s /home/oddmu/oddmu\&.service /etc/systemd/system/
systemctl enable --now oddmu
sudo systemctl enable --now \&./oddmu\&.service
.fi
.RE
.PP
@@ -112,10 +111,8 @@ To install, enable and start both units:
.PP
.nf
.RS 4
ln -s /home/oddmu/oddmu-unix-domain\&.socket /etc/systemd/system
ln -s /home/oddmu/oddmu-unix-domain\&.service /etc/systemd/system
systemctl enable --now oddmu-unix-domain\&.socket
systemctl enable --now oddmu-unix-domain\&.service
sudo systemctl enable --now \&./oddmu-unix-domain\&.socket
sudo systemctl enable --now \&./oddmu-unix-domain\&.service
.fi
.RE
.PP
@@ -134,8 +131,8 @@ Now you need to set up your web browser to use the Unix domain socket.\& See
.SS A personal wiki
.PP
On a single user machine, it might be useful to have a single wiki for the main
user available, on the standard port (80).\& In order to do this, setup a "user"
unit using systemd and save the following as "user-unix-domain.\&service":
user available.\& In order to do this, setup a "user" unit using systemd and save
the following as "user-unix-domain.\&service":
.PP
.nf
.RS 4
@@ -151,30 +148,18 @@ StandardOutput=journal
StandardError=journal
ExecStart=/home/alex/src/oddmu/oddmu
WorkingDirectory=/home/alex/wiki
Environment="ODDMU_PORT=80"
Environment="ODDMU_LANGUAGES=de,en"
.fi
.RE
.PP
Since this is a priviledged port, the binary needs an extra capability for an
ordinary user to do this.\& This is necessary so that the files are created and
owned by the same user.\& Otherwise, the regular user wouldn'\&t be able to edit the
files using their favourite text editor.\&
.PP
.nf
.RS 4
sudo setcap \&'cap_net_bind_service=+ep\&' oddmu
.fi
.RE
.PP
Note that as soon as you recomile, the capability is gone again and the above
must be repeated.\&
Since this is a user service, the same user can edit the files using their
favourite text editor.\&
.PP
Install it:
.PP
.nf
.RS 4
systemctl --user enable --now user-unix-domain\&.service
systemctl --user enable --now \&./user-unix-domain\&.service
.fi
.RE
.PP
@@ -186,6 +171,40 @@ journalctl --user --unit user-unix-domain\&.service
.fi
.RE
.PP
Note that no sudo is required!\&
.PP
.SS Using the priviledged port 80
.PP
When running a personal wiki, you can have the oddmu binary listen on port 80,
the standard HTTP port.\& It is not really worth the effort: It means that you can
visit "http://localhost/" instead of "http://localhost:8080".\& Nevertheless, if
you'\&re interested in giving it a try, here'\&s how to do it.\&
.PP
The service definition must specify the new port:
.PP
Environment="ODDMU_PORT=80"
.PP
Since this is a privileged port, the binary needs an extra capability for an
ordinary user to do this.\&
.PP
.nf
.RS 4
sudo setcap \&'cap_net_bind_service=+ep\&' oddmu
.fi
.RE
.PP
Note that as soon as you recompile, the capability is gone again and the above
must be repeated.\&
.PP
.SH SECURITY
.PP
Only allow direct access to Oddmu on systems and networks where you'\&re OK with
every user editing the pages.\& On the open web, this is not true.\& If your server
is on the open web, always run Oddmu behind a regular web server acting as a
reverse proxy, limiting regular visitors to read-only access.\& This means that
the regular web server listens on the regular privileged ports (80 for HTTP,
443 for HTTPS) and passes requests to Oddmu on some other port.\&
.PP
.SH SEE ALSO
.PP
\fIoddmu\fR(1), \fIoddmu-apache\fR(5), \fIoddmu-nginx\fR(5), \fIsystemd.\&exec\fR(5),
@@ -194,4 +213,3 @@ journalctl --user --unit user-unix-domain\&.service
.SH AUTHORS
.PP
Maintained by Alex Schroeder <alex@gnu.\&org>.\&
.PP

View File

@@ -37,8 +37,7 @@ Environment="ODDMU_WEBFINGER=1"
Install the service file and enable it:
```
ln -s /home/oddmu/oddmu.service /etc/systemd/system/
systemctl enable --now oddmu
sudo systemctl enable --now ./oddmu.service
```
You should be able to visit the wiki at http://localhost:8080/.
@@ -90,10 +89,8 @@ Environment="ODDMU_WEBFINGER=1"
To install, enable and start both units:
```
ln -s /home/oddmu/oddmu-unix-domain.socket /etc/systemd/system
ln -s /home/oddmu/oddmu-unix-domain.service /etc/systemd/system
systemctl enable --now oddmu-unix-domain.socket
systemctl enable --now oddmu-unix-domain.service
sudo systemctl enable --now ./oddmu-unix-domain.socket
sudo systemctl enable --now ./oddmu-unix-domain.service
```
To test just the unix domain socket, use _ncat(1)_:
@@ -109,8 +106,8 @@ _oddmu-apache_(5) or _oddmu-nginx_(5) for example configurations.
## A personal wiki
On a single user machine, it might be useful to have a single wiki for the main
user available, on the standard port (80). In order to do this, setup a "user"
unit using systemd and save the following as "user-unix-domain.service":
user available. In order to do this, setup a "user" unit using systemd and save
the following as "user-unix-domain.service":
```
[Unit]
@@ -125,26 +122,16 @@ StandardOutput=journal
StandardError=journal
ExecStart=/home/alex/src/oddmu/oddmu
WorkingDirectory=/home/alex/wiki
Environment="ODDMU_PORT=80"
Environment="ODDMU_LANGUAGES=de,en"
```
Since this is a priviledged port, the binary needs an extra capability for an
ordinary user to do this. This is necessary so that the files are created and
owned by the same user. Otherwise, the regular user wouldn't be able to edit the
files using their favourite text editor.
```
sudo setcap 'cap_net_bind_service=+ep' oddmu
```
Note that as soon as you recomile, the capability is gone again and the above
must be repeated.
Since this is a user service, the same user can edit the files using their
favourite text editor.
Install it:
```
systemctl --user enable --now user-unix-domain.service
systemctl --user enable --now ./user-unix-domain.service
```
To examine the log:
@@ -153,6 +140,38 @@ To examine the log:
journalctl --user --unit user-unix-domain.service
```
Note that no sudo is required!
## Using the priviledged port 80
When running a personal wiki, you can have the oddmu binary listen on port 80,
the standard HTTP port. It is not really worth the effort: It means that you can
visit "http://localhost/" instead of "http://localhost:8080". Nevertheless, if
you're interested in giving it a try, here's how to do it.
The service definition must specify the new port:
Environment="ODDMU_PORT=80"
Since this is a privileged port, the binary needs an extra capability for an
ordinary user to do this.
```
sudo setcap 'cap_net_bind_service=+ep' oddmu
```
Note that as soon as you recompile, the capability is gone again and the above
must be repeated.
# SECURITY
Only allow direct access to Oddmu on systems and networks where you're OK with
every user editing the pages. On the open web, this is not true. If your server
is on the open web, always run Oddmu behind a regular web server acting as a
reverse proxy, limiting regular visitors to read-only access. This means that
the regular web server listens on the regular privileged ports (80 for HTTP,
443 for HTTPS) and passes requests to Oddmu on some other port.
# SEE ALSO
_oddmu_(1), _oddmu-apache_(5), _oddmu-nginx_(5), _systemd.exec_(5),
@@ -161,4 +180,3 @@ _systemd.socket_(5), _capabilities_(7)
# AUTHORS
Maintained by Alex Schroeder <alex@gnu.org>.

27
man/scdoc-to-markdown Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/perl
use strict;
use warnings;
my $literal = 0;
while (<>) {
# bold
s/\*([^*]+)\*/**$1**/g;
# link to oddmu man pages (before italics)
s/_(oddmu[a-z.-]*)_\(([1-9])\)/[$1($2)]($1.$2)/g;
# italic
s/\b_([^_]+)_\b/*$1*/g;
# move all H1 headers to H2
s/^# /## /;
# the new H1 title
s/^([A-Z.-]*\([1-9]\))( ".*")?$/# $1/;
# quoted URLs
s/"(http.*?)"/`$1`/g;
# quoted wiki links
s/"(\[\[[^]]*\]\])"/`$1`/g;
# quoted Markdown links
s/"(\[.*?\]\(.*?\))"/`$1`/g;
# switch literal style
$literal = !$literal if /^```$/;
# protect hashtags except within literal blocks
s/#([^ #])/\\#$1/ unless $literal;
print;
}

View File

@@ -8,11 +8,9 @@ import (
"github.com/gomarkdown/markdown/ast"
"github.com/google/subcommands"
"io"
"io/fs"
"net/url"
"os"
"path"
"path/filepath"
"strings"
)
@@ -34,23 +32,19 @@ func (cmd *missingCmd) SetFlags(f *flag.FlagSet) {
}
func (cmd *missingCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
return missingCli(os.Stdout)
return missingCli(os.Stdout, &index)
}
func missingCli(w io.Writer) subcommands.ExitStatus {
names, err := existingPages()
if err != nil {
fmt.Fprintln(w, err)
return subcommands.ExitFailure
}
// missingCli implements the finding of links to missing pages. In order to make testing easier, it takes a Writer and
// an indexStore. The Writer is important so that test code can provide a buffer instead of os.Stdout; the indexStore 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 missingCli(w io.Writer, idx *indexStore) subcommands.ExitStatus {
found := false
for name, isPage := range names {
if !isPage {
continue
}
for name := range idx.titles {
p, err := loadPage(name)
if err != nil {
fmt.Fprintf(os.Stderr, "Loading %s: %s\n", p.Name, err)
fmt.Fprintf(os.Stderr, "Loading %s: %s\n", name, err)
return subcommands.ExitFailure
}
for _, link := range p.links() {
@@ -66,13 +60,17 @@ func missingCli(w io.Writer) subcommands.ExitStatus {
u.Path = strings.TrimSuffix(u.Path, ".md")
// pages containing a colon need the ./ prefix
u.Path = strings.TrimPrefix(u.Path, "./")
// check whether the destinatino is a known page
// check whether the destination is a known page
destination, err := url.PathUnescape(u.Path)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot decode %s: %s\n", link, err)
return subcommands.ExitFailure
}
_, ok := names[destination]
_, ok := idx.titles[destination]
// links to directories can work
if !ok {
_, ok = idx.titles[path.Join(destination, "index")]
}
if !ok {
if !found {
fmt.Fprintln(w, "Page\tMissing")
@@ -89,31 +87,6 @@ func missingCli(w io.Writer) subcommands.ExitStatus {
return subcommands.ExitSuccess
}
func existingPages() (map[string]bool, error) {
names := make(map[string]bool)
err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// skip hidden directories and files
if path != "." && strings.HasPrefix(filepath.Base(path), ".") {
if info.IsDir() {
return filepath.SkipDir
} else {
return nil
}
}
if strings.HasSuffix(path, ".md") {
name := filepath.ToSlash(strings.TrimSuffix(path, ".md"))
names[name] = true
} else {
names[path] = false
}
return nil
})
return names, err
}
// links parses the page content and returns an array of link destinations.
func (p *Page) links() []string {
var links []string

View File

@@ -9,7 +9,7 @@ import (
func TestMissingCmd(t *testing.T) {
b := new(bytes.Buffer)
s := missingCli(b)
s := missingCli(b, minimalIndex(t))
assert.Equal(t, subcommands.ExitSuccess, s)
r := `Page Missing
index test

View File

@@ -85,7 +85,7 @@ func wikiRenderer() *html.Renderer {
return renderer
}
// renderHtml renders the Page.Body to HTML and sets Page.Html, Page.Language, Page.Hashtags, and escapes Page.Name.
// renderHtml renders the Page.Body to HTML and sets Page.Html, Page.Hashtags, and escapes Page.Name.
func (p *Page) renderHtml() {
parser, hashtags := wikiParser()
renderer := wikiRenderer()

View File

@@ -1,19 +1,21 @@
html { max-width: 80ch; padding: 1ch; margin: auto; color: #111; background-color: #ffe }
body { hyphens: auto }
html { max-width: 80ch; padding: 1ch; margin: auto }
body { hyphens: auto; color: #111; background-color: #ffe }
header a { margin-right: 1ch }
h1 { text-wrap: balance }
footer { border-top: 1px solid #888 }
form, textarea { width: 97%; font-size: inherit }
pre { background-color: #fff; padding: 1ex 2ex; overflow-x: scroll }
pre { background-color: #fff; padding: 1ex 2ex; overflow-x: auto }
.diff { font-size: inherit; white-space: normal; overflow-wrap: break-word; background-color: white; border: 1px solid #333; padding: 1ch }
img { max-width: 100%; max-height: 90vh }
img, video { max-width: 100%; max-height: 90vh; width: auto; height: auto }
.right img { float: right; margin-left: 2em; margin-bottom: 1em; border: 1px solid #111 }
.left img { float: left; margin-right: 2em; margin-bottom: 1em; border: 1px solid #111 }
button { background-color: #eee; color: inherit; border-radius: 4px; border-width: 1px }
del { background-color: #fab }
ins { background-color: #af8 }
#search, #id { width: 30ch }
#search, #id { max-width: 30ch; width: calc(100% - 23ch) }
.result { font-size: larger }
.score { font-size: smaller; opacity: 0.8 }
.image { font-size: small; display: inline-block; margin-right: 1em; max-width: calc(20% - 1em); }
#view form { margin-top: 2px }
#view button { width: 6ch }
#view label { display: inline-block; width: 10ch }
@@ -25,7 +27,7 @@ th { font-weight: normal }
th + th, td + td { padding-left: 1em }
@media (prefers-color-scheme: dark) {
html { color: #eeeee8; background-color: #333 }
body { color: #eeeee8; background-color: #333 }
footer { border-top: 1px solid #666 }
.diff { background-color: inherit; border: 1px solid #666 }
.right img { border: 1px solid #111 }

View File

@@ -81,8 +81,8 @@ func (p *Page) toc() Toc {
// other level one headings, this is a "regular" table of contents. For a regular table of contents, the first entry is
// skipped.
func (toc Toc) print(w io.Writer) {
minLevel := 0;
levelOneCount := 0;
minLevel := 0
levelOneCount := 0
for _, h := range toc {
if h.Level == 1 {
levelOneCount++
@@ -101,7 +101,7 @@ func (toc Toc) print(w io.Writer) {
}
fmt.Fprint(w, "* [")
for _, c := range h.GetChildren() {
fmt.Fprint(w, string(c.AsLeaf().Literal));
fmt.Fprint(w, string(c.AsLeaf().Literal))
}
fmt.Fprintf(w, "](#%s)\n", h.HeadingID)
}

View File

@@ -5,7 +5,7 @@ package main
import (
"fmt"
_ "github.com/bashdrew/goheif"
_ "github.com/gen2brain/heic"
"github.com/disintegration/imaging"
"github.com/edwvee/exiffix"
"io"

View File

@@ -200,6 +200,7 @@ func commands() {
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&exportCmd{}, "")
subcommands.Register(&hashtagsCmd{}, "")
subcommands.Register(&htmlCmd{}, "")
subcommands.Register(&listCmd{}, "")

View File

@@ -7,6 +7,7 @@ import (
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"slices"
"strings"
"testing"
@@ -114,3 +115,19 @@ func cleanup(t *testing.T, dir string) {
}
})
}
// minimalIndex creates a new indexStore containing just the known Markdown files without any additional Markdown files
// from testdata.
func minimalIndex(t *testing.T) *indexStore {
idx := &indexStore{}
idx.reset()
names := []string{"index", "README"}
for _, name := range names {
p, err := loadPage(name)
assert.NoError(t, err)
idx.addPage(p)
}
err := filepath.Walk("themes", idx.walk)
assert.NoError(t, err)
return idx
}