Compare commits

...

7 Commits

Author SHA1 Message Date
5e5999676d feat: add --indent flag to control line indentation in remind output
- Introduces an 'indent' integer flag to the CLI for specifying the
  number of spaces to indent lines in the remind output.
- Updates Task.ToRemind to accept an indent parameter and apply heading
  spaces accordingly.
- Adjusts main.go to pass the indent value from the flag to ToRemind.
2026-01-15 00:13:24 +01:00
1099f4ab97 Revert "fix(parser): exclude "url" metadata from Remind output (since version 06.02.02 of remind)"
This reverts commit ec9df87d68.
2026-01-14 23:30:45 +01:00
ec9df87d68 fix(parser): exclude "url" metadata from Remind output (since version 06.02.02 of remind)
Previously, only "due" metadata was excluded when generating Remind
output. This change also skips "url" metadata, preventing it from
appearing in the Remind string.

Version 06.02.02 of remind supports "url" metadata.
2026-01-11 21:54:39 +01:00
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
2 changed files with 40 additions and 22 deletions

View File

@@ -23,10 +23,10 @@ type Task struct {
Description string `json:"description"` // description with projects/contexts/metadata removed Description string `json:"description"` // description with projects/contexts/metadata removed
Projects []string `json:"projects"` Projects []string `json:"projects"`
Contexts []string `json:"contexts"` Contexts []string `json:"contexts"`
Metadata map[string]string `json:"metadata"` Metadata map[string][]string `json:"metadata"`
} }
func (t Task) ToRemind() string { func (t Task) ToRemind(indent int) string {
var sb strings.Builder var sb strings.Builder
sb.WriteString("REM TODO ") sb.WriteString("REM TODO ")
if t.DueDate == nil { if t.DueDate == nil {
@@ -37,13 +37,19 @@ func (t Task) ToRemind() string {
sb.WriteString(" ++5 INFO \"Calendar: TODO.TX\" ") sb.WriteString(" ++5 INFO \"Calendar: TODO.TX\" ")
if len(t.Metadata) > 0 { if len(t.Metadata) > 0 {
for k, v := range t.Metadata { for k, values := range t.Metadata {
if k == "due" { if k == "due" {
continue continue
} }
// k must have the first letter uppercase for Remind // k must have the first letter uppercase for Remind
k = strings.ToUpper(k[:1]) + k[1:] kUpper := strings.ToUpper(k[:1]) + k[1:]
sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", k, v)) 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,14 +87,23 @@ func (t Task) ToRemind() string {
sb.WriteString(" (%b)") sb.WriteString(" (%b)")
} }
headingSpaces := strings.Repeat(" ", indent)
if len(t.Metadata) > 0 { if len(t.Metadata) > 0 {
for k := range t.Metadata { for k, values := range t.Metadata {
if k == "due" { if k == "due" {
continue continue
} }
// uppercase first letter for Remind // uppercase first letter for Remind
k = strings.ToUpper(k[:1]) + k[1:] kUpper := strings.ToUpper(k[:1]) + k[1:]
sb.WriteString(fmt.Sprintf("%%_%s: %%<%s>", k, k)) if len(values) == 1 {
sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kUpper, kUpper))
} else {
for i := range values {
kNumbered := fmt.Sprintf("%s%d", kUpper, i+1)
sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kNumbered, kNumbered))
}
}
} }
} }
@@ -181,7 +196,7 @@ func ParseReader(r io.Reader) ([]Task, []error) {
func ParseLine(line string) (Task, error) { func ParseLine(line string) (Task, error) {
t := Task{ t := Task{
Raw: line, Raw: line,
Metadata: make(map[string]string), Metadata: make(map[string][]string),
} }
working := strings.TrimSpace(line) working := strings.TrimSpace(line)
@@ -262,14 +277,14 @@ func ParseLine(line string) (Task, error) {
} }
val += " " + toks[i] val += " " + toks[i]
} }
t.Metadata["location"] = val t.Metadata["location"] = append(t.Metadata["location"], val)
continue continue
} }
if !strings.ContainsAny(tok, " \t") && strings.Contains(tok, ":") { if !strings.ContainsAny(tok, " \t") && strings.Contains(tok, ":") {
parts := strings.SplitN(tok, ":", 2) parts := strings.SplitN(tok, ":", 2)
k, v := parts[0], parts[1] k, v := parts[0], parts[1]
if k != "" && v != "" && !isProtocolKey(k) { if k != "" && v != "" && !isProtocolKey(k) {
t.Metadata[k] = v t.Metadata[k] = append(t.Metadata[k], v)
if k == "due" && dateRe.MatchString(v) { if k == "due" && dateRe.MatchString(v) {
if dt, err := time.Parse(dateLayout, v); err == nil { if dt, err := time.Parse(dateLayout, v); err == nil {
t.DueDate = &dt t.DueDate = &dt
@@ -277,6 +292,7 @@ func ParseLine(line string) (Task, error) {
} }
continue continue
} }
} }
descParts = append(descParts, tok) descParts = append(descParts, tok)
} }

View File

@@ -14,7 +14,8 @@ var (
inputFile string inputFile string
outputFile string outputFile string
debug bool debug bool
version = "v2" indent int
version = "v3.0.0"
) )
func main() { func main() {
@@ -59,7 +60,7 @@ func main() {
} }
for _, t := range tasks { for _, t := range tasks {
rem := t.ToRemind() rem := t.ToRemind(indent)
if rem == "" { if rem == "" {
continue continue
} }
@@ -71,6 +72,7 @@ func main() {
rootCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input file (default: stdin)") rootCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input file (default: stdin)")
rootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file (default: stdout)") rootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file (default: stdout)")
rootCmd.Flags().BoolVar(&debug, "debug", false, "Print intermediate JSON to stderr") rootCmd.Flags().BoolVar(&debug, "debug", false, "Print intermediate JSON to stderr")
rootCmd.Flags().IntVar(&indent, "indent", 0, "Number of spaces to indent lines (from second line onward)")
rootCmd.Version = version rootCmd.Version = version
rootCmd.Flags().BoolP("version", "v", false, "Show version and exit") rootCmd.Flags().BoolP("version", "v", false, "Show version and exit")
rootCmd.SetVersionTemplate(fmt.Sprintf("todotxt2remind version %s\n", version)) rootCmd.SetVersionTemplate(fmt.Sprintf("todotxt2remind version %s\n", version))