package gold import ( "encoding/json" "fmt" "io" "io/ioutil" "os" "strconv" "strings" "github.com/logrusorgru/aurora" bf "gopkg.in/russross/blackfriday.v2" ) type ElementStyle struct { Color string `json:"color"` BackgroundColor string `json:"background_color"` Underline bool `json:"underline"` Bold bool `json:"bold"` Italic bool `json:"italic"` CrossedOut bool `json:"crossed_out"` Faint bool `json:"faint"` Conceal bool `json:"conceal"` Overlined bool `json:"overlined"` Inverse bool `json:"inverse"` Blink bool `json:"blink"` } type Element struct { Token string Pre string Post string } type TermRenderer struct { style map[bf.NodeType]*ElementStyle } func Render(in string, stylePath string) ([]byte, error) { return RenderBytes([]byte(in), stylePath) } func RenderBytes(in []byte, stylePath string) ([]byte, error) { r, err := NewTermRenderer(stylePath) if err != nil { return nil, err } return bf.Run(in, bf.WithRenderer(r)), nil } func NewTermRenderer(stylePath string) (*TermRenderer, error) { if stylePath == "" { return NewTermRendererFromBytes([]byte("{}")) } f, err := os.Open(stylePath) if err != nil { return nil, err } defer f.Close() b, _ := ioutil.ReadAll(f) return NewTermRendererFromBytes(b) } func NewTermRendererFromBytes(b []byte) (*TermRenderer, error) { e := make(map[string]*ElementStyle, 0) err := json.Unmarshal(b, &e) if err != nil { return nil, err } tr := &TermRenderer{} tr.style = make(map[bf.NodeType]*ElementStyle) for k, v := range e { t, err := keyToType(k) if err != nil { fmt.Println(err) continue } tr.style[t] = v } return tr, nil } func NewElement(node *bf.Node) Element { switch node.Type { case bf.Document: return Element{ Token: "", Pre: "", Post: "", } case bf.BlockQuote: return Element{ Token: string(node.Literal), Pre: "\n", Post: "\n", } case bf.List: return Element{ Token: string(node.Literal), Pre: "\n", Post: "", } case bf.Item: return Element{ Token: string(node.Literal), Pre: "• ", Post: "", } case bf.Paragraph: pre := "\n" if node.Prev == nil || (node.Parent != nil && node.Parent.Type == bf.Item) { pre = "" } return Element{ Token: string(node.Literal), Pre: pre, Post: "\n", } case bf.Heading: var pre string if node.Prev != nil { pre = "\n" } return Element{ Token: fmt.Sprintf("%s %s", strings.Repeat("#", node.HeadingData.Level), node.FirstChild.Literal), Pre: pre, Post: "\n", } case bf.HorizontalRule: return Element{ Token: "---", Pre: "\n", Post: "\n", } case bf.Emph: return Element{ Token: string(node.FirstChild.Literal), Pre: "", Post: "", } case bf.Strong: return Element{ Token: string(node.FirstChild.Literal), Pre: "", Post: "", } case bf.Del: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.Link: return Element{ Token: fmt.Sprintf("[%s](%s)", string(node.LastChild.Literal), string(node.LinkData.Destination)), Pre: "", Post: "", } case bf.Image: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.Text: if node.Parent != nil && node.Parent.Type != bf.Link { return Element{ Token: string(node.Literal), Pre: "", Post: "", } } return Element{ Token: "", Pre: "", Post: "", } case bf.HTMLBlock: return Element{ Token: string(node.Literal), Pre: "\n", Post: "\n", } case bf.CodeBlock: return Element{ Token: string(node.Literal), Pre: "\n", Post: "\n", } case bf.Softbreak: return Element{ Token: string(node.Literal), Pre: "", Post: "\n", } case bf.Hardbreak: return Element{ Token: string(node.Literal), Pre: "\n", Post: "\n", } case bf.Code: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.HTMLSpan: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.Table: return Element{ Token: string(node.Literal), Pre: "\n", Post: "\n", } case bf.TableCell: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.TableHead: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.TableBody: return Element{ Token: string(node.Literal), Pre: "", Post: "", } case bf.TableRow: return Element{ Token: string(node.Literal), Pre: "\n", Post: "\n", } default: return Element{ Token: string(node.Literal), Pre: "", Post: "", } } } func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus { // fmt.Fprintf(w, "%s %t", node.Type, entering) el := NewElement(node) if entering && el.Pre != "" { fmt.Fprintf(w, "%s", el.Pre) } if !entering && el.Post != "" { fmt.Fprintf(w, "%s", el.Post) } if isChild(node) { return bf.GoToNext } if !entering { return bf.GoToNext } if el.Token == "" { return bf.GoToNext } rules := tr.style[node.Type] if rules == nil { fmt.Fprintf(w, "%s", el.Token) return bf.GoToNext } out := aurora.Reset(el.Token) if rules.Color != "" { i, err := strconv.Atoi(rules.Color) if err == nil && i >= 0 && i <= 255 { out = out.Index(uint8(i)) } } if rules.BackgroundColor != "" { i, err := strconv.Atoi(rules.BackgroundColor) if err == nil && i >= 0 && i <= 255 { out = out.Index(uint8(i)) } } if rules.Underline { out = out.Underline() } if rules.Bold { out = out.Bold() } if rules.Italic { out = out.Italic() } if rules.CrossedOut { out = out.CrossedOut() } if rules.Overlined { out = out.Overlined() } if rules.Inverse { out = out.Reverse() } if rules.Blink { out = out.Blink() } w.Write([]byte(aurora.Sprintf("%s", out))) return bf.GoToNext } func (tr *TermRenderer) RenderHeader(w io.Writer, ast *bf.Node) { } func (tr *TermRenderer) RenderFooter(w io.Writer, ast *bf.Node) { } func isChild(node *bf.Node) bool { if node.Parent == nil { return false } switch node.Parent.Type { case bf.Heading, bf.Link, bf.Emph, bf.Strong: return true default: return false } } func keyToType(key string) (bf.NodeType, error) { switch key { case "document": return bf.Document, nil case "block_quote": return bf.BlockQuote, nil case "list": return bf.List, nil case "item": return bf.Item, nil case "paragraph": return bf.Paragraph, nil case "heading": return bf.Heading, nil case "hr": return bf.HorizontalRule, nil case "emph": return bf.Emph, nil case "strong": return bf.Strong, nil case "del": return bf.Del, nil case "link": return bf.Link, nil case "image": return bf.Image, nil case "text": return bf.Text, nil case "html_block": return bf.HTMLBlock, nil case "code_block": return bf.CodeBlock, nil case "softbreak": return bf.Softbreak, nil case "hardbreak": return bf.Hardbreak, nil case "code": return bf.Code, nil case "html_span": return bf.HTMLSpan, nil case "table": return bf.Table, nil case "table_cel": return bf.TableCell, nil case "table_head": return bf.TableHead, nil case "table_body": return bf.TableBody, nil case "table_row": return bf.TableRow, nil default: return 0, fmt.Errorf("Invalid element type: %s", key) } }