Compare commits

...

6 Commits

Author SHA1 Message Date
e2d27367fe Remove version suffix from module path 2025-12-17 00:34:55 +01:00
f332c1708a feat!: migrate module to v3
- Update module path to v3 in go.mod and imports
- BREAKING CHANGE: consumers must update import paths to use v3
2025-12-17 00:20:43 +01:00
daffdf4441 feat(parser): support multiple values per metadata key in Task
BREAKING CHANGE: The Task struct's Metadata field now maps to slices of
strings (map[string][]string) instead of single strings. This allows
tasks to have multiple values for the same metadata key. All code
interacting with Metadata must be updated to handle slices. Version
bumped to v3.0.0.
2025-12-17 00:16:16 +01:00
bfb1bb3433 chore: update version to v2.0.0 in main.go
To make `go install` happy :-|
2025-11-26 16:39:10 +01:00
676ef83e50 chore: bump version to v2 in main.go 2025-11-26 16:24:55 +01:00
adbc2cf364 fix(parser): improve priority calculation accuracy using math.Round
Refactored PriorityAsRemind to use floating point division and rounding
for more precise priority mapping.
2025-11-26 16:24:28 +01:00
2 changed files with 39 additions and 26 deletions

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"math"
"regexp"
"strings"
"time"
@@ -13,16 +14,16 @@ import (
// Task represents a parsed todo item.
type Task struct {
Raw string `json:"raw"`
Completed bool `json:"completed"`
CompletionDate *time.Time `json:"completion_date,omitempty"`
Priority *rune `json:"priority,omitempty"` // 'A'..'Z'
CreationDate *time.Time `json:"creation_date,omitempty"`
DueDate *time.Time `json:"due_date,omitempty"` // parsed from due:YYYY-MM-DD
Description string `json:"description"` // description with projects/contexts/metadata removed
Projects []string `json:"projects"`
Contexts []string `json:"contexts"`
Metadata map[string]string `json:"metadata"`
Raw string `json:"raw"`
Completed bool `json:"completed"`
CompletionDate *time.Time `json:"completion_date,omitempty"`
Priority *rune `json:"priority,omitempty"` // 'A'..'Z'
CreationDate *time.Time `json:"creation_date,omitempty"`
DueDate *time.Time `json:"due_date,omitempty"` // parsed from due:YYYY-MM-DD
Description string `json:"description"` // description with projects/contexts/metadata removed
Projects []string `json:"projects"`
Contexts []string `json:"contexts"`
Metadata map[string][]string `json:"metadata"`
}
func (t Task) ToRemind() string {
@@ -36,13 +37,19 @@ func (t Task) ToRemind() string {
sb.WriteString(" ++5 INFO \"Calendar: TODO.TX\" ")
if len(t.Metadata) > 0 {
for k, v := range t.Metadata {
for k, values := range t.Metadata {
if k == "due" {
continue
}
// k must have the first letter uppercase for Remind
k = strings.ToUpper(k[:1]) + k[1:]
sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", k, v))
kUpper := strings.ToUpper(k[:1]) + k[1:]
if len(values) == 1 {
sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", kUpper, values[0]))
} else {
for i, v := range values {
sb.WriteString(fmt.Sprintf("INFO \"%s%d: %s\" ", kUpper, i+1, v))
}
}
}
}
@@ -81,13 +88,20 @@ func (t Task) ToRemind() string {
}
if len(t.Metadata) > 0 {
for k := range t.Metadata {
for k, values := range t.Metadata {
if k == "due" {
continue
}
// uppercase first letter for Remind
k = strings.ToUpper(k[:1]) + k[1:]
sb.WriteString(fmt.Sprintf("%%_%s: %%<%s>", k, k))
kUpper := strings.ToUpper(k[:1]) + k[1:]
if len(values) == 1 {
sb.WriteString(fmt.Sprintf("%%_%s: %%<%s>", kUpper, kUpper))
} else {
for i := range values {
kNumbered := fmt.Sprintf("%s%d", kUpper, i+1)
sb.WriteString(fmt.Sprintf("%%_%s: %%<%s>", kNumbered, kNumbered))
}
}
}
}
@@ -104,12 +118,10 @@ func (t Task) PriorityAsRemind() int {
if p < 'A' || p > 'Z' {
return 5000
}
step := 9999 / 25 // 399
val := 9999 - int(p-'A')*step
if p == 'Z' {
return 0
}
return val
pInt := int(p) - 65 // 'A' = 65
value := 9999 - (float64(pInt) * (float64(9999) / float64(25)))
value = math.Round(value)
return int(value)
}
func (t Task) MarshalJSON() ([]byte, error) {
@@ -182,7 +194,7 @@ func ParseReader(r io.Reader) ([]Task, []error) {
func ParseLine(line string) (Task, error) {
t := Task{
Raw: line,
Metadata: make(map[string]string),
Metadata: make(map[string][]string),
}
working := strings.TrimSpace(line)
@@ -263,14 +275,14 @@ func ParseLine(line string) (Task, error) {
}
val += " " + toks[i]
}
t.Metadata["location"] = val
t.Metadata["location"] = append(t.Metadata["location"], val)
continue
}
if !strings.ContainsAny(tok, " \t") && strings.Contains(tok, ":") {
parts := strings.SplitN(tok, ":", 2)
k, v := parts[0], parts[1]
if k != "" && v != "" && !isProtocolKey(k) {
t.Metadata[k] = v
t.Metadata[k] = append(t.Metadata[k], v)
if k == "due" && dateRe.MatchString(v) {
if dt, err := time.Parse(dateLayout, v); err == nil {
t.DueDate = &dt
@@ -278,6 +290,7 @@ func ParseLine(line string) (Task, error) {
}
continue
}
}
descParts = append(descParts, tok)
}

View File

@@ -14,7 +14,7 @@ var (
inputFile string
outputFile string
debug bool
version = "1"
version = "v3.0.0"
)
func main() {