package bfg import ( "fmt" "io" "strings" bf "github.com/russross/blackfriday/v2" ) type Link struct { URL []byte Description []byte } type GeminiRenderer struct { footLinks []Link footLinksOffset int inLink bool isVerbatim bool isQuoting bool } func NewGeminiRenderer() *GeminiRenderer { return &GeminiRenderer{} } func (r *GeminiRenderer) nl(w io.Writer) { w.Write([]byte{'\n'}) } func (r *GeminiRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus { switch node.Type { case bf.Text: if entering { var s string if r.isVerbatim { if r.isQuoting { s = "> " + strings.ReplaceAll(string(node.Literal), "\n", "\n> ") + "\n" } else { s = string(node.Literal) } } else { if node.Parent.Type == bf.Link { r.footLinks[len(r.footLinks)-1].Description = node.Literal } s = strings.ReplaceAll(string(node.Literal), "\n", " ") } w.Write([]byte(s)) } case bf.BlockQuote: r.isVerbatim = entering r.isQuoting = entering if !entering { r.nl(w) } case bf.List: if !entering { r.nl(w) } case bf.Item: if r.inLink = entering; r.inLink { w.Write([]byte{'*', ' '}) } else { r.nl(w) } case bf.Paragraph: if r.inLink || r.isVerbatim { break } if !entering { if r.footLinksOffset < len(r.footLinks) { r.nl(w) } for i, l := range r.footLinks[r.footLinksOffset:] { w.Write([]byte(fmt.Sprintf("\n=> %s [%d] %s", l.URL, i+r.footLinksOffset+1, l.Description))) } r.footLinksOffset = len(r.footLinks) r.nl(w) r.nl(w) } case bf.Heading: if entering { w.Write([]byte(strings.Repeat("#", node.Level) + " ")) } else { r.nl(w) r.nl(w) } case bf.HorizontalRule: case bf.Emph: case bf.Strong: case bf.Del: case bf.Link: if entering { r.footLinks = append(r.footLinks, Link{node.LinkData.Destination, nil}) } else { w.Write([]byte(fmt.Sprintf("[%d]", len(r.footLinks)))) } case bf.Image: if entering { w.Write([]byte(fmt.Sprintf("=> %s ", node.LinkData.Destination))) } case bf.HTMLBlock: case bf.CodeBlock: w.Write([]byte(fmt.Sprintf("```\n%s```\n\n", node.Literal))) case bf.Softbreak: case bf.Hardbreak: case bf.Code: w.Write([]byte(fmt.Sprintf("`%s`", node.Literal))) case bf.HTMLSpan: case bf.Table: case bf.TableCell: case bf.TableHead: case bf.TableBody: case bf.TableRow: } return bf.GoToNext } func (r *GeminiRenderer) RenderHeader(w io.Writer, ast *bf.Node) { } func (r *GeminiRenderer) RenderFooter(w io.Writer, ast *bf.Node) { } /* func main() { if c, err := ioutil.ReadFile("1.md"); err != nil { log.Fatal(err) } else { b := bf.Run(c, bf.WithExtensions(bf.FencedCode|bf.Autolink), bf.WithRenderer(NewGeminiRenderer())) fmt.Printf("%s\n", b) } } */