Files
byre/internal/todo/todo.go
T

143 lines
2.7 KiB
Go
Raw Normal View History

package todo
import (
"regexp"
"strings"
"g.fc.run/misc/byre/internal/api"
)
const File = "TODO.md"
var (
heading = regexp.MustCompile(`^#{1,6}\s+(.*?)\s*#*\s*$`)
listItem = regexp.MustCompile(`^(?:[-*+]|\d+[.)])\s+(.*)$`)
checkbox = regexp.MustCompile(`^\[([ xX])\]\s+`)
fence = regexp.MustCompile("^\\s*(```|~~~)")
)
func Parse(src string) []api.TodoItem {
items := []api.TodoItem{}
section := ""
var cur *api.TodoItem
body := []string{}
inFence := false
blank := false
finish := func() {
if cur == nil {
return
}
cur.Body = dedent(body)
items = append(items, *cur)
cur = nil
body = []string{}
}
for _, line := range strings.Split(strings.ReplaceAll(src, "\r\n", "\n"), "\n") {
if fence.MatchString(line) {
inFence = !inFence
if cur != nil {
body = append(body, line)
}
continue
}
if inFence {
if cur != nil {
body = append(body, line)
}
continue
}
if strings.TrimSpace(line) == "" {
blank = true
if cur != nil {
body = append(body, "")
}
continue
}
indented := line[0] == ' ' || line[0] == '\t'
afterBlank := blank
blank = false
if m := heading.FindStringSubmatch(line); m != nil {
finish()
section = m[1]
continue
}
if m := listItem.FindStringSubmatch(line); m != nil {
finish()
title := m[1]
box := checkbox.FindStringSubmatch(title)
if box != nil && box[1] != " " {
continue
}
title = strings.TrimSpace(checkbox.ReplaceAllString(title, ""))
cur = &api.TodoItem{Title: title, Section: section}
continue
}
if cur == nil {
continue
}
if !indented && afterBlank {
finish()
continue
}
body = append(body, line)
}
finish()
return items
}
func Find(items []api.TodoItem, title string) (api.TodoItem, bool) {
for _, item := range items {
if item.Title == title {
return item, true
}
}
return api.TodoItem{}, false
}
func Markdown(item api.TodoItem) string {
if item.Body == "" {
return "- " + item.Title
}
lines := []string{"- " + item.Title}
for _, line := range strings.Split(item.Body, "\n") {
if line == "" {
lines = append(lines, "")
continue
}
lines = append(lines, " "+line)
}
return strings.Join(lines, "\n")
}
func dedent(lines []string) string {
for len(lines) > 0 && lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
indent := -1
for _, line := range lines {
if line == "" {
continue
}
n := len(line) - len(strings.TrimLeft(line, " \t"))
if indent < 0 || n < indent {
indent = n
}
}
out := []string{}
for _, line := range lines {
if line == "" {
out = append(out, "")
continue
}
out = append(out, line[indent:])
}
return strings.Join(out, "\n")
}