Handle UDAs for editing; Fix layout; Add annotations

This commit is contained in:
Martin
2024-06-09 17:55:56 +02:00
parent 3e1cb9d1bc
commit bafd8958d4
12 changed files with 663 additions and 476 deletions

View File

@ -1,6 +1,7 @@
package taskwarrior
import (
"encoding/json"
"fmt"
"log/slog"
"math"
@ -13,6 +14,23 @@ const (
dtformat = "20060102T150405Z"
)
type UdaType string
const (
UdaTypeString UdaType = "string"
UdaTypeDate UdaType = "date"
UdaTypeNumeric UdaType = "numeric"
UdaTypeDuration UdaType = "duration"
)
type Uda struct {
Name string
Type UdaType
Label string
Values []string
Default string
}
type Annotation struct {
Entry string `json:"entry,omitempty"`
Description string `json:"description,omitempty"`
@ -22,12 +40,14 @@ func (a Annotation) String() string {
return fmt.Sprintf("%s %s", a.Entry, a.Description)
}
type Tasks []*Task
type Task struct {
Id int64 `json:"id,omitempty"`
Uuid string `json:"uuid,omitempty"`
Description string `json:"description,omitempty"`
Project string `json:"project"`
Priority string `json:"priority"`
Id int64 `json:"id,omitempty"`
Uuid string `json:"uuid,omitempty"`
Description string `json:"description,omitempty"`
Project string `json:"project"`
// Priority string `json:"priority"`
Status string `json:"status,omitempty"`
Tags []string `json:"tags"`
VirtualTags []string `json:"-"`
@ -114,8 +134,8 @@ func (t *Task) GetString(fieldWFormat string) string {
}
return t.Project
case "priority":
return t.Priority
// case "priority":
// return t.Priority
case "status":
return t.Status
@ -186,6 +206,7 @@ func (t *Task) GetString(fieldWFormat string) string {
return t.Recur
default:
// TODO: format according to UDA type
if val, ok := t.Udas[field]; ok {
if strVal, ok := val.(string); ok {
return strVal
@ -230,7 +251,69 @@ func (t *Task) RemoveTag(tag string) {
}
}
type Tasks []*Task
func (t *Task) UnmarshalJSON(data []byte) error {
type Alias Task
task := Alias{}
if err := json.Unmarshal(data, &task); err != nil {
return err
}
*t = Task(task)
m := make(map[string]any)
if err := json.Unmarshal(data, &m); err != nil {
return err
}
delete(m, "id")
delete(m, "uuid")
delete(m, "description")
delete(m, "project")
// delete(m, "priority")
delete(m, "status")
delete(m, "tags")
delete(m, "depends")
delete(m, "urgency")
delete(m, "parent")
delete(m, "due")
delete(m, "wait")
delete(m, "scheduled")
delete(m, "until")
delete(m, "start")
delete(m, "end")
delete(m, "entry")
delete(m, "modified")
delete(m, "recur")
delete(m, "annotations")
t.Udas = m
return nil
}
func (t *Task) MarshalJSON() ([]byte, error) {
type Alias Task
task := Alias(*t)
knownFields, err := json.Marshal(task)
if err != nil {
return nil, err
}
var knownMap map[string]any
if err := json.Unmarshal(knownFields, &knownMap); err != nil {
return nil, err
}
for key, value := range t.Udas {
if value != nil && value != "" {
knownMap[key] = value
}
}
return json.Marshal(knownMap)
}
type Context struct {
Name string
@ -418,3 +501,15 @@ func ValidateDate(s string) error {
return fmt.Errorf("invalid date")
}
func ValidateNumeric(s string) error {
if _, err := strconv.ParseFloat(s, 64); err != nil {
return fmt.Errorf("invalid number")
}
return nil
}
func ValidateDuration(s string) error {
// TODO: implement duration validation
return nil
}

View File

@ -89,15 +89,17 @@ type TaskWarrior interface {
GetReport(report string) *Report
GetReports() Reports
GetUdas() []string
GetUdas() []Uda
GetTasks(report *Report, filter ...string) Tasks
AddTask(task *Task) error
// AddTask(task *Task) error
ImportTask(task *Task)
SetTaskDone(task *Task)
DeleteTask(task *Task)
StartTask(task *Task)
StopTask(task *Task)
GetInformation(task *Task) string
AddTaskAnnotation(uuid string, annotation string)
Undo()
}
@ -171,15 +173,7 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks {
return nil
}
unstructuredTasks := make([]map[string]any, 0)
err = json.Unmarshal(output, &unstructuredTasks)
if err != nil {
slog.Error("Failed unmarshalling tasks:", err)
return nil
}
for i, task := range tasks {
task.Udas = unstructuredTasks[i]
for _, task := range tasks {
if task.Depends != nil && len(task.Depends) > 0 {
ids := make([]string, len(task.Depends))
for i, dependUuid := range task.Depends {
@ -325,7 +319,7 @@ func (ts *TaskSquire) GetReports() Reports {
return ts.reports
}
func (ts *TaskSquire) GetUdas() []string {
func (ts *TaskSquire) GetUdas() []Uda {
ts.mutex.Lock()
defer ts.mutex.Unlock()
@ -336,9 +330,27 @@ func (ts *TaskSquire) GetUdas() []string {
return nil
}
udas := make([]string, 0)
udas := make([]Uda, 0)
for _, uda := range strings.Split(string(output), "\n") {
if uda != "" {
udatype := UdaType(ts.config.Get(fmt.Sprintf("uda.%s.type", uda)))
if udatype == "" {
slog.Error(fmt.Sprintf("UDA type not found: %s", uda))
continue
}
label := ts.config.Get(fmt.Sprintf("uda.%s.label", uda))
values := strings.Split(ts.config.Get(fmt.Sprintf("uda.%s.values", uda)), ",")
def := ts.config.Get(fmt.Sprintf("uda.%s.default", uda))
uda := Uda{
Name: uda,
Label: label,
Type: udatype,
Values: values,
Default: def,
}
udas = append(udas, uda)
}
}
@ -367,42 +379,42 @@ func (ts *TaskSquire) SetContext(context *Context) error {
return nil
}
func (ts *TaskSquire) AddTask(task *Task) error {
ts.mutex.Lock()
defer ts.mutex.Unlock()
// func (ts *TaskSquire) AddTask(task *Task) error {
// ts.mutex.Lock()
// defer ts.mutex.Unlock()
addArgs := []string{"add"}
// addArgs := []string{"add"}
if task.Description == "" {
slog.Error("Task description is required")
return nil
} else {
addArgs = append(addArgs, task.Description)
}
if task.Priority != "" && task.Priority != "(none)" {
addArgs = append(addArgs, fmt.Sprintf("priority:%s", task.Priority))
}
if task.Project != "" && task.Project != "(none)" {
addArgs = append(addArgs, fmt.Sprintf("project:%s", task.Project))
}
if task.Tags != nil {
for _, tag := range task.Tags {
addArgs = append(addArgs, fmt.Sprintf("+%s", tag))
}
}
if task.Due != "" {
addArgs = append(addArgs, fmt.Sprintf("due:%s", task.Due))
}
// if task.Description == "" {
// slog.Error("Task description is required")
// return nil
// } else {
// addArgs = append(addArgs, task.Description)
// }
// if task.Priority != "" && task.Priority != "(none)" {
// addArgs = append(addArgs, fmt.Sprintf("priority:%s", task.Priority))
// }
// if task.Project != "" && task.Project != "(none)" {
// addArgs = append(addArgs, fmt.Sprintf("project:%s", task.Project))
// }
// if task.Tags != nil {
// for _, tag := range task.Tags {
// addArgs = append(addArgs, fmt.Sprintf("+%s", tag))
// }
// }
// if task.Due != "" {
// addArgs = append(addArgs, fmt.Sprintf("due:%s", task.Due))
// }
cmd := exec.Command(twBinary, append(ts.defaultArgs, addArgs...)...)
err := cmd.Run()
if err != nil {
slog.Error("Failed adding task:", err)
}
// cmd := exec.Command(twBinary, append(ts.defaultArgs, addArgs...)...)
// err := cmd.Run()
// if err != nil {
// slog.Error("Failed adding task:", err)
// }
// TODO remove error?
return nil
}
// // TODO remove error?
// return nil
// }
// TODO error handling
func (ts *TaskSquire) ImportTask(task *Task) {
@ -410,7 +422,6 @@ func (ts *TaskSquire) ImportTask(task *Task) {
defer ts.mutex.Unlock()
tasks, err := json.Marshal(Tasks{task})
if err != nil {
slog.Error("Failed marshalling task:", err)
}
@ -479,6 +490,31 @@ func (ts *TaskSquire) StopTask(task *Task) {
}
}
func (ts *TaskSquire) GetInformation(task *Task) string {
ts.mutex.Lock()
defer ts.mutex.Unlock()
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{fmt.Sprintf("uuid:%s", task.Uuid), "information"}...)...)
output, err := cmd.CombinedOutput()
if err != nil {
slog.Error("Failed getting task information:", err)
return ""
}
return string(output)
}
func (ts *TaskSquire) AddTaskAnnotation(uuid string, annotation string) {
ts.mutex.Lock()
defer ts.mutex.Unlock()
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{uuid, "annotate", annotation}...)...)
err := cmd.Run()
if err != nil {
slog.Error("Failed adding annotation:", err)
}
}
func (ts *TaskSquire) extractConfig() *TWConfig {
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_show"}...)...)
output, err := cmd.CombinedOutput()