summaryrefslogtreecommitdiff
path: root/gemtext
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-01-14 19:59:12 -0700
committertjpcc <tjp@ctrl-c.club>2023-01-14 19:59:12 -0700
commitcec3718bdd089bcf58575740c5ae4f86b27226d1 (patch)
tree497733255e0459d2a6bcbd059ea3e124d602d1b2 /gemtext
parent88b98dcf18f9bc9b098a574c96deabf9d323a997 (diff)
markdown converter
Diffstat (limited to 'gemtext')
-rw-r--r--gemtext/mdconv/convert.go72
-rw-r--r--gemtext/mdconv/convert_test.go102
-rw-r--r--gemtext/types.go10
3 files changed, 184 insertions, 0 deletions
diff --git a/gemtext/mdconv/convert.go b/gemtext/mdconv/convert.go
new file mode 100644
index 0000000..0c92f9f
--- /dev/null
+++ b/gemtext/mdconv/convert.go
@@ -0,0 +1,72 @@
+package mdconv
+
+import (
+ "fmt"
+ "io"
+ "text/template"
+
+ "tildegit.org/tjp/gus/gemtext"
+)
+
+func Convert(wr io.Writer, doc gemtext.Document, overrides *template.Template) error {
+ tmpl, err := baseTmpl.Clone()
+ if err != nil {
+ return err
+ }
+
+ if overrides != nil {
+ for _, override := range overrides.Templates() {
+ tmpl, err = tmpl.AddParseTree(override.Name(), override.Tree)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return tmpl.ExecuteTemplate(wr, "mdconv", doc)
+}
+
+var baseTmpl = template.Must(template.New("mdconv").Parse(fmt.Sprintf((`
+{{block "header" .}}{{end -}}
+{{range . -}}
+{{if .Type | eq %d}}{{block "textline" . -}}
+ {{. -}}
+{{end -}}
+{{else if .Type | eq %d}}{{block "linkline" . -}}
+ => [{{if eq .Label ""}}{{.URL}}{{else}}{{.Label}}{{end}}]({{.URL}})
+{{end -}}
+{{else if .Type | eq %d}}{{block "preformattoggleline" . -}}
+ ` + "```" + `
+{{end -}}
+{{else if .Type | eq %d}}{{block "preformattedtextline" . -}}
+ {{. -}}
+{{end -}}
+{{else if .Type | eq %d}}{{block "heading1line" . -}}
+ # {{.Body}}
+{{end -}}
+{{else if .Type | eq %d}}{{block "heading2line" . -}}
+ ## {{.Body}}
+{{end -}}
+{{else if .Type | eq %d}}{{block "heading3line" . -}}
+ ### {{.Body}}
+{{end -}}
+{{else if .Type | eq %d}}{{block "listitemline" . -}}
+ * {{.Body}}
+{{end -}}
+{{else if .Type | eq %d}}{{block "quoteline" . -}}
+ > {{.Body}}
+{{end -}}
+{{end -}}
+{{end -}}
+{{block "footer" .}}{{end -}}
+`)[1:],
+ gemtext.LineTypeText,
+ gemtext.LineTypeLink,
+ gemtext.LineTypePreformatToggle,
+ gemtext.LineTypePreformattedText,
+ gemtext.LineTypeHeading1,
+ gemtext.LineTypeHeading2,
+ gemtext.LineTypeHeading3,
+ gemtext.LineTypeListItem,
+ gemtext.LineTypeQuote,
+)))
diff --git a/gemtext/mdconv/convert_test.go b/gemtext/mdconv/convert_test.go
new file mode 100644
index 0000000..6cde08b
--- /dev/null
+++ b/gemtext/mdconv/convert_test.go
@@ -0,0 +1,102 @@
+package mdconv_test
+
+import (
+ "bytes"
+ "testing"
+ "text/template"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "tildegit.org/tjp/gus/gemtext"
+ "tildegit.org/tjp/gus/gemtext/mdconv"
+)
+
+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) {
+ mdDoc := `
+# top-level header line
+
+## subtitle
+
+This is some non-blank regular text.
+
+* an
+* unordered
+* list
+
+=> [as if](gemini://google.com/)
+=> [https://google.com/](https://google.com/)
+
+> this is a quote
+> -tjp
+
+`[1:] + "```\ndoc := gemtext.Parse(req.Body)\n```\n"
+
+ doc, err := gemtext.Parse(bytes.NewBufferString(gmiDoc))
+ require.Nil(t, err)
+
+ buf := &bytes.Buffer{}
+ require.Nil(t, mdconv.Convert(buf, doc, nil))
+
+ assert.Equal(t, mdDoc, buf.String())
+}
+
+func TestConvertWithOverrides(t *testing.T) {
+ mdDoc := `
+# h1: top-level header line
+text:
+## h2: subtitle
+text:
+text: This is some non-blank regular text.
+text:
+* li: an
+* li: unordered
+* li: list
+text:
+=> link: [as if](gemini://google.com/)
+=> link: [https://google.com/](https://google.com/)
+text:
+> quote: this is a quote
+> quote: -tjp
+text:
+`[1:] + "pftoggle: ```\npf: doc := gemtext.Parse(req.Body)\npftoggle: ```\n"
+
+ overrides := template.Must(template.New("overrides").Parse((`
+ {{define "textline"}}text: {{.}}{{end}}
+ {{define "linkline"}}=> link: [{{if eq .Label ""}}{{.URL}}{{else}}{{.Label}}{{end}}]({{.URL}})` + "\n" + `{{end}}
+ {{define "preformattoggleline"}}pftoggle: ` + "```\n" + `{{end}}
+ {{define "preformattedtextline"}}pf: {{.}}{{end}}
+ {{define "heading1line"}}# h1: {{.Body}}` + "\n" + `{{end}}
+ {{define "heading2line"}}## h2: {{.Body}}` + "\n" + `{{end}}
+ {{define "heading3line"}}### h3: {{.Body}}` + "\n" + `{{end}}
+ {{define "listitemline"}}* li: {{.Body}}` + "\n" + `{{end}}
+ {{define "quoteline"}}> quote: {{.Body}}` + "\n" + `{{end}}
+ `)[1:]))
+
+ doc, err := gemtext.Parse(bytes.NewBufferString(gmiDoc))
+ require.Nil(t, err)
+
+ buf := &bytes.Buffer{}
+ require.Nil(t, mdconv.Convert(buf, doc, overrides))
+
+ assert.Equal(t, mdDoc, buf.String())
+}
diff --git a/gemtext/types.go b/gemtext/types.go
index fefbece..440fed4 100644
--- a/gemtext/types.go
+++ b/gemtext/types.go
@@ -75,6 +75,9 @@ type Line interface {
// Raw reproduces the original bytes from the source reader.
Raw() []byte
+
+ // String represents the original bytes from the source reader as a string.
+ String() string
}
// Document is the list of lines that make up a full text/gemini resource.
@@ -87,6 +90,7 @@ type TextLine struct {
func (tl TextLine) Type() LineType { return LineTypeText }
func (tl TextLine) Raw() []byte { return tl.raw }
+func (tl TextLine) String() string { return string(tl.raw) }
// LinkLine is a line of LineTypeLink.
type LinkLine struct {
@@ -97,6 +101,7 @@ type LinkLine struct {
func (ll LinkLine) Type() LineType { return LineTypeLink }
func (ll LinkLine) Raw() []byte { return ll.raw }
+func (ll LinkLine) String() string { return string(ll.raw) }
// URL returns the original url portion of the line.
//
@@ -114,6 +119,7 @@ type PreformatToggleLine struct {
func (tl PreformatToggleLine) Type() LineType { return LineTypePreformatToggle }
func (tl PreformatToggleLine) Raw() []byte { return tl.raw }
+func (tl PreformatToggleLine) String() string { return string(tl.raw) }
// AltText returns the alt-text portion of the line.
//
@@ -135,6 +141,7 @@ type PreformattedTextLine struct {
func (tl PreformattedTextLine) Type() LineType { return LineTypePreformattedText }
func (tl PreformattedTextLine) Raw() []byte { return tl.raw }
+func (tl PreformattedTextLine) String() string { return string(tl.raw) }
// HeadingLine is a line of LineTypeHeading[1,2,3].
type HeadingLine struct {
@@ -145,6 +152,7 @@ type HeadingLine struct {
func (hl HeadingLine) Type() LineType { return hl.lineType }
func (hl HeadingLine) Raw() []byte { return hl.raw }
+func (hl HeadingLine) String() string { return string(hl.raw) }
// Body returns the portion of the line with the header text.
func (hl HeadingLine) Body() string { return string(hl.body) }
@@ -157,6 +165,7 @@ type ListItemLine struct {
func (li ListItemLine) Type() LineType { return LineTypeListItem }
func (li ListItemLine) Raw() []byte { return li.raw }
+func (li ListItemLine) String() string { return string(li.raw) }
// Body returns the text of the list item.
func (li ListItemLine) Body() string { return string(li.body) }
@@ -169,6 +178,7 @@ type QuoteLine struct {
func (ql QuoteLine) Type() LineType { return LineTypeQuote }
func (ql QuoteLine) Raw() []byte { return ql.raw }
+func (ql QuoteLine) String() string { return string(ql.raw) }
// Body returns the text of the quote.
func (ql QuoteLine) Body() string { return string(ql.body) }