Use ElementStyles to determine rendering style

Since BlackFriday's Nodes are limiting us a bit in rendering
flexibility, I've introduced Gold's own ElementStyles (a bit of
code repetition here, but alas).

Nodes can now be divided into multiple fragments, which means we
are able to display the link's URL and text in different styles.

Updated the dark theme to demo the newly gained flexibility.
This commit is contained in:
Christian Muehlhaeuser
2019-11-24 23:33:25 +01:00
parent 13d43240dc
commit d082cb2124
+237 -86
View File
@@ -13,6 +13,36 @@ import (
bf "gopkg.in/russross/blackfriday.v2" bf "gopkg.in/russross/blackfriday.v2"
) )
type StyleType int
const (
Document StyleType = iota
BlockQuote
List
Item
Paragraph
Heading
HorizontalRule
Emph
Strong
Del
Link
LinkText
Image
Text
HTMLBlock
CodeBlock
Softbreak
Hardbreak
Code
HTMLSpan
Table
TableCell
TableHead
TableBody
TableRow
)
type ElementStyle struct { type ElementStyle struct {
Color string `json:"color"` Color string `json:"color"`
BackgroundColor string `json:"background_color"` BackgroundColor string `json:"background_color"`
@@ -27,14 +57,21 @@ type ElementStyle struct {
Blink bool `json:"blink"` Blink bool `json:"blink"`
} }
type Element struct { type Fragment struct {
Token string Token string
Pre string Pre string
Post string Post string
Style StyleType
}
type Element struct {
Pre string
Post string
Fragments []Fragment
} }
type TermRenderer struct { type TermRenderer struct {
style map[bf.NodeType]*ElementStyle style map[StyleType]*ElementStyle
} }
func Render(in string, stylePath string) ([]byte, error) { func Render(in string, stylePath string) ([]byte, error) {
@@ -63,13 +100,13 @@ func NewTermRenderer(stylePath string) (*TermRenderer, error) {
} }
func NewTermRendererFromBytes(b []byte) (*TermRenderer, error) { func NewTermRendererFromBytes(b []byte) (*TermRenderer, error) {
e := make(map[string]*ElementStyle, 0) e := make(map[string]*ElementStyle)
err := json.Unmarshal(b, &e) err := json.Unmarshal(b, &e)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tr := &TermRenderer{} tr := &TermRenderer{}
tr.style = make(map[bf.NodeType]*ElementStyle) tr.style = make(map[StyleType]*ElementStyle)
for k, v := range e { for k, v := range e {
t, err := keyToType(k) t, err := keyToType(k)
@@ -86,27 +123,39 @@ func NewElement(node *bf.Node) Element {
switch node.Type { switch node.Type {
case bf.Document: case bf.Document:
return Element{ return Element{
Token: "",
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: "",
Style: Document,
}},
} }
case bf.BlockQuote: case bf.BlockQuote:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: BlockQuote,
}},
} }
case bf.List: case bf.List:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: List,
}},
} }
case bf.Item: case bf.Item:
return Element{ return Element{
Token: string(node.Literal),
Pre: "• ", Pre: "• ",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Item,
}},
} }
case bf.Paragraph: case bf.Paragraph:
pre := "\n" pre := "\n"
@@ -115,9 +164,12 @@ func NewElement(node *bf.Node) Element {
} }
return Element{ return Element{
Token: string(node.Literal),
Pre: pre, Pre: pre,
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Paragraph,
}},
} }
case bf.Heading: case bf.Heading:
var pre string var pre string
@@ -126,130 +178,209 @@ func NewElement(node *bf.Node) Element {
} }
return Element{ return Element{
Token: fmt.Sprintf("%s %s", strings.Repeat("#", node.HeadingData.Level), node.FirstChild.Literal),
Pre: pre, Pre: pre,
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: fmt.Sprintf("%s %s", strings.Repeat("#", node.HeadingData.Level), node.FirstChild.Literal),
Style: Heading,
}},
} }
case bf.HorizontalRule: case bf.HorizontalRule:
return Element{ return Element{
Token: "---",
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: "---",
Style: HorizontalRule,
}},
} }
case bf.Emph: case bf.Emph:
return Element{ return Element{
Token: string(node.FirstChild.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.FirstChild.Literal),
Style: Emph,
}},
} }
case bf.Strong: case bf.Strong:
return Element{ return Element{
Token: string(node.FirstChild.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.FirstChild.Literal),
Style: Strong,
}},
} }
case bf.Del: case bf.Del:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Del,
}},
} }
case bf.Link: case bf.Link:
f := []Fragment{}
if len(node.LastChild.Literal) > 0 {
f = append(f, Fragment{
Token: string(node.LastChild.Literal),
Style: LinkText,
})
}
if len(node.LinkData.Destination) > 0 {
f = append(f, Fragment{
Token: string(node.LinkData.Destination),
Pre: " (",
Post: ")",
Style: Link,
})
}
return Element{ return Element{
Token: fmt.Sprintf("[%s](%s)", string(node.LastChild.Literal), string(node.LinkData.Destination)),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: f,
} }
case bf.Image: case bf.Image:
f := []Fragment{}
if len(node.LastChild.Literal) > 0 {
f = append(f, Fragment{
Token: string(node.LastChild.Literal),
Style: Image,
})
}
if len(node.LinkData.Destination) > 0 {
f = append(f, Fragment{
Token: string(node.LinkData.Destination),
Pre: " [Image: ",
Post: "]",
Style: Link,
})
}
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: f,
} }
case bf.Text: case bf.Text:
if node.Parent != nil && node.Parent.Type != bf.Link {
return Element{ return Element{
Pre: "",
Post: "",
Fragments: []Fragment{{
Token: string(node.Literal), Token: string(node.Literal),
Pre: "", Style: Text,
Post: "", }},
}
}
return Element{
Token: "",
Pre: "",
Post: "",
} }
case bf.HTMLBlock: case bf.HTMLBlock:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: HTMLBlock,
}},
} }
case bf.CodeBlock: case bf.CodeBlock:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: CodeBlock,
}},
} }
case bf.Softbreak: case bf.Softbreak:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Softbreak,
}},
} }
case bf.Hardbreak: case bf.Hardbreak:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Hardbreak,
}},
} }
case bf.Code: case bf.Code:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Code,
}},
} }
case bf.HTMLSpan: case bf.HTMLSpan:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: HTMLSpan,
}},
} }
case bf.Table: case bf.Table:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: Table,
}},
} }
case bf.TableCell: case bf.TableCell:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: TableCell,
}},
} }
case bf.TableHead: case bf.TableHead:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: TableHead,
}},
} }
case bf.TableBody: case bf.TableBody:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: TableBody,
}},
} }
case bf.TableRow: case bf.TableRow:
return Element{ return Element{
Token: string(node.Literal),
Pre: "\n", Pre: "\n",
Post: "\n", Post: "\n",
Fragments: []Fragment{{
Token: string(node.Literal),
Style: TableRow,
}},
} }
default: default:
return Element{ return Element{
Token: string(node.Literal),
Pre: "", Pre: "",
Post: "", Post: "",
Fragments: []Fragment{{
Token: string(node.Literal),
}},
} }
} }
} }
@@ -262,30 +393,14 @@ func (tr *TermRenderer) RenderBytes(in []byte) []byte {
return bf.Run(in, bf.WithRenderer(tr)) return bf.Run(in, bf.WithRenderer(tr))
} }
func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus { func (tr *TermRenderer) renderFragment(w io.Writer, f Fragment) {
// fmt.Fprintf(w, "%s %t", node.Type, entering) rules := tr.style[f.Style]
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 { if rules == nil {
fmt.Fprintf(w, "%s", el.Token) fmt.Fprintf(w, "%s", f.Token)
return bf.GoToNext return
} }
out := aurora.Reset(el.Token)
out := aurora.Reset(f.Token)
if rules.Color != "" { if rules.Color != "" {
i, err := strconv.Atoi(rules.Color) i, err := strconv.Atoi(rules.Color)
if err == nil && i >= 0 && i <= 255 { if err == nil && i >= 0 && i <= 255 {
@@ -319,7 +434,40 @@ func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf
if rules.Blink { if rules.Blink {
out = out.Blink() out = out.Blink()
} }
w.Write([]byte(aurora.Sprintf("%s", out))) w.Write([]byte(aurora.Sprintf("%s", out)))
}
func (tr *TermRenderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus {
// fmt.Fprintf(w, "%s %t", node.Type, entering)
e := NewElement(node)
if entering && e.Pre != "" {
fmt.Fprintf(w, "%s", e.Pre)
}
if !entering && e.Post != "" {
fmt.Fprintf(w, "%s", e.Post)
}
if isChild(node) {
return bf.GoToNext
}
if !entering {
return bf.GoToNext
}
for _, f := range e.Fragments {
if f.Token == "" {
continue
}
if f.Pre != "" {
fmt.Fprintf(w, "%s", f.Pre)
}
tr.renderFragment(w, f)
if f.Post != "" {
fmt.Fprintf(w, "%s", f.Post)
}
}
return bf.GoToNext return bf.GoToNext
} }
@@ -334,64 +482,67 @@ func isChild(node *bf.Node) bool {
return false return false
} }
switch node.Parent.Type { switch node.Parent.Type {
case bf.Heading, bf.Link, bf.Emph, bf.Strong: case bf.Heading, bf.Link, bf.Image, bf.Emph, bf.Strong:
return true return true
default: default:
return false return false
} }
} }
func keyToType(key string) (bf.NodeType, error) { func keyToType(key string) (StyleType, error) {
switch key { switch key {
case "document": case "document":
return bf.Document, nil return Document, nil
case "block_quote": case "block_quote":
return bf.BlockQuote, nil return BlockQuote, nil
case "list": case "list":
return bf.List, nil return List, nil
case "item": case "item":
return bf.Item, nil return Item, nil
case "paragraph": case "paragraph":
return bf.Paragraph, nil return Paragraph, nil
case "heading": case "heading":
return bf.Heading, nil return Heading, nil
case "hr": case "hr":
return bf.HorizontalRule, nil return HorizontalRule, nil
case "emph": case "emph":
return bf.Emph, nil return Emph, nil
case "strong": case "strong":
return bf.Strong, nil return Strong, nil
case "del": case "del":
return bf.Del, nil return Del, nil
case "link": case "link":
return bf.Link, nil return Link, nil
case "link_text":
return LinkText, nil
case "image": case "image":
return bf.Image, nil return Image, nil
case "text": case "text":
return bf.Text, nil return Text, nil
case "html_block": case "html_block":
return bf.HTMLBlock, nil return HTMLBlock, nil
case "code_block": case "code_block":
return bf.CodeBlock, nil return CodeBlock, nil
case "softbreak": case "softbreak":
return bf.Softbreak, nil return Softbreak, nil
case "hardbreak": case "hardbreak":
return bf.Hardbreak, nil return Hardbreak, nil
case "code": case "code":
return bf.Code, nil return Code, nil
case "html_span": case "html_span":
return bf.HTMLSpan, nil return HTMLSpan, nil
case "table": case "table":
return bf.Table, nil return Table, nil
case "table_cel": case "table_cel":
return bf.TableCell, nil return TableCell, nil
case "table_head": case "table_head":
return bf.TableHead, nil return TableHead, nil
case "table_body": case "table_body":
return bf.TableBody, nil return TableBody, nil
case "table_row": case "table_row":
return bf.TableRow, nil return TableRow, nil
default: default:
return 0, fmt.Errorf("Invalid element type: %s", key) return 0, fmt.Errorf("Invalid style element type: %s", key)
} }
} }