From 7e1ecc4df44d20d2c9de1c8885ddc2c188062ef0 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 3 Jul 2024 12:36:05 +0200 Subject: Initial implementation of the gemtext template extension --- http/handlers/templates/functions/functions.go | 3 + http/handlers/templates/functions/gemtext.go | 153 +++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 http/handlers/templates/functions/functions.go create mode 100644 http/handlers/templates/functions/gemtext.go (limited to 'http') diff --git a/http/handlers/templates/functions/functions.go b/http/handlers/templates/functions/functions.go new file mode 100644 index 0000000..dd2101c --- /dev/null +++ b/http/handlers/templates/functions/functions.go @@ -0,0 +1,3 @@ +// Package functions makes available extra functions within templates being +// processed by the `http.handlers.templates` directive. +package functions diff --git a/http/handlers/templates/functions/gemtext.go b/http/handlers/templates/functions/gemtext.go new file mode 100644 index 0000000..5419290 --- /dev/null +++ b/http/handlers/templates/functions/gemtext.go @@ -0,0 +1,153 @@ +package functions + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "html" + "io" + "strings" + "text/template" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates" +) + +func init() { + caddy.RegisterModule(Gemtext{}) + httpcaddyfile.RegisterDirective( + "gemtext", + func(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) { + var f Gemtext + err := f.UnmarshalCaddyfile(h.Dispenser) + return []httpcaddyfile.ConfigValue{{ + Class: "template_function", Value: f, + }}, err + }, + ) +} + +type Gemtext struct{} + +var _ templates.CustomFunctions = (*Gemtext)(nil) + +func (f *Gemtext) CustomTemplateFunctions() template.FuncMap { + return template.FuncMap{ + "gemtext": f.funcGemtext, + } +} + +func (Gemtext) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.templates.functions.gemtext", + New: func() caddy.Module { return new(Gemtext) }, + } +} + +func sanitizeText(str string) string { + return html.EscapeString(strings.TrimSpace(str)) +} + +func (*Gemtext) funcGemtext(input any) (string, error) { + var ( + r = bufio.NewReader(strings.NewReader(caddy.ToString(input))) + w = new(bytes.Buffer) + pft, list bool + writeErr error + ) + + write := func(fmtStr string, args ...any) { + if writeErr != nil { + return + } + fmt.Fprintf(w, fmtStr, args...) + } + +loop: + for { + if writeErr != nil { + return "", fmt.Errorf("writing line: %w", writeErr) + } + + line, err := r.ReadString('\n') + + switch { + case errors.Is(err, io.EOF): + break loop + + case err != nil: + return "", fmt.Errorf("reading next line: %w", err) + + case strings.HasPrefix(line, "```"): + if !pft { + write("
\n")
+				pft = true
+			} else {
+				write("
\n") + pft = false + } + continue + + case pft: + write(line) + continue + + case len(strings.TrimSpace(line)) == 0: + continue + } + + // list case is special, because it requires a prefix and suffix tag + if strings.HasPrefix(line, "*") { + if !list { + write("\n") + list = false + } + + switch { + case strings.HasPrefix(line, "=>"): + // TODO convert gemini:// links ? + var ( + line = strings.TrimSpace(line[2:]) + urlStr = line + label = urlStr + ) + if i := strings.IndexAny(urlStr, " \t"); i > -1 { + urlStr, label = urlStr[:i], sanitizeText(urlStr[i:]) + } + write("

%s

\n", urlStr, label) + + case strings.HasPrefix(line, "###"): + write("

%s

\n", sanitizeText(line[3:])) + + case strings.HasPrefix(line, "##"): + write("

%s

\n", sanitizeText(line[2:])) + + case strings.HasPrefix(line, "#"): + write("

%s

\n", sanitizeText(line[1:])) + + case strings.HasPrefix(line, ">"): + write("
%s
\n", sanitizeText(line[1:])) + + default: + line = strings.TrimSpace(line) + write("

%s

\n", line) + } + } + + return w.String(), nil +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (*Gemtext) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + return nil +} -- cgit v1.2.3