async new with field parse

This commit is contained in:
Erik Winter 2021-09-04 12:20:35 +02:00
parent 22cad82870
commit 529b24aa13
15 changed files with 261 additions and 101 deletions

View File

@ -1,34 +0,0 @@
package command
import (
"strings"
"git.ewintr.nl/gte/cmd/cli/format"
"git.ewintr.nl/gte/internal/configuration"
"git.ewintr.nl/gte/internal/storage"
"git.ewintr.nl/gte/internal/task"
"git.ewintr.nl/gte/pkg/msend"
)
// Add sends an action to the NEW folder so it can be updated to a real task later
type Add struct {
disp *storage.Dispatcher
action string
}
func NewAdd(conf *configuration.Configuration, cmdArgs []string) (*Add, error) {
disp := storage.NewDispatcher(msend.NewSSLSMTP(conf.SMTP()))
return &Add{
disp: disp,
action: strings.Join(cmdArgs, " "),
}, nil
}
func (n *Add) Do() string {
if err := n.disp.Dispatch(&task.Task{Action: n.action}); err != nil {
return format.FormatError(err)
}
return "message sent\n"
}

View File

@ -11,7 +11,6 @@ var (
ErrInvalidAmountOfArgs = errors.New("invalid amount of args") ErrInvalidAmountOfArgs = errors.New("invalid amount of args")
ErrInvalidArg = errors.New("invalid argument") ErrInvalidArg = errors.New("invalid argument")
ErrCouldNotFindTask = errors.New("could not find task") ErrCouldNotFindTask = errors.New("could not find task")
ErrFieldAlreadyUsed = errors.New("field was already used")
ErrUnknownFolder = errors.New("unknown folder") ErrUnknownFolder = errors.New("unknown folder")
) )
@ -47,8 +46,8 @@ func Parse(args []string, conf *configuration.Configuration) (Command, error) {
return NewProjects(conf) return NewProjects(conf)
case "folder": case "folder":
return NewFolder(conf, cmdArgs) return NewFolder(conf, cmdArgs)
case "add": case "new":
return NewAdd(conf, cmdArgs) return NewNew(conf, cmdArgs)
case "remote": case "remote":
return parseRemote(conf, cmdArgs) return parseRemote(conf, cmdArgs)
default: default:

37
cmd/cli/command/new.go Normal file
View File

@ -0,0 +1,37 @@
package command
import (
"git.ewintr.nl/gte/cmd/cli/format"
"git.ewintr.nl/gte/internal/configuration"
"git.ewintr.nl/gte/internal/process"
"git.ewintr.nl/gte/internal/storage"
)
// New sends an action to the NEW folder so it can be updated to a real task later
type New struct {
newer *process.New
}
func NewNew(conf *configuration.Configuration, cmdArgs []string) (*New, error) {
local, err := storage.NewSqlite(conf.Sqlite())
if err != nil {
return &New{}, err
}
update, err := format.ParseTaskFieldArgs(cmdArgs)
if err != nil {
return &New{}, err
}
return &New{
newer: process.NewNew(local, update),
}, nil
}
func (n *New) Do() string {
if err := n.newer.Process(); err != nil {
return format.FormatError(err)
}
return ""
}

View File

@ -1,14 +1,10 @@
package command package command
import ( import (
"fmt"
"strings"
"git.ewintr.nl/gte/cmd/cli/format" "git.ewintr.nl/gte/cmd/cli/format"
"git.ewintr.nl/gte/internal/configuration" "git.ewintr.nl/gte/internal/configuration"
"git.ewintr.nl/gte/internal/process" "git.ewintr.nl/gte/internal/process"
"git.ewintr.nl/gte/internal/storage" "git.ewintr.nl/gte/internal/storage"
"git.ewintr.nl/gte/internal/task"
) )
type Update struct { type Update struct {
@ -21,7 +17,7 @@ func NewUpdate(localId int, conf *configuration.Configuration, cmdArgs []string)
return &Update{}, err return &Update{}, err
} }
update, err := ParseTaskFieldArgs(cmdArgs) update, err := format.ParseTaskFieldArgs(cmdArgs)
if err != nil { if err != nil {
return &Update{}, err return &Update{}, err
} }
@ -43,43 +39,5 @@ func (u *Update) Do() string {
return format.FormatError(err) return format.FormatError(err)
} }
return "local task updated\n" return ""
}
func ParseTaskFieldArgs(args []string) (*task.LocalUpdate, error) {
lu := &task.LocalUpdate{}
action, fields := []string{}, []string{}
for _, f := range args {
split := strings.SplitN(f, ":", 2)
if len(split) == 2 {
switch split[0] {
case "project":
if lu.Project != "" {
return &task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_PROJECT)
}
lu.Project = split[1]
fields = append(fields, task.FIELD_PROJECT)
case "due":
if !lu.Due.IsZero() {
return &task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_DUE)
}
lu.Due = task.NewDateFromString(split[1])
fields = append(fields, task.FIELD_DUE)
}
} else {
if len(f) > 0 {
action = append(action, f)
}
}
}
if len(action) > 0 {
lu.Action = strings.Join(action, " ")
fields = append(fields, task.FIELD_ACTION)
}
lu.Fields = fields
return lu, nil
} }

