markgone.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // This source code is dedicated to the Public Domain.
  2. /*
  3. Package markgone provides a minimal markup language based on godoc.
  4. MarkGone uses the minimal formatting syntax from godoc.
  5. Unlike godoc, MarkGone processes plain text, not comments in go source code;
  6. Philosophy
  7. Markups are gone.
  8. In other words, MarkGone text just looks like plain text.
  9. Formatting Syntax
  10. A heading is a single line
  11. followed by another paragraph,
  12. beginning with a capital letter,
  13. and containing no punctuation.
  14. Paragraphs are separated by one or more blank lines.
  15. To produce a pre-formatted blocks,
  16. simply indent every line of the block.
  17. Common indent prefix will be removed in output.
  18. http://example.com/urls_are_auto-linked
  19. Source Code HighLight
  20. MarkGone does not highlight pre-formatted blocks.
  21. The highlighting can be done via JavaScript.
  22. For example, with highlight.js:
  23. <link rel="stylesheet" href="/path/to/styles/default.css">
  24. <script src="/path/to/highlight.pack.js"></script>
  25. <script>
  26. document.addEventListener('load', () => {
  27. hljs.configure({languages: ["go", "c", "scheme", "java", "js", "html", "css"]});
  28. const codes = document.querySelectorAll('pre');
  29. codes.forEach((code) => { hljs.highlightBlock(code); });
  30. });
  31. </script>
  32. CSS
  33. Since markgone only uses four HTML elements
  34. h2, h3, p, pre
  35. customizing css style for it is simple.
  36. Sample css files are provided in the css directory of source code repository:
  37. []struct{
  38. filename string;
  39. description string;
  40. }{
  41. {"godoc.css":
  42. "mimic godoc style"},
  43. {"plain/blue-link":
  44. "mimic plain text with colored links"},
  45. {"plain/underline-link":
  46. "mimic plain text with underlined links"},
  47. {"plain/white-on-black":
  48. "mimic a console"},
  49. {"plain/green-on-black":
  50. "mimic a console with green text"},
  51. }
  52. Extensions
  53. If the first line is a single line, i.e. followed by a blank line,
  54. then MarkGone uses it as the title.
  55. Tags: space-separated tags on last line following at least one blank-line
  56. */
  57. package markgone
  58. import (
  59. "strings"
  60. "io"
  61. "go/doc"
  62. "html/template"
  63. "fmt"
  64. "bytes"
  65. )
  66. // ToHTML converts markgone text to formatted HTML.
  67. // See doc.ToHTML for more information.
  68. // See also ToHTMLString.
  69. func ToHTML(w io.Writer, text string, words map[string]string) {
  70. title, body, tags := prepared(text)
  71. if title != "" {
  72. titleToHTML(w, title)
  73. }
  74. doc.ToHTML(w, strings.Join(body, "\n"), words)
  75. if tags != nil {
  76. tagsToHTML(w, tags)
  77. }
  78. }
  79. // ToHTMLString is like ToHTML, but returns formatted HTML as a string.
  80. func ToHTMLString(text string, words map[string]string) string {
  81. var b bytes.Buffer
  82. ToHTML(&b, text, words)
  83. return b.String()
  84. }
  85. func prepared(text string) (title string, body []string, tags []string) {
  86. lines := strings.Split(text, "\n")
  87. length := len(lines)
  88. if length >= 2 && isTitle(lines[:2]) {
  89. title = lines[0]
  90. body, tags = preparedBody(lines[2:])
  91. } else {
  92. title = ""
  93. body, tags = preparedBody(lines)
  94. }
  95. return title, body, tags
  96. }
  97. func isTitle(lines []string) bool {
  98. if lines[0] != "" && lines[1] == "" {
  99. return true
  100. } else {
  101. return false
  102. }
  103. }
  104. func titleToHTML(w io.Writer, title string) {
  105. fmt.Fprint(w, "<h2>")
  106. template.HTMLEscape(w, []byte(title))
  107. fmt.Fprintln(w, "</h2>")
  108. }
  109. func preparedBody(lines []string) ([]string, []string) {
  110. lines = leadingBlankStripped(lines)
  111. lines = trailingBlankStripped(lines)
  112. lines = trailingSpacesStripped(lines)
  113. length := len(lines)
  114. if length <= 1 {
  115. return lines, nil
  116. } else {
  117. lastLine := lines[length-1]
  118. tagLinePrefix := "Tags: "
  119. if strings.HasPrefix(lastLine, tagLinePrefix) && lines[length-2] == "" {
  120. lastLine = lastLine[len(tagLinePrefix):]
  121. tags := strings.Split(lastLine, " ")
  122. tags = emptyStringsStripped(tags)
  123. return lines[:length-2], tags
  124. } else {
  125. return lines, nil
  126. }
  127. }
  128. }
  129. func leadingBlankStripped(lines []string) []string {
  130. if len(lines) == 0 {
  131. return lines
  132. } else if lines[0] != "" {
  133. return lines
  134. } else {
  135. return leadingBlankStripped(lines[1:])
  136. }
  137. }
  138. func trailingBlankStripped(lines []string) []string {
  139. length := len(lines)
  140. if length == 0 {
  141. return lines
  142. } else if lines[length-1] != "" {
  143. return lines
  144. } else {
  145. return trailingBlankStripped(lines[:length-1])
  146. }
  147. }
  148. func trailingSpacesStripped(lines []string) []string {
  149. for i, line := range lines {
  150. lines[i] = strings.TrimRight(line, " ")
  151. }
  152. return lines
  153. }
  154. func emptyStringsStripped(strings []string) []string {
  155. for i, str := range strings {
  156. if str == "" {
  157. strings = append(strings[:i], strings[i+1:]...)
  158. }
  159. }
  160. return strings
  161. }
  162. func tagsToHTML(w io.Writer, tags []string) {
  163. fmt.Fprintln(w, `<div class="taglist">`)
  164. fmt.Fprintln(w, "<strong>Tags:</strong>")
  165. for _, tag := range tags {
  166. tag = template.HTMLEscapeString(tag)
  167. fmt.Fprintf(w, "<a href=\"/tag/%s\" rel=\"tag\">%s</a>\n", tag, tag)
  168. }
  169. fmt.Fprintln(w, "</div>")
  170. }