package todo import ( "regexp" "strings" "g.fc.run/ai/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") }