From 18d3074633b06b7667ff25396a01cdff819fcf50 Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Sun, 31 Jan 2021 10:01:03 +0100 Subject: [PATCH] recurring prototype --- .gitignore | 2 +- cmd/gte-generate-recurring/main.go | 45 ++++++++++++++++++++ internal/task/date.go | 14 +++++-- internal/task/recur.go | 54 +++++++++++++++++++++--- internal/task/recur_test.go | 33 +++++++++++++++ internal/task/task.go | 67 +++++++++++++++++++++++++++--- 6 files changed, 200 insertions(+), 15 deletions(-) create mode 100644 cmd/gte-generate-recurring/main.go create mode 100644 internal/task/recur_test.go diff --git a/.gitignore b/.gitignore index ee2601c..b43503b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -gte-process-inbox +/gte-process-inbox diff --git a/cmd/gte-generate-recurring/main.go b/cmd/gte-generate-recurring/main.go new file mode 100644 index 0000000..da776f8 --- /dev/null +++ b/cmd/gte-generate-recurring/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "log" + "os" + + "git.sr.ht/~ewintr/gte/internal/task" + "git.sr.ht/~ewintr/gte/pkg/mstore" +) + +func main() { + config := &mstore.ImapConfiguration{ + ImapUrl: os.Getenv("IMAP_URL"), + ImapUsername: os.Getenv("IMAP_USERNAME"), + ImapPassword: os.Getenv("IMAP_PASSWORD"), + } + if !config.Valid() { + log.Fatal("please set IMAP_USER, IMAP_PASSWORD, etc environment variables") + } + + mailStore, err := mstore.ImapConnect(config) + if err != nil { + log.Fatal(err) + } + defer mailStore.Disconnect() + + taskRepo := task.NewRepository(mailStore) + tasks, err := taskRepo.FindAll(task.FOLDER_RECURRING) + if err != nil { + log.Fatal(err) + } + for _, t := range tasks { + if t.RecursToday() { + subject, body, err := t.CreateNextMessage(task.Today) + if err != nil { + log.Fatal(err) + } + if err := mailStore.Add(task.FOLDER_PLANNED, subject, body); err != nil { + log.Fatal(err) + } + } + + } + +} diff --git a/internal/task/date.go b/internal/task/date.go index 734b071..0773bb6 100644 --- a/internal/task/date.go +++ b/internal/task/date.go @@ -96,7 +96,7 @@ func NewDateFromString(date string) Date { newWeekday = time.Sunday } - daysToAdd := int(newWeekday) - weekday + daysToAdd := int(newWeekday) - int(weekday) if daysToAdd <= 0 { daysToAdd += 7 } @@ -116,11 +116,19 @@ func (d *Date) IsZero() bool { return d.t.IsZero() } -func (d *Date) Weekday() int { - return int(d.t.Weekday()) +func (d *Date) Time() time.Time { + return d.t +} + +func (d *Date) Weekday() time.Weekday { + return d.t.Weekday() } func (d *Date) Add(days int) Date { year, month, day := d.t.Date() return NewDate(year, int(month), day+days) } + +func (d *Date) After(ud Date) bool { + return d.t.After(ud.Time()) +} diff --git a/internal/task/recur.go b/internal/task/recur.go index 4d1a2d9..45cacd8 100644 --- a/internal/task/recur.go +++ b/internal/task/recur.go @@ -1,25 +1,68 @@ package task -import "time" - -type Weekday time.Weekday +import ( + "strings" + "time" +) type Period int type Recurrer interface { + RecursOn(date Date) bool FirstAfter(date Date) Date + String() string } +func NewRecurrer(recurStr string) Recurrer { + terms := strings.Split(recurStr, ", ") + if len(terms) < 3 { + return nil + } + + startDate, err := time.Parse("2006-01-02", terms[0]) + if err != nil { + return nil + } + + if terms[1] != "weekly" { + return nil + } + + if terms[2] != "wednesday" { + return nil + } + + year, month, date := startDate.Date() + return Weekly{ + Start: NewDate(year, int(month), date), + Weekday: time.Wednesday, + } +} + +// yyyy-mm-dd, weekly, wednesday type Weekly struct { Start Date - Weekday Weekday + Weekday time.Weekday } -func (w *Weekly) FirstAfter(date Date) Date { +func (w Weekly) RecursOn(date Date) bool { + if !w.Start.After(date) { + return false + } + + return w.Weekday == date.Weekday() +} + +func (w Weekly) FirstAfter(date Date) Date { //sd := w.Start.Weekday() return date } +func (w Weekly) String() string { + return "2021-01-31, weekly, wednesday" +} + +/* type BiWeekly struct { Start Date Weekday Weekday @@ -30,3 +73,4 @@ type RecurringTask struct { Start Date Recurrer Recurrer } +*/ diff --git a/internal/task/recur_test.go b/internal/task/recur_test.go new file mode 100644 index 0000000..a6b9e76 --- /dev/null +++ b/internal/task/recur_test.go @@ -0,0 +1,33 @@ +package task_test + +import ( + "testing" + "time" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/gte/internal/task" +) + +func TestNewRecurrer(t *testing.T) { + for _, tc := range []struct { + name string + input string + exp task.Recurrer + }{ + { + name: "empty", + }, + { + name: "weekly", + input: "2021-01-31, weekly, wednesday", + exp: task.Weekly{ + Start: task.NewDate(2021, 1, 31), + Weekday: time.Wednesday, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, tc.exp, task.NewRecurrer(tc.input)) + }) + } +} diff --git a/internal/task/task.go b/internal/task/task.go index 0c63176..569ccda 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -11,7 +11,8 @@ import ( ) var ( - ErrOutdatedTask = errors.New("task is outdated") + ErrOutdatedTask = errors.New("task is outdated") + ErrTaskIsNotRecurring = errors.New("task is not recurring") ) const ( @@ -124,6 +125,13 @@ func New(msg *mstore.Message) *Task { } due := NewDateFromString(dueStr) + // Recurrer + recurStr, d := FieldFromBody(FIELD_RECUR, msg.Body) + if d { + dirty = true + } + recur := NewRecurrer(recurStr) + // Folder folderOld := msg.Folder folderNew := folderOld @@ -131,11 +139,14 @@ func New(msg *mstore.Message) *Task { switch { case newId: folderNew = FOLDER_NEW - case !newId && due.IsZero(): + case !newId && recur != nil: + folderNew = FOLDER_RECURRING + case !newId && recur == nil && due.IsZero(): folderNew = FOLDER_UNPLANNED - case !newId && !due.IsZero(): + case !newId && recur == nil && !due.IsZero(): folderNew = FOLDER_PLANNED } + } if folderOld != folderNew { dirty = true @@ -163,6 +174,7 @@ func New(msg *mstore.Message) *Task { Folder: folderNew, Action: action, Due: due, + Recur: recur, Project: project, Message: msg, Current: true, @@ -183,6 +195,13 @@ func (t *Task) FormatSubject() string { FIELD_DUE: t.Due.String(), } + if fields[FIELD_DUE] != "" && fields[FIELD_PROJECT] == "" { + fields[FIELD_PROJECT] = " " + } + if fields[FIELD_PROJECT] != "" && fields[FIELD_ACTION] == "" { + fields[FIELD_ACTION] = " " + } + parts := []string{} for _, f := range order { if fields[f] != "" { @@ -194,15 +213,21 @@ func (t *Task) FormatSubject() string { } func (t *Task) FormatBody() string { - body := fmt.Sprintf("\n") - order := []string{FIELD_ACTION, FIELD_DUE, FIELD_PROJECT, FIELD_VERSION, FIELD_ID} + order := []string{FIELD_ACTION} 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(), } + 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}...) keyLen := 0 for _, f := range order { @@ -211,6 +236,7 @@ func (t *Task) FormatBody() string { } } + body := fmt.Sprintf("\n") for _, f := range order { key := f + FIELD_SEPARATOR for i := len(key); i <= keyLen; i++ { @@ -226,6 +252,35 @@ func (t *Task) FormatBody() string { return body } +func (t *Task) IsRecurrer() bool { + return t.Recur != nil +} + +func (t *Task) RecursToday() bool { + if !t.IsRecurrer() { + return false + } + return true + + return t.Recur.RecursOn(Today) +} + +func (t *Task) CreateNextMessage(date Date) (string, string, error) { + if !t.IsRecurrer() { + return "", "", ErrTaskIsNotRecurring + } + + tempTask := &Task{ + Id: uuid.New().String(), + Version: 1, + Action: t.Action, + Project: t.Project, + Due: t.Recur.FirstAfter(date), + } + + return tempTask.FormatSubject(), tempTask.FormatBody(), nil +} + func FieldFromBody(field, body string) (string, bool) { value := "" dirty := false