print.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package ast
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "strings"
  7. "unicode/utf8"
  8. )
  9. // Print is for debugging. It prints a string representation of parsed
  10. // markdown doc (result of parser.Parse()) to dst.
  11. //
  12. // To make output readable, it shortens text output.
  13. func Print(dst io.Writer, doc Node) {
  14. PrintWithPrefix(dst, doc, " ")
  15. }
  16. // PrintWithPrefix is like Print but allows customizing prefix used for
  17. // indentation. By default it's 2 spaces. You can change it to e.g. tab
  18. // by passing "\t"
  19. func PrintWithPrefix(w io.Writer, doc Node, prefix string) {
  20. // for more compact output, don't print outer Document
  21. if _, ok := doc.(*Document); ok {
  22. for _, c := range doc.GetChildren() {
  23. printRecur(w, c, prefix, 0)
  24. }
  25. } else {
  26. printRecur(w, doc, prefix, 0)
  27. }
  28. }
  29. // ToString is like Dump but returns result as a string
  30. func ToString(doc Node) string {
  31. var buf bytes.Buffer
  32. Print(&buf, doc)
  33. return buf.String()
  34. }
  35. func contentToString(d1 []byte, d2 []byte) string {
  36. if d1 != nil {
  37. return string(d1)
  38. }
  39. if d2 != nil {
  40. return string(d2)
  41. }
  42. return ""
  43. }
  44. func getContent(node Node) string {
  45. if c := node.AsContainer(); c != nil {
  46. return contentToString(c.Literal, c.Content)
  47. }
  48. leaf := node.AsLeaf()
  49. return contentToString(leaf.Literal, leaf.Content)
  50. }
  51. func shortenString(s string, maxLen int) string {
  52. // for cleaner, one-line ouput, replace some white-space chars
  53. // with their escaped version
  54. s = strings.Replace(s, "\n", `\n`, -1)
  55. s = strings.Replace(s, "\r", `\r`, -1)
  56. s = strings.Replace(s, "\t", `\t`, -1)
  57. if maxLen < 0 {
  58. return s
  59. }
  60. if utf8.RuneCountInString(s) < maxLen {
  61. return s
  62. }
  63. // add "…" to indicate truncation
  64. return string(append([]rune(s)[:maxLen-3], '…'))
  65. }
  66. // get a short name of the type of v which excludes package name
  67. // and strips "()" from the end
  68. func getNodeType(node Node) string {
  69. s := fmt.Sprintf("%T", node)
  70. s = strings.TrimSuffix(s, "()")
  71. if idx := strings.Index(s, "."); idx != -1 {
  72. return s[idx+1:]
  73. }
  74. return s
  75. }
  76. func printDefault(w io.Writer, indent string, typeName string, content string) {
  77. content = strings.TrimSpace(content)
  78. if len(content) > 0 {
  79. fmt.Fprintf(w, "%s%s '%s'\n", indent, typeName, content)
  80. } else {
  81. fmt.Fprintf(w, "%s%s\n", indent, typeName)
  82. }
  83. }
  84. func getListFlags(f ListType) string {
  85. var s string
  86. if f&ListTypeOrdered != 0 {
  87. s += "ordered "
  88. }
  89. if f&ListTypeDefinition != 0 {
  90. s += "definition "
  91. }
  92. if f&ListTypeTerm != 0 {
  93. s += "term "
  94. }
  95. if f&ListItemContainsBlock != 0 {
  96. s += "has_block "
  97. }
  98. if f&ListItemBeginningOfList != 0 {
  99. s += "start "
  100. }
  101. if f&ListItemEndOfList != 0 {
  102. s += "end "
  103. }
  104. s = strings.TrimSpace(s)
  105. return s
  106. }
  107. func printRecur(w io.Writer, node Node, prefix string, depth int) {
  108. if node == nil {
  109. return
  110. }
  111. indent := strings.Repeat(prefix, depth)
  112. content := shortenString(getContent(node), 40)
  113. typeName := getNodeType(node)
  114. switch v := node.(type) {
  115. case *Link:
  116. content := "url=" + string(v.Destination)
  117. printDefault(w, indent, typeName, content)
  118. case *Image:
  119. content := "url=" + string(v.Destination)
  120. printDefault(w, indent, typeName, content)
  121. case *List:
  122. if v.Start > 1 {
  123. content += fmt.Sprintf("start=%d ", v.Start)
  124. }
  125. if v.Tight {
  126. content += "tight "
  127. }
  128. if v.IsFootnotesList {
  129. content += "footnotes "
  130. }
  131. flags := getListFlags(v.ListFlags)
  132. if len(flags) > 0 {
  133. content += "flags=" + flags + " "
  134. }
  135. printDefault(w, indent, typeName, content)
  136. case *ListItem:
  137. if v.Tight {
  138. content += "tight "
  139. }
  140. if v.IsFootnotesList {
  141. content += "footnotes "
  142. }
  143. flags := getListFlags(v.ListFlags)
  144. if len(flags) > 0 {
  145. content += "flags=" + flags + " "
  146. }
  147. printDefault(w, indent, typeName, content)
  148. case *CodeBlock:
  149. printDefault(w, indent, typeName + ":" + string(v.Info), content)
  150. default:
  151. printDefault(w, indent, typeName, content)
  152. }
  153. for _, child := range node.GetChildren() {
  154. printRecur(w, child, prefix, depth+1)
  155. }
  156. }