View File

@ -1,12 +1,18 @@
package format package format
import ( import (
"errors"
"fmt" "fmt"
"sort" "sort"
"strings"
"git.ewintr.nl/gte/internal/task" "git.ewintr.nl/gte/internal/task"
) )
var (
ErrFieldAlreadyUsed = errors.New("field was already used")
)
func FormatError(err error) string { func FormatError(err error) string {
return fmt.Sprintf("could not perform command.\n\nerror: %s\n", err.Error()) return fmt.Sprintf("could not perform command.\n\nerror: %s\n", err.Error())
} }
@ -42,3 +48,41 @@ due: %s
return output return output
} }
func ParseTaskFieldArgs(args []string) (*task.LocalUpdate, error) {
lu := &task.LocalUpdate{}
action, fields := []string{}, []string{}
for _, f := range args {
split := strings.SplitN(f, ":", 2)
if len(split) == 2 {
switch split[0] {
case "project":
if lu.Project != "" {
return &task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_PROJECT)
}
lu.Project = split[1]
fields = append(fields, task.FIELD_PROJECT)
case "due":
if !lu.Due.IsZero() {
return &task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_DUE)
}
lu.Due = task.NewDateFromString(split[1])
fields = append(fields, task.FIELD_DUE)
}
} else {
if len(f) > 0 {
action = append(action, f)
}
}
}
if len(action) > 0 {
lu.Action = strings.Join(action, " ")
fields = append(fields, task.FIELD_ACTION)
}
lu.Fields = fields
return lu, nil
}

View File

@ -1,4 +1,4 @@
package command_test package format_test
import ( import (
"errors" "errors"
@ -6,7 +6,7 @@ import (
"testing" "testing"
"git.ewintr.nl/go-kit/test" "git.ewintr.nl/go-kit/test"
"git.ewintr.nl/gte/cmd/cli/command" "git.ewintr.nl/gte/cmd/cli/format"
"git.ewintr.nl/gte/internal/task" "git.ewintr.nl/gte/internal/task"
) )
@ -53,12 +53,12 @@ func TestParseTaskFieldArgs(t *testing.T) {
name: "two projects", name: "two projects",
input: "project:project1 project:project2", input: "project:project1 project:project2",
expUpdate: &task.LocalUpdate{}, expUpdate: &task.LocalUpdate{},
expErr: command.ErrFieldAlreadyUsed, expErr: format.ErrFieldAlreadyUsed,
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
args := strings.Split(tc.input, " ") args := strings.Split(tc.input, " ")
act, err := command.ParseTaskFieldArgs(args) act, err := format.ParseTaskFieldArgs(args)
test.Equals(t, tc.expUpdate, act) test.Equals(t, tc.expUpdate, act)
test.Assert(t, errors.Is(err, tc.expErr), "wrong err") test.Assert(t, errors.Is(err, tc.expErr), "wrong err")
}) })

33
internal/process/new.go Normal file
View File

@ -0,0 +1,33 @@
package process
import (
"errors"
"fmt"
"git.ewintr.nl/gte/internal/storage"
"git.ewintr.nl/gte/internal/task"
)
var (
ErrNewTask = errors.New("could not add new task")
)
type New struct {
local storage.LocalRepository
update *task.LocalUpdate
}
func NewNew(local storage.LocalRepository, update *task.LocalUpdate) *New {
return &New{
local: local,
update: update,
}
}
func (n *New) Process() error {
if _, err := n.local.Add(n.update); err != nil {
return fmt.Errorf("%w: %v", ErrNewTask, err)
}
return nil
}

View File

@ -0,0 +1,28 @@
package process_test
import (
"testing"
"git.ewintr.nl/go-kit/test"
"git.ewintr.nl/gte/internal/process"
"git.ewintr.nl/gte/internal/storage"
"git.ewintr.nl/gte/internal/task"
)
func TestNew(t *testing.T) {
local := storage.NewMemory()
update := &task.LocalUpdate{
Fields: []string{task.FIELD_ACTION, task.FIELD_PROJECT, task.FIELD_DUE},
Project: "project",
Action: "action",
Due: task.NewDate(2021, 9, 4),
}
n := process.NewNew(local, update)
test.OK(t, n.Process())
tasks, err := local.FindAll()
test.OK(t, err)
test.Assert(t, len(tasks) == 1, "amount of tasks was not 1")
tsk := tasks[0]
test.Assert(t, tsk.Id != "", "id was empty")
test.Equals(t, update, tsk.LocalUpdate)
}

