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 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 } diff --git a/internal/gemini/renderer.go b/internal/gemini/renderer.go index 6c75610..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'} @@ -86,9 +88,16 @@ 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) + 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 + w.Write(lineBreak) + w.Write(lineBreak) + } } } } @@ -137,6 +146,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 } @@ -251,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", " ") @@ -329,7 +354,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