date, folders and fix cleanup

This commit is contained in:
Erik Winter 2021-01-30 15:25:25 +01:00
parent 9092336bf9
commit 204c660d34
6 changed files with 313 additions and 73 deletions

View File

@ -1,14 +1,31 @@
package task
import (
"strings"
"time"
)
const (
DateFormat = "2006-01-02 (Monday)"
)
var Today Date
func init() {
year, month, day := time.Now().Date()
Today = NewDate(year, int(month), day)
}
type Date struct {
t time.Time
}
func NewDate(year, month, day int) *Date {
func NewDate(year, month, day int) Date {
if year == 0 && month == 0 && day == 0 {
return Date{}
}
var m time.Month
switch month {
case 1:
@ -37,21 +54,73 @@ func NewDate(year, month, day int) *Date {
m = time.December
}
t := time.Date(year, m, day, 10, 0, 0, 0, time.UTC)
t := time.Date(year, m, day, 0, 0, 0, 0, time.UTC)
if year == 0 && month == 0 && day == 0 {
t = time.Time{}
}
return &Date{
return Date{
t: t,
}
}
func NewDateFromString(date string) Date {
date = strings.ToLower(strings.TrimSpace(date))
if date == "no date" || date == "" {
return Date{}
}
t, err := time.Parse(DateFormat, date)
if err == nil {
return Date{t: t}
}
t, err = time.Parse("2006-01-02", date)
if err == nil {
return Date{t: t}
}
weekday := Today.Weekday()
var newWeekday time.Weekday
switch {
case date == "monday" || date == "maandag":
newWeekday = time.Monday
case date == "tuesday" || date == "dinsdag":
newWeekday = time.Tuesday
case date == "wednesday" || date == "woensdag":
newWeekday = time.Wednesday
case date == "thursday" || date == "donderdag":
newWeekday = time.Thursday
case date == "friday" || date == "vrijdag":
newWeekday = time.Friday
case date == "saturday" || date == "zaterdag":
newWeekday = time.Saturday
case date == "sunday" || date == "zondag":
newWeekday = time.Sunday
}
daysToAdd := int(newWeekday) - weekday
if daysToAdd < 0 {
daysToAdd += 7
}
return Today.Add(daysToAdd)
}
func (d *Date) String() string {
if d.t.IsZero() {
return "no date"
}
return d.t.Format("2006-01-02")
return strings.ToLower(d.t.Format(DateFormat))
}
func (d *Date) IsZero() bool {
return d.t.IsZero()
}
func (d *Date) Weekday() int {
return int(d.t.Weekday())
}
func (d *Date) Add(days int) Date {
year, month, day := d.t.Date()
return NewDate(year, int(month), day+days)
}

View File

@ -7,10 +7,58 @@ import (
"git.sr.ht/~ewintr/gte/internal/task"
)
func TestNewDateFromString(t *testing.T) {
task.Today = task.NewDate(2021, 1, 30)
for _, tc := range []struct {
name string
input string
exp task.Date
}{
{
name: "empty",
exp: task.Date{},
},
{
name: "no date",
input: "no date",
exp: task.Date{},
},
{
name: "normal",
input: "2021-01-30 (saturday)",
exp: task.NewDate(2021, 1, 30),
},
{
name: "short",
input: "2021-01-30",
exp: task.NewDate(2021, 1, 30),
},
{
name: "english dayname lowercase",
input: "monday",
exp: task.NewDate(2021, 2, 1),
},
{
name: "english dayname capitalized",
input: "Monday",
exp: task.NewDate(2021, 2, 1),
},
{
name: "ducth dayname lowercase",
input: "maandag",
exp: task.NewDate(2021, 2, 1),
},
} {
t.Run(tc.name, func(t *testing.T) {
test.Equals(t, tc.exp, task.NewDateFromString(tc.input))
})
}
}
func TestDateString(t *testing.T) {
for _, tc := range []struct {
name string
date *task.Date
date task.Date
exp string
}{
{
@ -21,12 +69,12 @@ func TestDateString(t *testing.T) {
{
name: "normal",
date: task.NewDate(2021, 1, 30),
exp: "2021-01-30",
exp: "2021-01-30 (saturday)",
},
{
name: "normalize",
date: task.NewDate(2021, 1, 32),
exp: "2021-02-01",
exp: "2021-02-01 (monday)",
},
} {
t.Run(tc.name, func(t *testing.T) {

View File

@ -3,6 +3,7 @@ package task
import (
"errors"
"fmt"
"strconv"
"git.sr.ht/~ewintr/gte/pkg/mstore"
)
@ -67,43 +68,52 @@ func (tr *TaskRepo) Update(t *Task) error {
// Cleanup removes older versions of tasks
func (tr *TaskRepo) CleanUp() error {
// loop through folders, get all tasks
taskSet := make(map[string][]*Task)
// loop through folders, get all task version info
type msgInfo struct {
Version int
Message *mstore.Message
}
msgsSet := make(map[string][]msgInfo)
for _, folder := range knownFolders {
tasks, err := tr.FindAll(folder)
msgs, err := tr.mstore.Messages(folder)
if err != nil {
return err
return fmt.Errorf("%w: %v", ErrMStoreError, err)
}
for _, t := range tasks {
if _, ok := taskSet[t.Id]; !ok {
taskSet[t.Id] = []*Task{}
for _, msg := range msgs {
id, _ := FieldFromBody(FIELD_ID, msg.Body)
versionStr, _ := FieldFromBody(FIELD_VERSION, msg.Body)
version, _ := strconv.Atoi(versionStr)
if _, ok := msgsSet[id]; !ok {
msgsSet[id] = []msgInfo{}
}
taskSet[t.Id] = append(taskSet[t.Id], t)
msgsSet[id] = append(msgsSet[id], msgInfo{
Version: version,
Message: msg,
})
}
}
// determine which ones need to be gone
var tobeRemoved []*Task
for _, tasks := range taskSet {
var tobeRemoved []*mstore.Message
for _, mInfos := range msgsSet {
maxVersion := 0
for _, t := range tasks {
if t.Version > maxVersion {
maxVersion = t.Version
for _, mInfo := range mInfos {
if mInfo.Version > maxVersion {
maxVersion = mInfo.Version
}
}
for _, t := range tasks {
if t.Version < maxVersion {
tobeRemoved = append(tobeRemoved, t)
for _, mInfo := range mInfos {
if mInfo.Version < maxVersion {
tobeRemoved = append(tobeRemoved, mInfo.Message)
}
}
}
// remove them
for _, t := range tobeRemoved {
if err := tr.mstore.Remove(t.Message); err != nil {
for _, msg := range tobeRemoved {
if err := tr.mstore.Remove(msg); err != nil {
return err
}
}

View File

@ -201,6 +201,5 @@ version: 3
}}
actPlanned, err := mem.Messages(folderPlanned)
test.OK(t, err)
fmt.Printf("act: %+v\n", actPlanned[0])
test.Equals(t, expPlanned, actPlanned)
}

View File

@ -78,10 +78,12 @@ type Task struct {
func New(msg *mstore.Message) *Task {
// Id
dirty := false
newId := false
id, d := FieldFromBody(FIELD_ID, msg.Body)
if id == "" {
id = uuid.New().String()
dirty = true
newId = true
}
if d {
dirty = true
@ -106,10 +108,27 @@ func New(msg *mstore.Message) *Task {
dirty = true
}
// Due
dueStr, d := FieldFromBody(FIELD_DUE, msg.Body)
if dueStr == "" || d {
dirty = true
}
due := NewDateFromString(dueStr)
// Folder
folder := msg.Folder
if folder == FOLDER_INBOX {
folder = FOLDER_NEW
folderOld := msg.Folder
folderNew := folderOld
if folderOld == FOLDER_INBOX {
switch {
case newId:
folderNew = FOLDER_NEW
case !newId && due.IsZero():
folderNew = FOLDER_UNPLANNED
case !newId && !due.IsZero():
folderNew = FOLDER_PLANNED
}
}
if folderOld != folderNew {
dirty = true
}
@ -126,8 +145,9 @@ func New(msg *mstore.Message) *Task {
return &Task{
Id: id,
Version: version,
Folder: folder,
Folder: folderNew,
Action: action,
Due: due,
Project: project,
Message: msg,
Current: true,
@ -136,10 +156,16 @@ func New(msg *mstore.Message) *Task {
}
func (t *Task) FormatSubject() string {
order := []string{FIELD_PROJECT, FIELD_ACTION}
var order []string
if !t.Due.IsZero() {
order = append(order, FIELD_DUE)
}
order = append(order, FIELD_PROJECT, FIELD_ACTION)
fields := map[string]string{
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
FIELD_DUE: t.Due.String(),
}
parts := []string{}
@ -154,12 +180,13 @@ func (t *Task) FormatSubject() string {
func (t *Task) FormatBody() string {
body := fmt.Sprintf("\n")
order := []string{FIELD_ID, FIELD_VERSION, FIELD_PROJECT, FIELD_ACTION}
order := []string{FIELD_ACTION, FIELD_DUE, FIELD_PROJECT, FIELD_VERSION, FIELD_ID}
fields := map[string]string{
FIELD_ID: t.Id,
FIELD_VERSION: strconv.Itoa(t.Version),
FIELD_PROJECT: t.Project,
FIELD_ACTION: t.Action,
FIELD_DUE: t.Due.String(),
}
keyLen := 0
@ -215,15 +242,11 @@ func FieldFromBody(field, body string) (string, bool) {
}
func FieldFromSubject(field, subject string) string {
if field == FIELD_ACTION {
return strings.ToLower(subject)
if field != FIELD_ACTION {
return ""
}
return ""
}
terms := strings.Split(subject, SUBJECT_SEPARATOR)
func increment(s string) string {
i, _ := strconv.Atoi(s)
i++
return strconv.Itoa(i)
return terms[len(terms)-1]
}

View File

@ -14,7 +14,7 @@ func TestNewFromMessage(t *testing.T) {
version := 2
action := "some action"
project := "project"
folder := task.FOLDER_NEW
date := task.NewDate(2021, 1, 20)
for _, tc := range []struct {
name string
@ -33,9 +33,10 @@ func TestNewFromMessage(t *testing.T) {
{
name: "id, action, project and folder",
message: &mstore.Message{
Folder: folder,
Folder: task.FOLDER_UNPLANNED,
Body: fmt.Sprintf(`
id: %s
due: no date
version: %d
action: %s
project: %s
@ -46,21 +47,40 @@ project: %s
exp: &task.Task{
Id: id,
Version: version,
Folder: folder,
Folder: task.FOLDER_UNPLANNED,
Action: action,
Project: project,
},
},
{
name: "with date",
message: &mstore.Message{
Folder: task.FOLDER_PLANNED,
Body: fmt.Sprintf(`
id: %s
due: %s
version: %d
action: %s
`, id, date.String(), version, action),
},
hasId: true,
hasVersion: true,
exp: &task.Task{
Id: id,
Folder: task.FOLDER_PLANNED,
Action: action,
Version: version,
Due: date,
},
},
{
name: "folder inbox get updated to new",
message: &mstore.Message{
Folder: task.FOLDER_INBOX,
Body: fmt.Sprintf(`
id: %s
action: %s
`, id, action),
`, action),
},
hasId: true,
exp: &task.Task{
Id: id,
Folder: task.FOLDER_NEW,
@ -68,13 +88,51 @@ action: %s
Dirty: true,
},
},
{
name: "folder inbox gets updated to planned",
message: &mstore.Message{
Folder: task.FOLDER_INBOX,
Body: fmt.Sprintf(`
id: %s
due: %s
action: %s
`, id, date.String(), action),
},
hasId: true,
exp: &task.Task{
Id: id,
Folder: task.FOLDER_PLANNED,
Action: action,
Due: date,
Dirty: true,
},
},
{
name: "folder new gets updated to unplanned",
message: &mstore.Message{
Folder: task.FOLDER_INBOX,
Body: fmt.Sprintf(`
id: %s
due: no date
action: %s
`, id, action),
},
hasId: true,
exp: &task.Task{
Id: id,
Folder: task.FOLDER_UNPLANNED,
Action: action,
Dirty: true,
},
},
{
name: "action in subject takes precedence",
message: &mstore.Message{
Folder: folder,
Folder: task.FOLDER_PLANNED,
Subject: "some other action",
Body: fmt.Sprintf(`
id: %s
due: no date
version: %d
action: %s
`, id, version, action),
@ -84,21 +142,21 @@ action: %s
exp: &task.Task{
Id: id,
Version: version,
Folder: folder,
Folder: task.FOLDER_PLANNED,
Action: action,
},
},
{
name: "action from subject if not present in body",
message: &mstore.Message{
Folder: folder,
Folder: task.FOLDER_PLANNED,
Subject: action,
Body: fmt.Sprintf(`id: %s`, id),
},
hasId: true,
exp: &task.Task{
Id: id,
Folder: folder,
Folder: task.FOLDER_PLANNED,
Action: action,
Dirty: true,
},
@ -106,7 +164,7 @@ action: %s
{
name: "quoted fields",
message: &mstore.Message{
Folder: folder,
Folder: task.FOLDER_PLANNED,
Body: fmt.Sprintf(`
action: %s
@ -118,7 +176,7 @@ Forwarded message:
hasId: true,
exp: &task.Task{
Id: id,
Folder: folder,
Folder: task.FOLDER_PLANNED,
Action: action,
Dirty: true,
},
@ -143,6 +201,7 @@ Forwarded message:
func TestFormatSubject(t *testing.T) {
action := "an action"
project := " a project"
due := task.NewDate(2021, 1, 30)
for _, tc := range []struct {
name string
@ -155,18 +214,34 @@ func TestFormatSubject(t *testing.T) {
},
{
name: "action",
task: &task.Task{Action: action},
exp: action,
task: &task.Task{
Action: action,
},
exp: action,
},
{
name: "project",
task: &task.Task{Project: project},
exp: project,
task: &task.Task{
Project: project,
},
exp: project,
},
{
name: "action and project",
task: &task.Task{Action: action, Project: project},
exp: fmt.Sprintf("%s - %s", project, action),
task: &task.Task{
Action: action,
Project: project,
},
exp: fmt.Sprintf("%s - %s", project, action),
},
{
name: "action, date and project",
task: &task.Task{
Action: action,
Project: project,
Due: due,
},
exp: fmt.Sprintf("%s - %s - %s", due.String(), project, action),
},
} {
t.Run(tc.name, func(t *testing.T) {
@ -190,10 +265,11 @@ func TestFormatBody(t *testing.T) {
name: "empty",
task: &task.Task{},
exp: `
id:
version: 0
project:
action:
due: no date
project:
version: 0
id:
`,
},
{
@ -203,15 +279,17 @@ action:
Version: version,
Action: action,
Project: project,
Due: task.NewDate(2021, 1, 30),
Message: &mstore.Message{
Body: "previous body",
},
},
exp: `
id: an id
version: 6
project: project
action: an action
due: 2021-01-30 (saturday)
project: project
version: 6
id: an id
Previous version:
@ -327,6 +405,7 @@ field: valuea
}
func TestFieldFromSubject(t *testing.T) {
action := "action"
for _, tc := range []struct {
name string
field string
@ -335,7 +414,7 @@ func TestFieldFromSubject(t *testing.T) {
}{
{
name: "empty field",
subject: "subject",
subject: action,
},
{
name: "empty subject",
@ -344,13 +423,25 @@ func TestFieldFromSubject(t *testing.T) {
{
name: "unknown field",
field: "unknown",
subject: "subject",
subject: action,
},
{
name: "known field",
field: task.FIELD_ACTION,
subject: "subject",
exp: "subject",
subject: action,
exp: action,
},
{
name: "with project",
field: task.FIELD_ACTION,
subject: fmt.Sprintf("project - %s", action),
exp: action,
},
{
name: "with due and project",
field: task.FIELD_ACTION,
subject: fmt.Sprintf("due - project - %s", action),
exp: action,
},
} {
t.Run(tc.name, func(t *testing.T) {