package taskwarrior import ( "fmt" "log/slog" "math" "strconv" "strings" "time" ) type Task struct { Id int64 `json:"id,omitempty"` Uuid string `json:"uuid,omitempty"` Description string `json:"description,omitempty"` Project string `json:"project,omitempty"` Priority string `json:"priority,omitempty"` Status string `json:"status,omitempty"` Tags []string `json:"tags,omitempty"` Depends []string `json:"depends,omitempty"` Urgency float32 `json:"urgency,omitempty"` Parent string `json:"parent,omitempty"` Due string `json:"due,omitempty"` Wait string `json:"wait,omitempty"` Scheduled string `json:"scheduled,omitempty"` Until string `json:"until,omitempty"` Start string `json:"start,omitempty"` End string `json:"end,omitempty"` Entry string `json:"entry,omitempty"` Modified string `json:"modified,omitempty"` Recur string `json:"recur,omitempty"` } func (t *Task) GetString(fieldWFormat string) string { field, format, _ := strings.Cut(fieldWFormat, ".") switch field { case "id": return strconv.FormatInt(t.Id, 10) case "uuid": return t.Uuid case "description": return t.Description case "project": return t.Project case "priority": return t.Priority case "status": return t.Status case "tags": return strings.Join(t.Tags, ", ") case "urgency": return fmt.Sprintf("%.2f", t.Urgency) case "due": return formatDate(t.Due, format) case "wait": return t.Wait case "scheduled": return formatDate(t.Scheduled, format) case "end": return t.End case "entry": return formatDate(t.Entry, format) case "modified": return t.Modified case "start": return formatDate(t.Start, format) case "until": return formatDate(t.Until, format) // TODO: implement these fields case "depends": return strings.Join(t.Depends, ", ") case "recur": return t.Recur default: slog.Error(fmt.Sprintf("Field not implemented: %s", field)) return "" } } type Tasks []*Task type Context struct { Name string Active bool ReadFilter string WriteFilter string } type Contexts map[string]*Context type Report struct { Name string Description string Labels []string Filter string Sort string Columns []string Context bool } type Reports map[string]*Report func formatDate(date string, format string) string { if date == "" { return "" } dtformat := "20060102T150405Z" dt, err := time.Parse(dtformat, date) if err != nil { slog.Error("Failed to parse time:", err) return "" } switch format { case "formatted", "": return dt.Format("2006-01-02 15:04") // TODO: proper julian date formatting case "julian": return dt.Format("060102.1504") case "epoch": return strconv.FormatInt(dt.Unix(), 10) case "iso": return dt.Format("2006-01-02T150405Z") case "age": return parseDurationVague(time.Since(dt)) case "relative": return parseDurationVague(time.Until(dt)) // TODO: implement remaining case "remaining": return "" case "countdown": return parseCountdown(time.Since(dt)) default: slog.Error(fmt.Sprintf("Date format not implemented: %s", format)) return "" } } func parseDurationVague(d time.Duration) string { dur := d.Round(time.Second).Abs() days := dur.Hours() / 24 var formatted string if dur >= time.Hour*24*365 { formatted = fmt.Sprintf("%.1fy", days/365) } else if dur >= time.Hour*24*90 { formatted = strconv.Itoa(int(math.Round(days/30))) + "mo" } else if dur >= time.Hour*24*7 { formatted = strconv.Itoa(int(math.Round(days/7))) + "w" } else if dur >= time.Hour*24 { formatted = strconv.Itoa(int(days)) + "d" } else if dur >= time.Hour { formatted = strconv.Itoa(int(dur.Round(time.Hour).Hours())) + "h" } else if dur >= time.Minute { formatted = strconv.Itoa(int(dur.Round(time.Minute).Minutes())) + "min" } else if dur >= time.Second { formatted = strconv.Itoa(int(dur.Round(time.Second).Seconds())) + "s" } if d < 0 { formatted = "-" + formatted } return formatted } func parseCountdown(d time.Duration) string { hours := int(d.Hours()) minutes := int(d.Minutes()) % 60 seconds := int(d.Seconds()) % 60 return fmt.Sprintf("%d:%02d:%02d", hours, minutes, seconds) }