package ticket import ( "fmt" "regexp" "strings" ) // taskCheckboxRe matches GFM/YouTrack-style task list items: // // - [ ] unchecked // - [x] checked // * [X] also accepted var taskCheckboxRe = regexp.MustCompile(`^(\s*)([-*+])\s+\[([ xX])\](\s*)(.*)$`) // Checkbox is one task-list item inside a Markdown description. type Checkbox struct { Line int // 0-based line index in the source Checked bool Text string // label after the checkbox marker } // ListCheckboxes returns all task-list items in markdown source order. func ListCheckboxes(markdown string) []Checkbox { if markdown == "" { return nil } lines := strings.Split(markdown, "\n") var out []Checkbox for i, line := range lines { m := taskCheckboxRe.FindStringSubmatch(line) if m == nil { continue } out = append(out, Checkbox{ Line: i, Checked: m[3] == "x" || m[3] == "X", Text: m[5], }) } return out } // ToggleCheckbox flips the checked state of the checkbox at index // (0-based among ListCheckboxes results) and returns the updated markdown. func ToggleCheckbox(markdown string, index int) (string, error) { boxes := ListCheckboxes(markdown) if index < 0 || index >= len(boxes) { return markdown, fmt.Errorf("checkbox %d: not found", index) } lines := strings.Split(markdown, "\n") lineIdx := boxes[index].Line m := taskCheckboxRe.FindStringSubmatch(lines[lineIdx]) if m == nil { return markdown, fmt.Errorf("checkbox %d: line changed", index) } mark := " " if !(m[3] == "x" || m[3] == "X") { mark = "x" } lines[lineIdx] = m[1] + m[2] + " [" + mark + "]" + m[4] + m[5] return strings.Join(lines, "\n"), nil }