renderer.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package markdown
  2. import (
  3. "fmt"
  4. "io"
  5. "github.com/diamondburned/ningen/v3/discordmd"
  6. "github.com/yuin/goldmark/ast"
  7. gmr "github.com/yuin/goldmark/renderer"
  8. )
  9. var DefaultRenderer = newRenderer()
  10. type renderer struct {
  11. config *gmr.Config
  12. }
  13. func newRenderer() *renderer {
  14. config := gmr.NewConfig()
  15. return &renderer{config}
  16. }
  17. // AddOptions implements renderer.Renderer.
  18. func (r *renderer) AddOptions(opts ...gmr.Option) {
  19. for _, opt := range opts {
  20. opt.SetConfig(r.config)
  21. }
  22. }
  23. func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error {
  24. return ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
  25. switch n := n.(type) {
  26. case *ast.Document:
  27. // noop
  28. case *ast.Heading:
  29. r.renderHeading(w)
  30. case *ast.Text:
  31. r.renderText(w, n, entering, source)
  32. case *ast.FencedCodeBlock:
  33. r.renderFencedCodeBlock(w, n, entering, source)
  34. case *ast.AutoLink:
  35. r.renderAutoLink(w, n, entering, source)
  36. case *ast.Link:
  37. r.renderLink(w, n, entering)
  38. case *discordmd.Inline:
  39. r.renderInline(w, n, entering)
  40. case *discordmd.Mention:
  41. r.renderMention(w, n, entering)
  42. case *discordmd.Emoji:
  43. r.renderEmoji(w, n, entering)
  44. }
  45. return ast.WalkContinue, nil
  46. })
  47. }
  48. func (r *renderer) renderHeading(w io.Writer) {
  49. io.WriteString(w, "\n")
  50. }
  51. func (r *renderer) renderFencedCodeBlock(w io.Writer, n *ast.FencedCodeBlock, entering bool, source []byte) {
  52. io.WriteString(w, "\n")
  53. if entering {
  54. // body
  55. lines := n.Lines()
  56. for i := range lines.Len() {
  57. line := lines.At(i)
  58. io.WriteString(w, "| ")
  59. w.Write(line.Value(source))
  60. }
  61. }
  62. }
  63. func (r *renderer) renderAutoLink(w io.Writer, n *ast.AutoLink, entering bool, source []byte) {
  64. if entering {
  65. linkColor := r.config.Options["linkColor"].(string)
  66. io.WriteString(w, "["+linkColor+"]")
  67. w.Write(n.URL(source))
  68. } else {
  69. io.WriteString(w, "[-::]")
  70. }
  71. }
  72. func (r *renderer) renderLink(w io.Writer, n *ast.Link, entering bool) {
  73. if entering {
  74. linkColor := r.config.Options["linkColor"].(string)
  75. io.WriteString(w, fmt.Sprintf("[%s:::%s]", linkColor, n.Destination))
  76. } else {
  77. io.WriteString(w, "[-:::-]")
  78. }
  79. }
  80. func (r *renderer) renderText(w io.Writer, n *ast.Text, entering bool, source []byte) {
  81. if entering {
  82. w.Write(n.Segment.Value(source))
  83. switch {
  84. case n.HardLineBreak():
  85. io.WriteString(w, "\n\n")
  86. case n.SoftLineBreak():
  87. io.WriteString(w, "\n")
  88. }
  89. }
  90. }
  91. func (r *renderer) renderInline(w io.Writer, n *discordmd.Inline, entering bool) {
  92. if entering {
  93. switch n.Attr {
  94. case discordmd.AttrBold:
  95. io.WriteString(w, "[::b]")
  96. case discordmd.AttrItalics:
  97. io.WriteString(w, "[::i]")
  98. case discordmd.AttrUnderline:
  99. io.WriteString(w, "[::u]")
  100. case discordmd.AttrStrikethrough:
  101. io.WriteString(w, "[::s]")
  102. case discordmd.AttrMonospace:
  103. io.WriteString(w, "[::r]")
  104. }
  105. } else {
  106. switch n.Attr {
  107. case discordmd.AttrBold:
  108. io.WriteString(w, "[::B]")
  109. case discordmd.AttrItalics:
  110. io.WriteString(w, "[::I]")
  111. case discordmd.AttrUnderline:
  112. io.WriteString(w, "[::U]")
  113. case discordmd.AttrStrikethrough:
  114. io.WriteString(w, "[::S]")
  115. case discordmd.AttrMonospace:
  116. io.WriteString(w, "[::R]")
  117. }
  118. }
  119. }
  120. func (r *renderer) renderMention(w io.Writer, n *discordmd.Mention, entering bool) {
  121. if entering {
  122. mentionColor := r.config.Options["mentionColor"].(string)
  123. _, _ = fmt.Fprintf(w, "[%s::b]", mentionColor)
  124. switch {
  125. case n.Channel != nil:
  126. io.WriteString(w, "#"+n.Channel.Name)
  127. case n.GuildUser != nil:
  128. username := n.GuildUser.DisplayOrUsername()
  129. if r.config.Options["showNicknames"].(bool) && n.GuildUser.Member != nil && n.GuildUser.Member.Nick != "" {
  130. username = n.GuildUser.Member.Nick
  131. }
  132. io.WriteString(w, "@"+username)
  133. case n.GuildRole != nil:
  134. io.WriteString(w, "@"+n.GuildRole.Name)
  135. }
  136. } else {
  137. io.WriteString(w, "[-::B]")
  138. }
  139. }
  140. func (r *renderer) renderEmoji(w io.Writer, n *discordmd.Emoji, entering bool) {
  141. if entering {
  142. emojiColor := r.config.Options["emojiColor"].(string)
  143. io.WriteString(w, "["+emojiColor+"]")
  144. io.WriteString(w, ":"+n.Name+":")
  145. } else {
  146. io.WriteString(w, "[-]")
  147. }
  148. }