gte/internal/task/task.go

198 lines
4.0 KiB
Go
Raw Normal View History

2021-01-29 12:29:23 +01:00
package task
import (
"errors"
"fmt"
"strings"
"git.sr.ht/~ewintr/gte/pkg/mstore"
"github.com/google/uuid"
)
var (
ErrOutdatedTask = errors.New("task is outdated")
)
2021-01-29 17:22:07 +01:00
const (
FOLDER_INBOX = "INBOX"
FOLDER_NEW = "New"
2021-01-29 12:29:23 +01:00
2021-01-29 18:10:06 +01:00
QUOTE_PREFIX = ">"
PREVIOUS_SEPARATOR = "Previous version:"
2021-01-29 19:40:46 +01:00
FIELD_SEPARATOR = ":"
SUBJECT_SEPARATOR = " - "
FIELD_ID = "id"
FIELD_ACTION = "action"
FIELD_PROJECT = "project"
FIELD_DUE = "date"
)
var (
knownFolders = []string{FOLDER_INBOX, FOLDER_NEW}
2021-01-29 17:22:07 +01:00
)
2021-01-29 12:29:23 +01:00
2021-01-29 17:22:07 +01:00
// Task reperesents a task based on the data stored in a message
2021-01-29 12:29:23 +01:00
type Task struct {
2021-01-29 17:22:07 +01:00
// Id is a UUID that gets carried over when a new message is constructed
Id string
// Folder is the same name as the mstore folder
Folder string
2021-01-29 19:40:46 +01:00
// Ordinary task attributes
2021-01-29 17:22:07 +01:00
Action string
2021-01-29 19:40:46 +01:00
Project string
2021-01-29 17:22:07 +01:00
Due Date
2021-01-29 19:40:46 +01:00
2021-01-29 17:22:07 +01:00
Message *mstore.Message
// Current indicates whether the task represents an existing message in the mstore
Current bool
// Dirty indicates whether the task contains updates not present in the message
Dirty bool
2021-01-29 12:29:23 +01:00
}
2021-01-29 17:22:07 +01:00
// New constructs a Task based on an mstore.Message.
//
// The data in the message is stored as key: value pairs, one per line. The line can start with quoting marks.
// The subject line also contains values in the format "date - project - action".
// Keys that exist more than once are merged. The one that appears first in the body takes precedence. A value present in the Body takes precedence over one in the subject.
// This enables updating a task by forwarding a topposted message whith new values for fields that the user wants to update.
func New(msg *mstore.Message) *Task {
2021-01-29 19:40:46 +01:00
// Id
2021-01-29 17:22:07 +01:00
dirty := false
2021-01-29 17:48:22 +01:00
id, d := FieldFromBody(FIELD_ID, msg.Body)
2021-01-29 12:29:23 +01:00
if id == "" {
id = uuid.New().String()
2021-01-29 17:22:07 +01:00
dirty = true
2021-01-29 12:29:23 +01:00
}
2021-01-29 17:48:22 +01:00
if d {
dirty = true
}
2021-01-29 12:29:23 +01:00
2021-01-29 19:40:46 +01:00
// Action
2021-01-29 17:48:22 +01:00
action, d := FieldFromBody(FIELD_ACTION, msg.Body)
2021-01-29 12:29:23 +01:00
if action == "" {
2021-01-29 17:22:07 +01:00
action = FieldFromSubject(FIELD_ACTION, msg.Subject)
if action != "" {
dirty = true
}
2021-01-29 12:29:23 +01:00
}
2021-01-29 17:48:22 +01:00
if d {
dirty = true
}
2021-01-29 12:29:23 +01:00
2021-01-29 19:40:46 +01:00
// Folder
2021-01-29 12:29:23 +01:00
folder := msg.Folder
2021-01-29 17:22:07 +01:00
if folder == FOLDER_INBOX {
folder = FOLDER_NEW
dirty = true
2021-01-29 12:29:23 +01:00
}
2021-01-29 19:40:46 +01:00
// Project
project, d := FieldFromBody(FIELD_PROJECT, msg.Body)
if d {
dirty = true
}
2021-01-29 12:29:23 +01:00
return &Task{
2021-01-29 17:22:07 +01:00
Id: id,
Folder: folder,
2021-01-29 19:40:46 +01:00
Action: action,
Project: project,
2021-01-29 17:22:07 +01:00
Message: msg,
Current: true,
Dirty: dirty,
2021-01-29 12:29:23 +01:00
}
}
2021-01-29 17:22:07 +01:00
func (t *Task) FormatSubject() string {
2021-01-29 19:40:46 +01:00
order := []string{FIELD_PROJECT, FIELD_ACTION}
fields := map[string]string{
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
}
parts := []string{}
for _, f := range order {
if fields[f] != "" {
parts = append(parts, fields[f])
}
}
return strings.Join(parts, SUBJECT_SEPARATOR)
2021-01-29 17:22:07 +01:00
}
2021-01-29 12:29:23 +01:00
2021-01-29 17:22:07 +01:00
func (t *Task) FormatBody() string {
body := fmt.Sprintf("\n")
2021-01-29 19:40:46 +01:00
order := []string{FIELD_ID, FIELD_PROJECT, FIELD_ACTION}
2021-01-29 17:22:07 +01:00
fields := map[string]string{
2021-01-29 19:40:46 +01:00
FIELD_ID: t.Id,
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
2021-01-29 12:29:23 +01:00
}
2021-01-29 17:22:07 +01:00
keyLen := 0
for _, f := range order {
if len(f) > keyLen {
keyLen = len(f)
}
2021-01-29 12:29:23 +01:00
}
2021-01-29 17:22:07 +01:00
for _, f := range order {
key := f + FIELD_SEPARATOR
for i := len(key); i <= keyLen; i++ {
key += " "
}
line := strings.TrimSpace(fmt.Sprintf("%s %s", key, fields[f]))
body += fmt.Sprintf("%s\n", line)
2021-01-29 12:29:23 +01:00
}
2021-01-29 18:10:06 +01:00
if t.Message != nil {
body += fmt.Sprintf("\nPrevious version:\n\n%s\n", t.Message.Body)
}
2021-01-29 12:29:23 +01:00
return body
}
2021-01-29 17:48:22 +01:00
func FieldFromBody(field, body string) (string, bool) {
value := ""
dirty := false
2021-01-29 12:29:23 +01:00
lines := strings.Split(body, "\n")
for _, line := range lines {
2021-01-29 18:10:06 +01:00
line = strings.TrimSpace(strings.TrimPrefix(line, QUOTE_PREFIX))
if line == PREVIOUS_SEPARATOR {
return value, dirty
}
2021-01-29 17:22:07 +01:00
parts := strings.SplitN(line, FIELD_SEPARATOR, 2)
2021-01-29 12:29:23 +01:00
if len(parts) < 2 {
continue
}
2021-01-29 17:48:22 +01:00
2021-01-29 18:10:06 +01:00
fieldName := strings.ToLower(strings.TrimSpace(parts[0]))
2021-01-29 17:48:22 +01:00
if fieldName == field {
if value == "" {
value = strings.TrimSpace(parts[1])
} else {
dirty = true
}
2021-01-29 12:29:23 +01:00
}
}
2021-01-29 17:48:22 +01:00
return value, dirty
2021-01-29 12:29:23 +01:00
}
func FieldFromSubject(field, subject string) string {
2021-01-29 17:22:07 +01:00
if field == FIELD_ACTION {
2021-01-29 12:29:23 +01:00
return strings.ToLower(subject)
}
return ""
}