Add todo mode and replace the plan with TODO.md
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user