From 845bfa7cb263d6bdb8049d088c71017ead4cd63c Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Fri, 13 Aug 2021 08:52:52 +0500 Subject: [PATCH 1/4] Fix panic on empty blockquotes While renderer previously assumed there would always be a single paragraph inside the blockquote, there sometimes can be either more or none. See #5. --- internal/gemini/renderer.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/gemini/renderer.go b/internal/gemini/renderer.go index 6c75610..c022e14 100644 --- a/internal/gemini/renderer.go +++ b/internal/gemini/renderer.go @@ -86,9 +86,17 @@ func (r Renderer) blockquote(w io.Writer, node *ast.BlockQuote, entering bool) { // TODO: Renderer.blockquote: needs support for subnode rendering; // ideally to be merged with paragraph if entering { - if para, ok := node.Children[0].(*ast.Paragraph); ok { - w.Write(quotePrefix) - r.text(w, para) + if node := node.AsContainer(); node != nil { + for _, child := range node.Children { + w.Write(quotePrefix) + // assume children would be paragraphs + r.text(w, child) + // double linebreak to ensure Gemini clients don't merge + // quotes; gomarkdown assumes separate blockquotes are + // paragraphs of the same blockquote while we don't + w.Write(lineBreak) + w.Write(lineBreak) + } } } } @@ -137,6 +145,7 @@ func (r Renderer) paragraph(w io.Writer, node *ast.Paragraph, entering bool) (no } linksOnly := func() bool { for _, child := range children { + // TODO: simplify if _, ok := child.(*ast.Link); ok { continue } @@ -329,7 +338,6 @@ func (r Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Walk switch node := node.(type) { case *ast.BlockQuote: r.blockquote(w, node, entering) - noNewLine = false case *ast.Heading: r.heading(w, node, entering) noNewLine = false From adcf0ac73e9d47dacb7b10a0be9910612def28d9 Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Fri, 13 Aug 2021 09:00:37 +0500 Subject: [PATCH 2/4] Make linter happy This removes some of the leftovers of older logic in cmd/gmnhg. --- cmd/gmnhg/main.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/gmnhg/main.go b/cmd/gmnhg/main.go index 98f1ee7..f21637b 100644 --- a/cmd/gmnhg/main.go +++ b/cmd/gmnhg/main.go @@ -80,9 +80,9 @@ import ( ) const ( - defaultTemplate = "single" - indexMdFilename = "_index.gmi.md" - indexFilename = "index.gmi" + defaultPageTemplate = "single" + indexMdFilename = "_index.gmi.md" + indexFilename = "index.gmi" ) const ( @@ -94,7 +94,6 @@ const ( var ( tmplNameRegex = regexp.MustCompile(templateBase + `([\w-_ /]+)\.gotmpl`) - contentNameRegex = regexp.MustCompile(contentBase + `([\w-_ ]+)\.md`) topLevelPostRegex = regexp.MustCompile(contentBase + `([\w-_ ]+)/([\w-_ ]+)\.md`) ) @@ -201,7 +200,7 @@ func main() { } // render posts to Gemtext and collect top level posts data - posts := make(map[string]*post, 0) + posts := make(map[string]*post) topLevelPosts := make(map[string][]*post) if err := filepath.Walk(contentBase, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -252,7 +251,7 @@ func main() { } var singleTemplate = defaultSingleTemplate - if tmpl, hasTmpl := templates["single"]; hasTmpl { + if tmpl, hasTmpl := templates[defaultPageTemplate]; hasTmpl { singleTemplate = tmpl } From 35977a721c3087505613cd6c9653a2a4d3638513 Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Fri, 13 Aug 2021 09:08:45 +0500 Subject: [PATCH 3/4] Add golangci-lint config Somehow the config managed to stay in .gitignore a while, causing CI checks to fail. --- .golangci.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..9f70b67 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,4 @@ +linters: + disable: + # no, we won't check every invocation of (io.Writer).Write() + - errcheck From 36922b6e952ddd87bedca16e38b34b913fcb6236 Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Fri, 13 Aug 2021 09:49:06 +0500 Subject: [PATCH 4/4] Keep line breaks in blockquotes This makes the renderer preserve existing line breaks in blockquotes, provided they do not split paragraphs. Some clients/sites may use this to form semantic around line breaks (for instance, for poems). See #5. --- internal/gemini/renderer.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/gemini/renderer.go b/internal/gemini/renderer.go index c022e14..39702ec 100644 --- a/internal/gemini/renderer.go +++ b/internal/gemini/renderer.go @@ -18,6 +18,7 @@ package gemini import ( + "bytes" "fmt" "io" "regexp" @@ -32,6 +33,7 @@ var ( lineBreak = []byte{'\n'} space = []byte{' '} linkPrefix = []byte("=> ") + quoteBrPrefix = []byte("\n> ") quotePrefix = []byte("> ") itemPrefix = []byte("* ") itemIndent = []byte{'\t'} @@ -89,8 +91,7 @@ func (r Renderer) blockquote(w io.Writer, node *ast.BlockQuote, entering bool) { if node := node.AsContainer(); node != nil { for _, child := range node.Children { w.Write(quotePrefix) - // assume children would be paragraphs - r.text(w, child) + r.blockquoteText(w, child) // double linebreak to ensure Gemini clients don't merge // quotes; gomarkdown assumes separate blockquotes are // paragraphs of the same blockquote while we don't @@ -260,6 +261,21 @@ func (r Renderer) text(w io.Writer, node ast.Node) { } } +// TODO: this really should've been unified with text(), but having two +// extra params for prefix/line breaks is not neat +func (r Renderer) blockquoteText(w io.Writer, node ast.Node) { + if node := node.AsLeaf(); node != nil { + // pad every line break with blockquote symbol + w.Write([]byte(bytes.ReplaceAll(node.Literal, lineBreak, quoteBrPrefix))) + return + } + if node := node.AsContainer(); node != nil { + for _, child := range node.Children { + r.blockquoteText(w, child) + } + } +} + func extractText(node ast.Node) string { if node := node.AsLeaf(); node != nil { return strings.ReplaceAll(string(node.Literal), "\n", " ")