date, folders and fix cleanup
This commit is contained in:
parent
9092336bf9
commit
204c660d34
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue