gemtext.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package gemtext
  2. import (
  3. "servitor/style"
  4. "regexp"
  5. "strings"
  6. )
  7. /*
  8. Specification:
  9. https://gemini.circumlunar.space/docs/specification.html
  10. */
  11. type Markup struct {
  12. tree []string
  13. cached string
  14. cachedWidth int
  15. }
  16. func NewMarkup(text string) (*Markup, []string, error) {
  17. lines := strings.Split(text, "\n")
  18. rendered, links := renderWithLinks(lines, 80)
  19. return &Markup{
  20. tree: lines,
  21. cached: rendered,
  22. cachedWidth: 80,
  23. }, links, nil
  24. }
  25. func (m *Markup) Render(width int) string {
  26. if m.cachedWidth == width {
  27. return m.cached
  28. }
  29. rendered, _ := renderWithLinks(m.tree, width)
  30. m.cached = rendered
  31. m.cachedWidth = width
  32. return rendered
  33. }
  34. func renderWithLinks(lines []string, width int) (string, []string) {
  35. links := []string{}
  36. result := ""
  37. preformattedMode := false
  38. preformattedBuffer := ""
  39. for _, line := range lines {
  40. if strings.HasPrefix(line, "```") {
  41. if preformattedMode {
  42. result += style.CodeBlock(strings.TrimSuffix(preformattedBuffer, "\n")) + "\n"
  43. preformattedBuffer = ""
  44. preformattedMode = false
  45. } else {
  46. preformattedMode = true
  47. }
  48. continue
  49. }
  50. if preformattedMode {
  51. preformattedBuffer += line + "\n"
  52. continue
  53. }
  54. if match := regexp.MustCompile(`^=>[ \t]*(.*?)(?:[ \t]+(.*))?$`).FindStringSubmatch(line); len(match) == 3 {
  55. uri := match[1]
  56. alt := match[2]
  57. if alt == "" {
  58. alt = uri
  59. }
  60. links = append(links, uri)
  61. result += style.LinkBlock(alt, len(links)) + "\n"
  62. } else if match := regexp.MustCompile(`^#[ \t]+(.*)$`).FindStringSubmatch(line); len(match) == 2 {
  63. result += style.Header(match[1], 1) + "\n"
  64. } else if match := regexp.MustCompile(`^##[ \t]+(.*)$`).FindStringSubmatch(line); len(match) == 2 {
  65. result += style.Header(match[1], 2) + "\n"
  66. } else if match := regexp.MustCompile(`^###[ \t]+(.*)$`).FindStringSubmatch(line); len(match) == 2 {
  67. result += style.Header(match[1], 3) + "\n"
  68. } else if match := regexp.MustCompile(`^\* (.*)$`).FindStringSubmatch(line); len(match) == 2 {
  69. result += style.Bullet(match[1]) + "\n"
  70. } else if match := regexp.MustCompile(`^> ?(.*)$`).FindStringSubmatch(line); len(match) == 2 {
  71. result += style.QuoteBlock(match[1]) + "\n"
  72. } else {
  73. result += line + "\n"
  74. }
  75. }
  76. // If trailing backticks are omitted, implicitly automatically add them
  77. if preformattedMode {
  78. result += style.CodeBlock(strings.TrimSuffix(preformattedBuffer, "\n")) + "\n"
  79. }
  80. return strings.Trim(result, "\n"), links
  81. }