~ft/bfg

ad960f43b9b455f0923049c70c929dac3c16d60c — Sigrid Haflínudóttir 3 months ago
first
2 files changed, 138 insertions(+), 0 deletions(-)

A README.md
A bfg.go
A  => README.md +3 -0
@@ 1,3 @@
# bfg

Blackfriday → Gemini

A  => bfg.go +135 -0
@@ 1,135 @@
package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"strings"

	bf "gopkg.in/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)
	}
}