diff options
Diffstat (limited to 'http/handlers/templates/functions')
-rw-r--r-- | http/handlers/templates/functions/functions.go | 3 | ||||
-rw-r--r-- | http/handlers/templates/functions/gemtext.go | 153 |
2 files changed, 156 insertions, 0 deletions
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("<pre>\n") + pft = true + } else { + write("</pre>\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("<ul>\n") + } + write("<li>%s</li>\n", sanitizeText(line[1:])) + list = true + continue + } else if list { + write("</ul>\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("<p><a href=\"%s\">%s</a></p>\n", urlStr, label) + + case strings.HasPrefix(line, "###"): + write("<h3>%s</h3>\n", sanitizeText(line[3:])) + + case strings.HasPrefix(line, "##"): + write("<h2>%s</h2>\n", sanitizeText(line[2:])) + + case strings.HasPrefix(line, "#"): + write("<h1>%s</h1>\n", sanitizeText(line[1:])) + + case strings.HasPrefix(line, ">"): + write("<blockquote>%s</blockquote>\n", sanitizeText(line[1:])) + + default: + line = strings.TrimSpace(line) + write("<p>%s</p>\n", line) + } + } + + return w.String(), nil +} + +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. +func (*Gemtext) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume directive name + return nil +} |