recurring prototype
This commit is contained in:
parent
e50d624b9c
commit
18d3074633
|
@ -1,2 +1,2 @@
|
||||||
gte-process-inbox
|
/gte-process-inbox
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -96,7 +96,7 @@ func NewDateFromString(date string) Date {
|
||||||
newWeekday = time.Sunday
|
newWeekday = time.Sunday
|
||||||
}
|
}
|
||||||
|
|
||||||
daysToAdd := int(newWeekday) - weekday
|
daysToAdd := int(newWeekday) - int(weekday)
|
||||||
if daysToAdd <= 0 {
|
if daysToAdd <= 0 {
|
||||||
daysToAdd += 7
|
daysToAdd += 7
|
||||||
}
|
}
|
||||||
|
@ -116,11 +116,19 @@ func (d *Date) IsZero() bool {
|
||||||
return d.t.IsZero()
|
return d.t.IsZero()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Date) Weekday() int {
|
func (d *Date) Time() time.Time {
|
||||||
return int(d.t.Weekday())
|
return d.t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Date) Weekday() time.Weekday {
|
||||||
|
return d.t.Weekday()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Date) Add(days int) Date {
|
func (d *Date) Add(days int) Date {
|
||||||
year, month, day := d.t.Date()
|
year, month, day := d.t.Date()
|
||||||
return NewDate(year, int(month), day+days)
|
return NewDate(year, int(month), day+days)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Date) After(ud Date) bool {
|
||||||
|
return d.t.After(ud.Time())
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +1,68 @@
|
||||||
package task
|
package task
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"strings"
|
||||||
type Weekday time.Weekday
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Period int
|
type Period int
|
||||||
type Recurrer interface {
|
type Recurrer interface {
|
||||||
|
RecursOn(date Date) bool
|
||||||
FirstAfter(date Date) Date
|
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 {
|
type Weekly struct {
|
||||||
Start Date
|
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()
|
//sd := w.Start.Weekday()
|
||||||
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w Weekly) String() string {
|
||||||
|
return "2021-01-31, weekly, wednesday"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
type BiWeekly struct {
|
type BiWeekly struct {
|
||||||
Start Date
|
Start Date
|
||||||
Weekday Weekday
|
Weekday Weekday
|
||||||
|
@ -30,3 +73,4 @@ type RecurringTask struct {
|
||||||
Start Date
|
Start Date
|
||||||
Recurrer Recurrer
|
Recurrer Recurrer
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrOutdatedTask = errors.New("task is outdated")
|
ErrOutdatedTask = errors.New("task is outdated")
|
||||||
|
ErrTaskIsNotRecurring = errors.New("task is not recurring")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -124,6 +125,13 @@ func New(msg *mstore.Message) *Task {
|
||||||
}
|
}
|
||||||
due := NewDateFromString(dueStr)
|
due := NewDateFromString(dueStr)
|
||||||
|
|
||||||
|
// Recurrer
|
||||||
|
recurStr, d := FieldFromBody(FIELD_RECUR, msg.Body)
|
||||||
|
if d {
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
recur := NewRecurrer(recurStr)
|
||||||
|
|
||||||
// Folder
|
// Folder
|
||||||
folderOld := msg.Folder
|
folderOld := msg.Folder
|
||||||
folderNew := folderOld
|
folderNew := folderOld
|
||||||
|
@ -131,11 +139,14 @@ func New(msg *mstore.Message) *Task {
|
||||||
switch {
|
switch {
|
||||||
case newId:
|
case newId:
|
||||||
folderNew = FOLDER_NEW
|
folderNew = FOLDER_NEW
|
||||||
case !newId && due.IsZero():
|
case !newId && recur != nil:
|
||||||
|
folderNew = FOLDER_RECURRING
|
||||||
|
case !newId && recur == nil && due.IsZero():
|
||||||
folderNew = FOLDER_UNPLANNED
|
folderNew = FOLDER_UNPLANNED
|
||||||
case !newId && !due.IsZero():
|
case !newId && recur == nil && !due.IsZero():
|
||||||
folderNew = FOLDER_PLANNED
|
folderNew = FOLDER_PLANNED
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if folderOld != folderNew {
|
if folderOld != folderNew {
|
||||||
dirty = true
|
dirty = true
|
||||||
|
@ -163,6 +174,7 @@ func New(msg *mstore.Message) *Task {
|
||||||
Folder: folderNew,
|
Folder: folderNew,
|
||||||
Action: action,
|
Action: action,
|
||||||
Due: due,
|
Due: due,
|
||||||
|
Recur: recur,
|
||||||
Project: project,
|
Project: project,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
Current: true,
|
Current: true,
|
||||||
|
@ -183,6 +195,13 @@ func (t *Task) FormatSubject() string {
|
||||||
FIELD_DUE: t.Due.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{}
|
parts := []string{}
|
||||||
for _, f := range order {
|
for _, f := range order {
|
||||||
if fields[f] != "" {
|
if fields[f] != "" {
|
||||||
|
@ -194,15 +213,21 @@ func (t *Task) FormatSubject() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Task) FormatBody() string {
|
func (t *Task) FormatBody() string {
|
||||||
body := fmt.Sprintf("\n")
|
order := []string{FIELD_ACTION}
|
||||||
order := []string{FIELD_ACTION, FIELD_DUE, FIELD_PROJECT, FIELD_VERSION, FIELD_ID}
|
|
||||||
fields := map[string]string{
|
fields := map[string]string{
|
||||||
FIELD_ID: t.Id,
|
FIELD_ID: t.Id,
|
||||||
FIELD_VERSION: strconv.Itoa(t.Version),
|
FIELD_VERSION: strconv.Itoa(t.Version),
|
||||||
FIELD_PROJECT: t.Project,
|
FIELD_PROJECT: t.Project,
|
||||||
FIELD_ACTION: t.Action,
|
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
|
keyLen := 0
|
||||||
for _, f := range order {
|
for _, f := range order {
|
||||||
|
@ -211,6 +236,7 @@ func (t *Task) FormatBody() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body := fmt.Sprintf("\n")
|
||||||
for _, f := range order {
|
for _, f := range order {
|
||||||
key := f + FIELD_SEPARATOR
|
key := f + FIELD_SEPARATOR
|
||||||
for i := len(key); i <= keyLen; i++ {
|
for i := len(key); i <= keyLen; i++ {
|
||||||
|
@ -226,6 +252,35 @@ func (t *Task) FormatBody() string {
|
||||||
return body
|
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) {
|
func FieldFromBody(field, body string) (string, bool) {
|
||||||
value := ""
|
value := ""
|
||||||
dirty := false
|
dirty := false
|
||||||
|
|
Loading…
Reference in New Issue