gitea

Development moved to Codeberg

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
package org

import (
	"fmt"
	"regexp"
	"strings"
	"unicode"
)

type List struct {
	Kind  string
	Items []Node
}

type ListItem struct {
	Bullet   string
	Status   string
	Children []Node
}

type DescriptiveListItem struct {
	Bullet  string
	Status  string
	Term    []Node
	Details []Node
}

var unorderedListRegexp = regexp.MustCompile(`^(\s*)([+*-])(\s+(.*)|$)`)
var orderedListRegexp = regexp.MustCompile(`^(\s*)(([0-9]+|[a-zA-Z])[.)])(\s+(.*)|$)`)
var descriptiveListItemRegexp = regexp.MustCompile(`\s::(\s|$)`)
var listItemStatusRegexp = regexp.MustCompile(`\[( |X|-)\]\s`)

func lexList(line string) (token, bool) {
	if m := unorderedListRegexp.FindStringSubmatch(line); m != nil {
		return token{"unorderedList", len(m[1]), m[4], m}, true
	} else if m := orderedListRegexp.FindStringSubmatch(line); m != nil {
		return token{"orderedList", len(m[1]), m[5], m}, true
	}
	return nilToken, false
}

func isListToken(t token) bool {
	return t.kind == "unorderedList" || t.kind == "orderedList"
}

func listKind(t token) (string, string) {
	kind := ""
	switch bullet := t.matches[2]; {
	case bullet == "*" || bullet == "+" || bullet == "-":
		kind = "unordered"
	case unicode.IsLetter(rune(bullet[0])), unicode.IsDigit(rune(bullet[0])):
		kind = "ordered"
	default:
		panic(fmt.Sprintf("bad list bullet '%s': %#v", bullet, t))
	}
	if descriptiveListItemRegexp.MatchString(t.content) {
		return kind, "descriptive"
	}
	return kind, kind
}

func (d *Document) parseList(i int, parentStop stopFn) (int, Node) {
	start, lvl := i, d.tokens[i].lvl
	listMainKind, kind := listKind(d.tokens[i])
	list := List{Kind: kind}
	stop := func(*Document, int) bool {
		if parentStop(d, i) || d.tokens[i].lvl != lvl || !isListToken(d.tokens[i]) {
			return true
		}
		itemMainKind, _ := listKind(d.tokens[i])
		return itemMainKind != listMainKind
	}
	for !stop(d, i) {
		consumed, node := d.parseListItem(list, i, parentStop)
		i += consumed
		list.Items = append(list.Items, node)
	}
	return i - start, list
}

func (d *Document) parseListItem(l List, i int, parentStop stopFn) (int, Node) {
	start, nodes, bullet := i, []Node{}, d.tokens[i].matches[2]
	minIndent, dterm, content, status := d.tokens[i].lvl+len(bullet), "", d.tokens[i].content, ""
	originalBaseLvl := d.baseLvl
	d.baseLvl = minIndent + 1
	if m := listItemStatusRegexp.FindStringSubmatch(content); m != nil {
		status, content = m[1], content[len("[ ] "):]
	}
	if l.Kind == "descriptive" {
		if m := descriptiveListItemRegexp.FindStringIndex(content); m != nil {
			dterm, content = content[:m[0]], content[m[1]:]
			d.baseLvl = strings.Index(d.tokens[i].matches[0], " ::") + 4
		}
	}

	d.tokens[i] = tokenize(strings.Repeat(" ", minIndent) + content)
	stop := func(d *Document, i int) bool {
		if parentStop(d, i) {
			return true
		}
		t := d.tokens[i]
		return t.lvl < minIndent && !(t.kind == "text" && t.content == "")
	}
	for !stop(d, i) && (i <= start+1 || !isSecondBlankLine(d, i)) {
		consumed, node := d.parseOne(i, stop)
		i += consumed
		nodes = append(nodes, node)
	}
	d.baseLvl = originalBaseLvl
	if l.Kind == "descriptive" {
		return i - start, DescriptiveListItem{bullet, status, d.parseInline(dterm), nodes}
	}
	return i - start, ListItem{bullet, status, nodes}
}

func (n List) String() string                { return orgWriter.WriteNodesAsString(n) }
func (n ListItem) String() string            { return orgWriter.WriteNodesAsString(n) }
func (n DescriptiveListItem) String() string { return orgWriter.WriteNodesAsString(n) }