View File

@ -50,7 +50,7 @@ func TestSend(t *testing.T) {
lt, err := local.FindById(task2.Id) lt, err := local.FindById(task2.Id)
test.OK(t, err) test.OK(t, err)
lt.AddUpdate(lu) lt.AddUpdate(lu)
test.OK(t, local.SetLocalUpdate(lt)) test.OK(t, local.SetLocalUpdate(lt.Id, lt.LocalUpdate))
out := msend.NewMemory() out := msend.NewMemory()
disp := storage.NewDispatcher(out) disp := storage.NewDispatcher(out)

View File

@ -33,7 +33,7 @@ func (u *Update) Process() error {
return fmt.Errorf("%w: %v", ErrUpdateTask, err) return fmt.Errorf("%w: %v", ErrUpdateTask, err)
} }
tsk.AddUpdate(u.update) tsk.AddUpdate(u.update)
if err := u.local.SetLocalUpdate(tsk); err != nil { if err := u.local.SetLocalUpdate(tsk.Id, tsk.LocalUpdate); err != nil {
return fmt.Errorf("%w: %v", ErrUpdateTask, err) return fmt.Errorf("%w: %v", ErrUpdateTask, err)
} }

View File

@ -18,8 +18,9 @@ type LocalRepository interface {
FindAll() ([]*task.LocalTask, error) FindAll() ([]*task.LocalTask, error)
FindById(id string) (*task.LocalTask, error) FindById(id string) (*task.LocalTask, error)
FindByLocalId(id int) (*task.LocalTask, error) FindByLocalId(id int) (*task.LocalTask, error)
SetLocalUpdate(tsk *task.LocalTask) error SetLocalUpdate(id string, update *task.LocalUpdate) error
MarkDispatched(id int) error MarkDispatched(id int) error
Add(update *task.LocalUpdate) (*task.LocalTask, error)
} }
// NextLocalId finds a new local id by incrememting to a variable limit. // NextLocalId finds a new local id by incrememting to a variable limit.

View File

@ -4,6 +4,7 @@ import (
"time" "time"
"git.ewintr.nl/gte/internal/task" "git.ewintr.nl/gte/internal/task"
"github.com/google/uuid"
) )
// Memory is an in memory implementation of LocalRepository // Memory is an in memory implementation of LocalRepository
@ -68,9 +69,9 @@ func (m *Memory) FindByLocalId(localId int) (*task.LocalTask, error) {
return &task.LocalTask{}, ErrTaskNotFound return &task.LocalTask{}, ErrTaskNotFound
} }
func (m *Memory) SetLocalUpdate(tsk *task.LocalTask) error { func (m *Memory) SetLocalUpdate(id string, update *task.LocalUpdate) error {
tsk.LocalStatus = task.STATUS_UPDATED m.tasks[id].LocalStatus = task.STATUS_UPDATED
m.tasks[tsk.Id] = tsk m.tasks[id].LocalUpdate = update
return nil return nil
} }
@ -81,3 +82,25 @@ func (m *Memory) MarkDispatched(localId int) error {
return nil return nil
} }
func (m *Memory) Add(update *task.LocalUpdate) (*task.LocalTask, error) {
var used []int
for _, t := range m.tasks {
used = append(used, t.LocalId)
}
tsk := &task.LocalTask{
Task: task.Task{
Id: uuid.New().String(),
Version: 0,
Folder: task.FOLDER_NEW,
},
LocalId: NextLocalId(used),
LocalStatus: task.STATUS_UPDATED,
LocalUpdate: update,
}
m.tasks[tsk.Id] = tsk
return tsk, nil
}

View File

