gte/internal/task/task.go

270 lines
5.0 KiB
Go
Raw Normal View History

2021-01-29 12:29:23 +01:00
package task
import (
"errors"
"fmt"
2021-01-30 11:20:12 +01:00
"strconv"
2021-01-29 12:29:23 +01:00
"strings"
2021-05-13 09:47:06 +02:00
"git.ewintr.nl/gte/pkg/mstore"
2021-01-29 12:29:23 +01:00
"github.com/google/uuid"
)
var (
2021-01-31 10:01:03 +01:00
ErrOutdatedTask = errors.New("task is outdated")
ErrTaskIsNotRecurring = errors.New("task is not recurring")
2021-01-29 12:29:23 +01:00
)
2021-01-29 17:22:07 +01:00
const (
2021-01-30 11:20:12 +01:00
FOLDER_INBOX = "INBOX"
FOLDER_NEW = "New"
FOLDER_RECURRING = "Recurring"
FOLDER_PLANNED = "Planned"
FOLDER_UNPLANNED = "Unplanned"
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"
2021-01-30 11:20:12 +01:00
FIELD_VERSION = "version"
2021-01-29 19:40:46 +01:00
FIELD_ACTION = "action"
FIELD_PROJECT = "project"
2021-01-30 11:20:12 +01:00
FIELD_DUE = "due"
2021-01-31 08:22:31 +01:00
FIELD_RECUR = "recur"
2021-01-29 19:40:46 +01:00
)
var (
2021-01-30 11:20:12 +01:00
knownFolders = []string{
FOLDER_INBOX,
FOLDER_NEW,
FOLDER_RECURRING,
FOLDER_PLANNED,
FOLDER_UNPLANNED,
}
2021-06-04 10:45:56 +02:00
subjectFieldNames = []string{FIELD_ACTION}
bodyFieldNames = []string{
FIELD_ID,
FIELD_VERSION,
FIELD_ACTION,
FIELD_PROJECT,
FIELD_DUE,
FIELD_RECUR,
}
2021-01-29 17:22:07 +01:00
)
2021-01-29 12:29:23 +01:00
type Task struct {
2021-06-04 10:45:56 +02:00
Message *mstore.Message
2021-01-29 17:22:07 +01:00
2021-06-04 10:45:56 +02:00
Id string
2021-01-30 11:20:12 +01:00
Version int
2021-06-04 10:45:56 +02:00
Folder string
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-31 08:22:31 +01:00
Recur Recurrer
2021-01-29 12:29:23 +01:00
}
2021-06-04 10:45:56 +02:00
func NewFromMessage(msg *mstore.Message) *Task {
t := &Task{
Folder: msg.Folder,
Message: msg,
2021-01-29 17:48:22 +01:00
}
2021-01-29 12:29:23 +01:00
2021-06-04 10:45:56 +02:00
// parse fields from message
subjectFields := map[string]string{}
for _, f := range subjectFieldNames {
subjectFields[f] = FieldFromSubject(f, msg.Subject)
2021-01-30 11:20:12 +01:00
}
2021-06-04 10:45:56 +02:00
bodyFields := map[string]string{}
for _, f := range bodyFieldNames {
value, _ := FieldFromBody(f, msg.Body)
bodyFields[f] = value
2021-01-29 17:48:22 +01:00
}
2021-01-29 12:29:23 +01:00
2021-06-04 10:45:56 +02:00
// apply precedence rules
version, _ := strconv.Atoi(bodyFields[FIELD_VERSION])
id := bodyFields[FIELD_ID]
if id == "" {
id = uuid.New().String()
version = 0
2021-01-30 15:25:25 +01:00
}
2021-06-04 10:45:56 +02:00
t.Id = id
t.Version = version
2021-01-30 15:25:25 +01:00
2021-06-04 10:45:56 +02:00
t.Action = bodyFields[FIELD_ACTION]
if t.Action == "" {
t.Action = subjectFields[FIELD_ACTION]
2021-01-31 10:01:03 +01:00
}
2021-06-04 10:45:56 +02:00
t.Project = bodyFields[FIELD_PROJECT]
t.Due = NewDateFromString(bodyFields[FIELD_DUE])
t.Recur = NewRecurrer(bodyFields[FIELD_RECUR])
2021-01-29 12:29:23 +01:00
2021-06-04 10:45:56 +02:00
return t
}
2021-01-29 19:40:46 +01:00
2021-06-04 10:45:56 +02:00
func (t *Task) TargetFolder() string {
switch {
case t.Version == 0:
return FOLDER_NEW
case t.IsRecurrer():
return FOLDER_RECURRING
case !t.Due.IsZero():
return FOLDER_PLANNED
default:
return FOLDER_UNPLANNED
2021-01-30 11:20:12 +01:00
}
2021-06-04 10:45:56 +02:00
}
2021-01-30 11:20:12 +01:00
2021-06-04 10:45:56 +02:00
func (t *Task) NextMessage() *mstore.Message {
tNew := t
tNew.Folder = t.TargetFolder()
tNew.Version++
return &mstore.Message{
Folder: tNew.Folder,
Subject: tNew.FormatSubject(),
Body: tNew.FormatBody(),
2021-01-29 12:29:23 +01:00
}
}
2021-01-29 17:22:07 +01:00
func (t *Task) FormatSubject() string {
2021-01-30 15:25:25 +01:00
var order []string
if !t.Due.IsZero() {
order = append(order, FIELD_DUE)
}
order = append(order, FIELD_PROJECT, FIELD_ACTION)
2021-01-29 19:40:46 +01:00
fields := map[string]string{
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
2021-01-30 15:25:25 +01:00
FIELD_DUE: t.Due.String(),
2021-01-29 19:40:46 +01:00
}
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 {
2021-01-31 10:01:03 +01:00
order := []string{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,
2021-01-30 11:20:12 +01:00
FIELD_VERSION: strconv.Itoa(t.Version),
2021-01-29 19:40:46 +01:00
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
2021-01-29 12:29:23 +01:00
}
2021-01-31 10:01:03 +01:00
if t.IsRecurrer() {
order = append(order, FIELD_RECUR)
fields[FIELD_RECUR] = t.Recur.String()
} else {
order = append(order, FIELD_DUE)
fields[FIELD_DUE] = t.Due.String()
}
order = append(order, []string{FIELD_PROJECT, FIELD_VERSION, FIELD_ID}...)
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-31 10:01:03 +01:00
body := fmt.Sprintf("\n")
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-31 10:01:03 +01:00
func (t *Task) IsRecurrer() bool {
return t.Recur != nil
}
func (t *Task) RecursToday() bool {
2021-02-05 10:04:19 +01:00
return t.RecursOn(Today)
}
func (t *Task) RecursOn(date Date) bool {
2021-01-31 10:01:03 +01:00
if !t.IsRecurrer() {
return false
}
2021-02-05 10:04:19 +01:00
return t.Recur.RecursOn(date)
2021-01-31 10:01:03 +01:00
}
2021-05-13 08:15:14 +02:00
func (t *Task) GenerateFromRecurrer(date Date) (*Task, error) {
if !t.IsRecurrer() || !t.RecursOn(date) {
return &Task{}, ErrTaskIsNotRecurring
2021-01-31 10:01:03 +01:00
}
2021-05-13 08:15:14 +02:00
return &Task{
2021-01-31 10:01:03 +01:00
Id: uuid.New().String(),
Version: 1,
Action: t.Action,
Project: t.Project,
2021-01-31 12:11:02 +01:00
Due: date,
2021-05-13 08:15:14 +02:00
}, nil
2021-01-31 10:01:03 +01:00
}
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 = lowerAndTrim(parts[1])
2021-01-29 17:48:22 +01:00
} 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-02-01 14:20:41 +01:00
if field != FIELD_ACTION {
return ""
2021-01-31 08:22:31 +01:00
}
2021-01-30 11:20:12 +01:00
2021-02-01 14:20:41 +01:00
terms := strings.Split(subject, SUBJECT_SEPARATOR)
return lowerAndTrim(terms[len(terms)-1])
2021-01-30 11:20:12 +01:00
}