From 6586db782ea6dcb5f2eb191a690ec7e7df51161f Mon Sep 17 00:00:00 2001 From: tjpcc Date: Tue, 17 Jan 2023 16:41:04 -0700 Subject: Updates * update README * move "gemtext" to within "gemini" --- gemini/gemtext/mdconv/convert.go | 78 +++++++++++++++++++++++++ gemini/gemtext/mdconv/convert_test.go | 103 ++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 gemini/gemtext/mdconv/convert.go create mode 100644 gemini/gemtext/mdconv/convert_test.go (limited to 'gemini/gemtext/mdconv') diff --git a/gemini/gemtext/mdconv/convert.go b/gemini/gemtext/mdconv/convert.go new file mode 100644 index 0000000..c2f434d --- /dev/null +++ b/gemini/gemtext/mdconv/convert.go @@ -0,0 +1,78 @@ +package mdconv + +import ( + "io" + "text/template" + + "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 a gemtext.LinkLine. +// - "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.AddAllTemplates(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("mdconv").Parse(` +{{ define "header" }}{{ end }} +{{ define "textline" }}{{ if ne .String "\n" }} +{{ . }}{{ end }}{{ end }} +{{ define "linkline" }} +=> [{{ if eq .Label "" }}{{ .URL }}{{ else }}{{ .Label }}{{ end }}]({{ .URL }}) +{{ end }} +{{ define "preformattedtextlines" }}` + "\n```\n" + `{{ range . }}{{ . }}{{ end }}` + "```\n" + `{{ end }} +{{ define "heading1line" }} +# {{ .Body }} +{{ end }} +{{ define "heading2line" }} +## {{ .Body }} +{{ end }} +{{ define "heading3line" }} +### {{ .Body }} +{{ end }} +{{ define "listitemlines" }} +{{ range . }}* {{ .Body }} +{{ end }}{{ end }} +{{ define "quoteline" }} +> {{ .Body }} +{{ end }} +{{ define "footer" }}{{ end }} +`)) diff --git a/gemini/gemtext/mdconv/convert_test.go b/gemini/gemtext/mdconv/convert_test.go new file mode 100644 index 0000000..c8fd53c --- /dev/null +++ b/gemini/gemtext/mdconv/convert_test.go @@ -0,0 +1,103 @@ +package mdconv_test + +import ( + "bytes" + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "tildegit.org/tjp/gus/gemini/gemtext" + "tildegit.org/tjp/gus/gemini/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 + +` + "```\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:] + "```\npf: doc := gemtext.Parse(req.Body)\n```\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 "preformattedtextlines"}}` + "```\n" + `{{range . }}pf: {{.}}{{end}}` + "```\n" + `{{end}} + {{define "heading1line"}}# h1: {{.Body}}` + "\n" + `{{end}} + {{define "heading2line"}}## h2: {{.Body}}` + "\n" + `{{end}} + {{define "heading3line"}}### h3: {{.Body}}` + "\n" + `{{end}} + {{define "listitemlines"}}{{range .}}* li: {{.Body}}` + "\n" + `{{end}}{{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()) +} -- cgit v1.2.3