@ -105,13 +105,8 @@ func TestMemory(t *testing.T) {
Recur: task.NewRecurrer("today, weekly, monday"), Recur: task.NewRecurrer("today, weekly, monday"),
Done: true, Done: true,
} }
lt := &task.LocalTask{ test.OK(t, mem.SetLocalUpdate(task2.Id, expUpdate))
Task: *task2, actTask, err := mem.FindById(task2.Id)
LocalId: 2,
LocalUpdate: expUpdate,
}
test.OK(t, mem.SetLocalUpdate(lt))
actTask, err := mem.FindByLocalId(2)
test.OK(t, err) test.OK(t, err)
test.Equals(t, expUpdate, actTask.LocalUpdate) test.Equals(t, expUpdate, actTask.LocalUpdate)
test.Equals(t, task.STATUS_UPDATED, actTask.LocalStatus) test.Equals(t, task.STATUS_UPDATED, actTask.LocalStatus)
@ -127,4 +122,36 @@ func TestMemory(t *testing.T) {
test.OK(t, err) test.OK(t, err)
test.Equals(t, task.STATUS_DISPATCHED, act.LocalStatus) test.Equals(t, task.STATUS_DISPATCHED, act.LocalStatus)
}) })
t.Run("add", func(t *testing.T) {
action := "action"
project := "project"
due := task.NewDate(2021, 9, 4)
recur := task.Daily{Start: task.NewDate(2021, 9, 5)}
mem := storage.NewMemory()
expUpdate := &task.LocalUpdate{
Fields: []string{task.FIELD_ACTION, task.FIELD_PROJECT, task.FIELD_DUE, task.FIELD_RECUR},
Action: action,
Project: project,
Due: due,
Recur: recur,
}
act1, err := mem.Add(expUpdate)
test.OK(t, err)
test.Assert(t, act1.Id != "", "id was empty")
test.Equals(t, task.FOLDER_NEW, act1.Folder)
test.Equals(t, "", act1.Action)
test.Equals(t, "", act1.Project)
test.Assert(t, act1.Due.IsZero(), "date was not zero")
test.Equals(t, nil, act1.Recur)
test.Equals(t, 0, act1.Version)
test.Equals(t, 1, act1.LocalId)
test.Equals(t, task.STATUS_UPDATED, act1.LocalStatus)
test.Equals(t, expUpdate, act1.LocalUpdate)
act2, err := mem.FindById(act1.Id)
test.OK(t, err)
test.Equals(t, act1, act2)
})
} }

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"git.ewintr.nl/gte/internal/task" "git.ewintr.nl/gte/internal/task"
"github.com/google/uuid"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
@ -188,11 +189,11 @@ func (s *Sqlite) FindByLocalId(localId int) (*task.LocalTask, error) {
return t, nil return t, nil
} }
func (s *Sqlite) SetLocalUpdate(tsk *task.LocalTask) error { func (s *Sqlite) SetLocalUpdate(id string, update *task.LocalUpdate) error {
if _, err := s.db.Exec(` if _, err := s.db.Exec(`
UPDATE task UPDATE task
SET local_update = ?, local_status = ? SET local_update = ?, local_status = ?
WHERE local_id = ?`, tsk.LocalUpdate, task.STATUS_UPDATED, tsk.LocalId); err != nil { WHERE id = ?`, update, task.STATUS_UPDATED, id); err != nil {
return fmt.Errorf("%w: %v", ErrSqliteFailure, err) return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} }
@ -209,6 +210,49 @@ WHERE local_id = ?`, task.STATUS_DISPATCHED, localId); err != nil {
return nil return nil
} }
func (s *Sqlite) Add(update *task.LocalUpdate) (*task.LocalTask, error) {
rows, err := s.db.Query(`SELECT local_id FROM task`)
if err != nil {
return &task.LocalTask{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
}
var used []int
for rows.Next() {
var localId int
if err := rows.Scan(&localId); err != nil {
return &task.LocalTask{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
}
used = append(used, localId)
}
rows.Close()
tsk := &task.LocalTask{
Task: task.Task{
Id: uuid.New().String(),
Version: 0,
Folder: task.FOLDER_NEW,
},
LocalId: NextLocalId(used),
LocalStatus: task.STATUS_UPDATED,
LocalUpdate: update,
}
var recurStr string
if tsk.LocalUpdate.Recur != nil {
recurStr = tsk.LocalUpdate.Recur.String()
}
if _, err := s.db.Exec(`
INSERT INTO task
(id, local_id, version, folder, action, project, due, recur, local_status, local_update)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
tsk.Id, tsk.LocalId, tsk.Version, tsk.Folder, tsk.Action, tsk.Project,
tsk.Due.String(), recurStr, tsk.LocalStatus, tsk.LocalUpdate); err != nil {
return &task.LocalTask{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
}
return tsk, nil
}
func (s *Sqlite) migrate(wanted []sqliteMigration) error { func (s *Sqlite) migrate(wanted []sqliteMigration) error {
// admin table // admin table
if _, err := s.db.Exec(` if _, err := s.db.Exec(`

View File

@ -37,7 +37,7 @@ func (lt *LocalTask) ApplyUpdate() {
return return
} }
u := lt.LocalUpdate u := lt.LocalUpdate
if u.ForVersion == 0 || u.ForVersion != lt.Version { if u.ForVersion != lt.Version {
lt.LocalUpdate = &LocalUpdate{} lt.LocalUpdate = &LocalUpdate{}
return return
} }