summaryrefslogtreecommitdiff
path: root/gemini/gemtext/htmlconv
diff options
context:
space:
mode:
Diffstat (limited to 'gemini/gemtext/htmlconv')
-rw-r--r--gemini/gemtext/htmlconv/convert.go86
-rw-r--r--gemini/gemtext/htmlconv/convert_test.go46
2 files changed, 132 insertions, 0 deletions
diff --git a/gemini/gemtext/htmlconv/convert.go b/gemini/gemtext/htmlconv/convert.go
new file mode 100644
index 0000000..5028766
--- /dev/null
+++ b/gemini/gemtext/htmlconv/convert.go
@@ -0,0 +1,86 @@
+package htmlconv
+
+import (
+ "html/template"
+ "io"
+
+ "tildegit.org/tjp/gus/gemini/gemtext"
+ "tildegit.org/tjp/gus/gemini/gemtext/internal"
+)
+
+// Convert writes markdown to a writer from the provided gemtext document.
+//
+// Templates can be provided to override the output for different line types.
+// The templates supported are:
+// - "header" is called before any lines and is passed the full Document.
+// - "footer" is called after the lines and is passed the full Document.
+// - "textline" is called once per line of text and is passed a gemtext.TextLine.
+// - "linkline" is called once per link line and is passed an object which wraps
+// a gemtext.LinkLine but also supports a ValidatedURL() method returning a
+// string which html/template will always allow as href attributes.
+// - "preformattedtextlines" is called once for a block of preformatted text and is
+// passed a slice of gemtext.PreformattedTextLines.
+// - "heading1line" is called once per h1 line and is passed a gemtext.Heading1Line.
+// - "heading2line" is called once per h2 line and is passed a gemtext.Heading2Line.
+// - "heading3line" is called once per h3 line and is passed a gemtext.Heading3Line.
+// - "listitemlines" is called once for a block of contiguous list item lines and
+// is passed a slice of gemtext.ListItemLines.
+// - "quoteline" is passed once per blockquote line and is passed a gemtext.QuoteLine.
+//
+// There exist default implementations of each of these templates, so the "overrides"
+// argument can be nil.
+func Convert(wr io.Writer, doc gemtext.Document, overrides *template.Template) error {
+ if err := internal.ValidateLinks(doc); err != nil {
+ return err
+ }
+
+ tmpl, err := baseTmpl.Clone()
+ if err != nil {
+ return err
+ }
+
+ tmpl, err = internal.AddHTMLTemplates(tmpl, overrides)
+ if err != nil {
+ return err
+ }
+
+ for _, item := range internal.RenderItems(doc) {
+ if err := tmpl.ExecuteTemplate(wr, item.Template, item.Object); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+var baseTmpl = template.Must(template.New("htmlconv").Parse(`
+{{ define "header" }}<html><body>{{ end }}
+{{ define "textline" }}{{ if ne .String "\n" }}<p>{{ . }}</p>{{ end }}{{ end }}
+{{ define "linkline" -}}
+ <p>=> <a href="{{ .ValidatedURL }}">{{ if eq .Label "" -}}
+ {{ .URL }}
+ {{- else -}}
+ {{ .Label }}
+ {{- end -}}
+ </a></p>
+{{- end }}
+{{ define "preformattedtextlines" -}}
+ <pre>
+ {{- range . -}}
+ {{ . }}
+ {{- end -}}
+ </pre>
+{{- end }}
+{{ define "heading1line" }}<h1>{{ .Body }}</h1>{{ end }}
+{{ define "heading2line" }}<h2>{{ .Body }}</h2>{{ end }}
+{{ define "heading3line" }}<h3>{{ .Body }}</h3>{{ end }}
+{{ define "listitemlines" -}}
+ <ul>
+ {{- range . -}}
+ <li>{{ .Body }}</li>
+ {{- end -}}
+ </ul>
+{{- end }}
+{{ define "quoteline" }}<blockquote>{{ .Body }}</blockquote>{{ end }}
+{{ define "footer" }}</body></html>{{ end }}
+`))
diff --git a/gemini/gemtext/htmlconv/convert_test.go b/gemini/gemtext/htmlconv/convert_test.go
new file mode 100644
index 0000000..641ffb8
--- /dev/null
+++ b/gemini/gemtext/htmlconv/convert_test.go
@@ -0,0 +1,46 @@
+package htmlconv_test
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "tildegit.org/tjp/gus/gemini/gemtext"
+ "tildegit.org/tjp/gus/gemini/gemtext/htmlconv"
+)
+
+var gmiDoc = `
+# top-level header line
+
+## subtitle
+
+This is some non-blank regular text.
+
+* an
+* unordered
+* list
+
+=> gemini://google.com/ as if
+=> https://google.com/
+
+> this is a quote
+> -tjp
+
+`[1:] + "```pre-formatted code\ndoc := gemtext.Parse(req.Body)\n```ignored closing alt-text\n"
+
+func TestConvert(t *testing.T) {
+ htmlDoc := `
+<html><body><h1>top-level header line</h1><h2>subtitle</h2><p>This is some non-blank regular text.
+</p><ul><li>an</li><li>unordered</li><li>list</li></ul><p>=> <a href="gemini://google.com/">as if</a></p><p>=> <a href="https://google.com/">https://google.com/</a></p><blockquote> this is a quote</blockquote><blockquote> -tjp</blockquote><pre>doc := gemtext.Parse(req.Body)
+</pre></body></html>`[1:]
+
+ doc, err := gemtext.Parse(bytes.NewBufferString(gmiDoc))
+ require.Nil(t, err)
+
+ buf := &bytes.Buffer{}
+ require.Nil(t, htmlconv.Convert(buf, doc, nil))
+
+ assert.Equal(t, htmlDoc, buf.String())
+}